Merge branch 'main' of https://github.com/apache/cloudstack into clvm-enhancements

This commit is contained in:
Pearl Dsilva 2026-04-13 14:30:51 -04:00
commit be2994db0f
143 changed files with 9050 additions and 542 deletions

1
.github/CODEOWNERS vendored
View File

@ -17,6 +17,7 @@
/plugins/storage/volume/linstor @rp-
/plugins/storage/volume/storpool @slavkap
/plugins/storage/volume/ontap @rajiv1 @sandeeplocharla @piyush5 @suryag
.pre-commit-config.yaml @jbampton
/.github/linters/ @jbampton

View File

@ -71,7 +71,6 @@ import org.apache.cloudstack.api.command.user.vm.GetVMPasswordCmd;
import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.config.ConfigurationGroup;
import org.apache.cloudstack.framework.config.ConfigKey;
import com.cloud.alert.Alert;
import com.cloud.capacity.Capacity;
@ -108,14 +107,6 @@ import com.cloud.vm.VirtualMachineProfile;
public interface ManagementService {
static final String Name = "management-server";
ConfigKey<Boolean> JsInterpretationEnabled = new ConfigKey<>("Hidden"
, Boolean.class
, "js.interpretation.enabled"
, "false"
, "Enable/Disable all JavaScript interpretation related functionalities to create or update Javascript rules."
, false
, ConfigKey.Scope.Global);
/**
* returns the a map of the names/values in the configuration table
*
@ -534,6 +525,4 @@ public interface ManagementService {
boolean removeManagementServer(RemoveManagementServerCmd cmd);
void checkJsInterpretationAllowedIfNeededForParameterValue(String paramName, boolean paramValue);
}

View File

@ -138,6 +138,8 @@ public interface AccountService {
Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly);
Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId);
/**
* returns the user account object for a given user id
* @param userId user id

View File

@ -20,6 +20,7 @@ public class ApiConstants {
public static final String ACCOUNT = "account";
public static final String ACCOUNTS = "accounts";
public static final String ACCOUNT_NAME = "accountname";
public static final String ACCOUNT_STATE_TO_SHOW = "accountstatetoshow";
public static final String ACCOUNT_TYPE = "accounttype";
public static final String ACCOUNT_ID = "accountid";
public static final String ACCOUNT_IDS = "accountids";

View File

@ -37,7 +37,7 @@ public interface VolumeImportUnmanageService extends PluggableService, Configura
Arrays.asList(Hypervisor.HypervisorType.KVM, Hypervisor.HypervisorType.VMware);
List<Storage.StoragePoolType> SUPPORTED_STORAGE_POOL_TYPES_FOR_KVM = Arrays.asList(Storage.StoragePoolType.NetworkFilesystem,
Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.RBD);
Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.RBD, Storage.StoragePoolType.SharedMountPoint);
ConfigKey<Boolean> AllowImportVolumeWithBackingFile = new ConfigKey<>(Boolean.class,
"allow.import.volume.with.backing.file",

View File

@ -121,6 +121,11 @@
<artifactId>cloud-plugin-storage-volume-adaptive</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-storage-volume-ontap</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-storage-volume-solidfire</artifactId>

View File

@ -52,6 +52,7 @@ import com.cloud.storage.StorageLayer;
import com.cloud.utils.Pair;
import com.cloud.utils.UriUtils;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.HttpClientCloudStackUserAgent;
import com.cloud.utils.net.Proxy;
/**
@ -125,6 +126,7 @@ public class HttpTemplateDownloader extends ManagedContextRunnable implements Te
GetMethod request = new GetMethod(downloadUrl);
request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler);
request.setFollowRedirects(followRedirects);
request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT);
return request;
}

View File

@ -18,8 +18,11 @@
//
package com.cloud.storage.template;
import com.cloud.storage.StorageLayer;
import com.cloud.utils.UriUtils;
import com.cloud.utils.net.HttpClientCloudStackUserAgent;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodRetryHandler;
@ -59,6 +62,7 @@ public class MetalinkTemplateDownloader extends TemplateDownloaderBase implement
GetMethod request = new GetMethod(downloadUrl);
request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler);
request.setFollowRedirects(followRedirects);
request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT);
if (!toFileSet) {
String[] parts = downloadUrl.split("/");
String filename = parts[parts.length - 1];

View File

@ -44,6 +44,7 @@ import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.lang3.StringUtils;
import com.cloud.storage.StorageLayer;
import com.cloud.utils.net.HttpClientCloudStackUserAgent;
public class SimpleHttpMultiFileDownloader extends ManagedContextRunnable implements TemplateDownloader {
private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager();
@ -95,6 +96,7 @@ public class SimpleHttpMultiFileDownloader extends ManagedContextRunnable implem
GetMethod request = new GetMethod(downloadUrl);
request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler);
request.setFollowRedirects(followRedirects);
request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT);
return request;
}
@ -141,6 +143,7 @@ public class SimpleHttpMultiFileDownloader extends ManagedContextRunnable implem
continue;
}
HeadMethod headMethod = new HeadMethod(downloadUrl);
headMethod.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT);
try {
if (client.executeMethod(headMethod) != HttpStatus.SC_OK) {
continue;

View File

@ -34,6 +34,7 @@ public class RestoreBackupCommand extends Command {
private List<String> backupVolumesUUIDs;
private List<PrimaryDataStoreTO> restoreVolumePools;
private List<String> restoreVolumePaths;
private List<Long> restoreVolumeSizes;
private List<String> backupFiles;
private String diskType;
private Boolean vmExists;
@ -92,6 +93,14 @@ public class RestoreBackupCommand extends Command {
this.restoreVolumePaths = restoreVolumePaths;
}
public List<Long> getRestoreVolumeSizes() {
return restoreVolumeSizes;
}
public void setRestoreVolumeSizes(List<Long> restoreVolumeSizes) {
this.restoreVolumeSizes = restoreVolumeSizes;
}
public List<String> getBackupFiles() {
return backupFiles;
}

View File

@ -19,6 +19,7 @@
package org.apache.cloudstack.direct.download;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -32,6 +33,7 @@ import java.util.Map;
import com.cloud.utils.Pair;
import com.cloud.utils.UriUtils;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.HttpClientCloudStackUserAgent;
import com.cloud.utils.storage.QCOW2Utils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.httpclient.HttpClient;
@ -39,6 +41,7 @@ import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.io.IOUtils;
public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl {
@ -68,6 +71,7 @@ public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl {
protected GetMethod createRequest(String downloadUrl, Map<String, String> headers) {
GetMethod request = new GetMethod(downloadUrl);
request.setFollowRedirects(this.isFollowRedirects());
request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT);
if (MapUtils.isNotEmpty(headers)) {
for (String key : headers.keySet()) {
request.setRequestHeader(key, headers.get(key));
@ -111,6 +115,7 @@ public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl {
public boolean checkUrl(String url) {
HeadMethod httpHead = new HeadMethod(url);
httpHead.setFollowRedirects(this.isFollowRedirects());
httpHead.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT);
try {
int responseCode = client.executeMethod(httpHead);
if (responseCode != HttpStatus.SC_OK) {

View File

@ -17,6 +17,7 @@
package com.cloud.vm;
import static com.cloud.configuration.ConfigurationManagerImpl.EXPOSE_ERRORS_TO_USER;
import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS;
import java.lang.reflect.Field;
@ -941,10 +942,22 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
public void start(final String vmUuid, final Map<VirtualMachineProfile.Param, Object> params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) {
try {
advanceStart(vmUuid, params, planToDeploy, planner);
} catch (ConcurrentOperationException | InsufficientCapacityException e) {
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid);
} catch (ConcurrentOperationException e) {
final CallContext cctxt = CallContext.current();
final Account account = cctxt.getCallingAccount();
if (canExposeError(account)) {
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid);
}
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to concurrent operation.", vmUuid), e).add(VirtualMachine.class, vmUuid);
} catch (final InsufficientCapacityException e) {
final CallContext cctxt = CallContext.current();
final Account account = cctxt.getCallingAccount();
if (canExposeError(account)) {
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid);
}
throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to insufficient capacity.", vmUuid), e).add(VirtualMachine.class, vmUuid);
} catch (final ResourceUnavailableException e) {
if (e.getScope() != null && e.getScope().equals(VirtualRouter.class)){
if (e.getScope() != null && e.getScope().equals(VirtualRouter.class)) {
Account callingAccount = CallContext.current().getCallingAccount();
String errorSuffix = (callingAccount != null && callingAccount.getType() == Account.Type.ADMIN) ?
String.format("Failure: %s", e.getMessage()) :
@ -1375,6 +1388,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType());
Throwable lastKnownError = null;
boolean canRetry = true;
ExcludeList avoids = null;
try {
@ -1398,7 +1412,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
int retry = StartRetry.value();
while (retry-- != 0) {
logger.debug("Instance start attempt #{}", (StartRetry.value() - retry));
int attemptNumber = StartRetry.value() - retry;
logger.debug("Instance start attempt #{}", attemptNumber);
if (reuseVolume) {
final List<VolumeVO> vols = _volsDao.findReadyRootVolumesByInstance(vm.getId());
@ -1464,8 +1479,13 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
reuseVolume = false;
continue;
}
throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId(),
areAffinityGroupsAssociated(vmProfile));
String message = String.format("Unable to create a deployment for %s after %s attempts", vmProfile, attemptNumber);
if (canExposeError(account) && lastKnownError != null) {
message += String.format(" Last known error: %s", lastKnownError.getMessage());
throw new CloudRuntimeException(message, lastKnownError);
} else {
throw new InsufficientServerCapacityException(message, DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile));
}
}
avoids.addHost(dest.getHost().getId());
@ -1633,11 +1653,15 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
throw new ExecutionException("Unable to start VM:" + vm.getUuid() + " due to error in finalizeStart, not retrying");
}
}
logger.info("Unable to start VM on {} due to {}", dest.getHost(), (startAnswer == null ? " no start answer" : startAnswer.getDetails()));
String msg = String.format("Unable to start VM on %s due to %s", dest.getHost(), startAnswer == null ? "no start command answer" : startAnswer.getDetails());
lastKnownError = new ExecutionException(msg);
if (startAnswer != null && startAnswer.getContextParam("stopRetry") != null) {
logger.error(msg, lastKnownError);
break;
}
logger.debug(msg, lastKnownError);
} catch (OperationTimedoutException e) {
logger.debug("Unable to send the start command to host {} failed to start VM: {}", dest.getHost(), vm);
if (e.isActive()) {
@ -1647,6 +1671,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
throw new AgentUnavailableException("Unable to start " + vm.getHostName(), destHostId, e);
} catch (final ResourceUnavailableException e) {
logger.warn("Unable to contact resource.", e);
lastKnownError = e;
if (!avoids.add(e)) {
if (e.getScope() == Volume.class || e.getScope() == Nic.class) {
throw e;
@ -1703,10 +1728,22 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
}
if (startedVm == null) {
throw new CloudRuntimeException("Unable to start Instance '" + vm.getHostName() + "' (" + vm.getUuid() + "), see management server log for details");
String messageTmpl = "Unable to start Instance '%s' (%s)%s";
String details;
if (canExposeError(account) && lastKnownError != null) {
details = ": " + lastKnownError.getMessage();
} else {
details = ", see management server log for details";
}
String message = String.format(messageTmpl, vm.getHostName(), vm.getUuid(), details);
throw new CloudRuntimeException(message, lastKnownError);
}
}
private boolean canExposeError(Account account) {
return (account != null && account.getType() == Account.Type.ADMIN) || Boolean.TRUE.equals(EXPOSE_ERRORS_TO_USER.value());
}
protected void updateStartCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, StartCommand command) {
if (!HypervisorType.External.equals(host.getHypervisorType())) {
return;

View File

@ -262,7 +262,7 @@ public class DomainDaoImpl extends GenericDaoBase<DomainVO, Long> implements Dom
SearchCriteria<DomainVO> sc = DomainPairSearch.create();
sc.setParameters("id", parentId, childId);
List<DomainVO> domainPair = listBy(sc);
List<DomainVO> domainPair = listIncludingRemovedBy(sc);
if ((domainPair != null) && (domainPair.size() == 2)) {
DomainVO d1 = domainPair.get(0);

View File

@ -45,4 +45,9 @@ public interface HostTagsDao extends GenericDao<HostTagVO, Long> {
HostTagResponse newHostTagResponse(HostTagVO hostTag);
List<HostTagVO> searchByIds(Long... hostTagIds);
/**
* List all host tags defined on hosts within a cluster
*/
List<String> listByClusterId(Long clusterId);
}

View File

@ -23,6 +23,7 @@ import org.apache.cloudstack.api.response.HostTagResponse;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@ -43,9 +44,12 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
private final SearchBuilder<HostTagVO> stSearch;
private final SearchBuilder<HostTagVO> tagIdsearch;
private final SearchBuilder<HostTagVO> ImplicitTagsSearch;
private final GenericSearchBuilder<HostTagVO, String> tagSearch;
@Inject
private ConfigurationDao _configDao;
@Inject
private HostDao hostDao;
public HostTagsDaoImpl() {
HostSearch = createSearchBuilder();
@ -72,6 +76,11 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
ImplicitTagsSearch.and("hostId", ImplicitTagsSearch.entity().getHostId(), SearchCriteria.Op.EQ);
ImplicitTagsSearch.and("isImplicit", ImplicitTagsSearch.entity().getIsImplicit(), SearchCriteria.Op.EQ);
ImplicitTagsSearch.done();
tagSearch = createSearchBuilder(String.class);
tagSearch.selectFields(tagSearch.entity().getTag());
tagSearch.and("hostIdIN", tagSearch.entity().getHostId(), SearchCriteria.Op.IN);
tagSearch.done();
}
@Override
@ -235,4 +244,15 @@ public class HostTagsDaoImpl extends GenericDaoBase<HostTagVO, Long> implements
return tagList;
}
@Override
public List<String> listByClusterId(Long clusterId) {
List<Long> hostIds = hostDao.listIdsByClusterId(clusterId);
if (CollectionUtils.isEmpty(hostIds)) {
return new ArrayList<>();
}
SearchCriteria<String> sc = tagSearch.create();
sc.setParameters("hostIdIN", hostIds.toArray());
return customSearch(sc, null);
}
}

View File

@ -193,6 +193,7 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
PersistentNetworkSearch.and("id", PersistentNetworkSearch.entity().getId(), Op.NEQ);
PersistentNetworkSearch.and("guestType", PersistentNetworkSearch.entity().getGuestType(), Op.IN);
PersistentNetworkSearch.and("broadcastUri", PersistentNetworkSearch.entity().getBroadcastUri(), Op.EQ);
PersistentNetworkSearch.and("dc", PersistentNetworkSearch.entity().getDataCenterId(), Op.EQ);
PersistentNetworkSearch.and("removed", PersistentNetworkSearch.entity().getRemoved(), Op.NULL);
final SearchBuilder<NetworkOfferingVO> persistentNtwkOffJoin = _ntwkOffDao.createSearchBuilder();
persistentNtwkOffJoin.and("persistent", persistentNtwkOffJoin.entity().isPersistent(), Op.EQ);

View File

@ -170,6 +170,7 @@ public class SnapshotDaoImpl extends GenericDaoBase<SnapshotVO, Long> implements
CountSnapshotsByAccount.select(null, Func.COUNT, null);
CountSnapshotsByAccount.and("account", CountSnapshotsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ);
CountSnapshotsByAccount.and("status", CountSnapshotsByAccount.entity().getState(), SearchCriteria.Op.NIN);
CountSnapshotsByAccount.and("snapshotTypeNEQ", CountSnapshotsByAccount.entity().getSnapshotType(), SearchCriteria.Op.NIN);
CountSnapshotsByAccount.and("removed", CountSnapshotsByAccount.entity().getRemoved(), SearchCriteria.Op.NULL);
CountSnapshotsByAccount.done();
@ -220,6 +221,7 @@ public class SnapshotDaoImpl extends GenericDaoBase<SnapshotVO, Long> implements
SearchCriteria<Long> sc = CountSnapshotsByAccount.create();
sc.setParameters("account", accountId);
sc.setParameters("status", State.Error, State.Destroyed);
sc.setParameters("snapshotTypeNEQ", Snapshot.Type.GROUP.ordinal());
return customSearch(sc, null).get(0);
}

View File

@ -17,7 +17,12 @@
package com.cloud.upgrade.dao;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.exception.CloudRuntimeException;
public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate {
@ -42,4 +47,46 @@ public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgr
return new InputStream[] {script};
}
@Override
public void performDataMigration(Connection conn) {
unhideJsInterpretationEnabled(conn);
}
protected void unhideJsInterpretationEnabled(Connection conn) {
String value = getJsInterpretationEnabled(conn);
if (value != null) {
updateJsInterpretationEnabledFields(conn, value);
}
}
protected String getJsInterpretationEnabled(Connection conn) {
String query = "SELECT value FROM cloud.configuration WHERE name = 'js.interpretation.enabled' AND category = 'Hidden';";
try (PreparedStatement pstmt = conn.prepareStatement(query)) {
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return rs.getString("value");
}
logger.debug("Unable to retrieve value of hidden configuration 'js.interpretation.enabled'. The configuration may already be unhidden.");
return null;
} catch (SQLException e) {
throw new CloudRuntimeException("Error while retrieving value of hidden configuration 'js.interpretation.enabled'.", e);
}
}
protected void updateJsInterpretationEnabledFields(Connection conn, String encryptedValue) {
String query = "UPDATE cloud.configuration SET value = ?, category = 'System', component = 'JsInterpreter', is_dynamic = 1 WHERE name = 'js.interpretation.enabled';";
try (PreparedStatement pstmt = conn.prepareStatement(query)) {
String decryptedValue = DBEncryptionUtil.decrypt(encryptedValue);
logger.info("Updating setting 'js.interpretation.enabled' to decrypted value [{}], category 'System', component 'JsInterpreter', and is_dynamic '1'.", decryptedValue);
pstmt.setString(1, decryptedValue);
pstmt.executeUpdate();
} catch (SQLException e) {
throw new CloudRuntimeException("Error while unhiding configuration 'js.interpretation.enabled'.", e);
} catch (CloudRuntimeException e) {
logger.warn("Error while decrypting configuration 'js.interpretation.enabled'. The configuration may already be decrypted.");
}
}
}

View File

@ -51,6 +51,8 @@ StateDao<ObjectInDataStoreStateMachine.State, ObjectInDataStoreStateMachine.Even
SnapshotDataStoreVO findBySnapshotIdAndDataStoreRoleAndState(long snapshotId, DataStoreRole role, ObjectInDataStoreStateMachine.State state);
List<SnapshotDataStoreVO> listBySnapshotIdAndDataStoreRoleAndStateIn(long snapshotId, DataStoreRole role, ObjectInDataStoreStateMachine.State... state);
List<SnapshotDataStoreVO> listReadyByVolumeIdAndCheckpointPathNotNull(long volumeId);
SnapshotDataStoreVO findOneBySnapshotId(long snapshotId, long zoneId);

View File

@ -68,6 +68,7 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
protected SearchBuilder<SnapshotDataStoreVO> searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq;
private SearchBuilder<SnapshotDataStoreVO> stateSearch;
private SearchBuilder<SnapshotDataStoreVO> idStateNinSearch;
private SearchBuilder<SnapshotDataStoreVO> idEqRoleEqStateInSearch;
protected SearchBuilder<SnapshotVO> snapshotVOSearch;
private SearchBuilder<SnapshotDataStoreVO> snapshotCreatedSearch;
private SearchBuilder<SnapshotDataStoreVO> dataStoreAndInstallPathSearch;
@ -151,6 +152,10 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
idStateNinSearch.and(STATE, idStateNinSearch.entity().getState(), SearchCriteria.Op.NOTIN);
idStateNinSearch.done();
idEqRoleEqStateInSearch = createSearchBuilder();
idEqRoleEqStateInSearch.and(SNAPSHOT_ID, idEqRoleEqStateInSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ);
idEqRoleEqStateInSearch.and(STORE_ROLE, idEqRoleEqStateInSearch.entity().getRole(), SearchCriteria.Op.EQ);
idEqRoleEqStateInSearch.and(STATE, idEqRoleEqStateInSearch.entity().getState(), SearchCriteria.Op.IN);
snapshotVOSearch = snapshotDao.createSearchBuilder();
snapshotVOSearch.and(VOLUME_ID, snapshotVOSearch.entity().getVolumeId(), SearchCriteria.Op.EQ);
@ -388,6 +393,15 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase<SnapshotDataStoreVO
return findOneBy(sc);
}
@Override
public List<SnapshotDataStoreVO> listBySnapshotIdAndDataStoreRoleAndStateIn(long snapshotId, DataStoreRole role, State... state) {
SearchCriteria<SnapshotDataStoreVO> sc = idEqRoleEqStateInSearch.create();
sc.setParameters(SNAPSHOT_ID, snapshotId);
sc.setParameters(STORE_ROLE, role);
sc.setParameters(STATE, (Object[])state);
return listBy(sc);
}
@Override
public SnapshotDataStoreVO findOneBySnapshotId(long snapshotId, long zoneId) {
try (TransactionLegacy transactionLegacy = TransactionLegacy.currentTxn()) {

View File

@ -687,22 +687,23 @@ CREATE TABLE IF NOT EXISTS `cloud`.`backup_details` (
UPDATE `cloud`.`backups` b
INNER JOIN `cloud`.`vm_instance` vm ON b.vm_id = vm.id
SET b.backed_volumes = (
SELECT CONCAT("[",
GROUP_CONCAT(
CONCAT(
"{\"uuid\":\"", v.uuid, "\",",
"\"type\":\"", v.volume_type, "\",",
"\"size\":", v.`size`, ",",
"\"path\":\"", IFNULL(v.path, 'null'), "\",",
"\"deviceId\":", IFNULL(v.device_id, 'null'), ",",
"\"diskOfferingId\":\"", doff.uuid, "\",",
"\"minIops\":", IFNULL(v.min_iops, 'null'), ",",
"\"maxIops\":", IFNULL(v.max_iops, 'null'),
"}"
)
SEPARATOR ","
SELECT COALESCE(
CAST(
JSON_ARRAYAGG(
JSON_OBJECT(
'uuid', v.uuid,
'type', v.volume_type,
'size', v.size,
'path', v.path,
'deviceId', v.device_id,
'diskOfferingId', doff.uuid,
'minIops', v.min_iops,
'maxIops', v.max_iops
)
) AS CHAR
),
"]")
'[]'
)
FROM `cloud`.`volumes` v
LEFT JOIN `cloud`.`disk_offering` doff ON v.disk_offering_id = doff.id
WHERE v.instance_id = vm.id
@ -711,22 +712,23 @@ SET b.backed_volumes = (
-- Add diskOfferingId, deviceId, minIops and maxIops to backup_volumes in vm_instance table
UPDATE `cloud`.`vm_instance` vm
SET vm.backup_volumes = (
SELECT CONCAT("[",
GROUP_CONCAT(
CONCAT(
"{\"uuid\":\"", v.uuid, "\",",
"\"type\":\"", v.volume_type, "\",",
"\"size\":", v.`size`, ",",
"\"path\":\"", IFNULL(v.path, 'null'), "\",",
"\"deviceId\":", IFNULL(v.device_id, 'null'), ",",
"\"diskOfferingId\":\"", doff.uuid, "\",",
"\"minIops\":", IFNULL(v.min_iops, 'null'), ",",
"\"maxIops\":", IFNULL(v.max_iops, 'null'),
"}"
)
SEPARATOR ","
SELECT COALESCE(
CAST(
JSON_ARRAYAGG(
JSON_OBJECT(
'uuid', v.uuid,
'type', v.volume_type,
'size', v.size,
'path', v.path,
'deviceId', v.device_id,
'diskOfferingId', doff.uuid,
'minIops', v.min_iops,
'maxIops', v.max_iops
)
) AS CHAR
),
"]")
'[]'
)
FROM `cloud`.`volumes` v
LEFT JOIN `cloud`.`disk_offering` doff ON v.disk_offering_id = doff.id
WHERE v.instance_id = vm.id

View File

@ -18,5 +18,3 @@
--;
-- Schema upgrade cleanup from 4.22.0.0 to 4.22.1.0
--;
DROP VIEW IF EXISTS `cloud`.`account_netstats_view`;

View File

@ -35,3 +35,6 @@ UPDATE `cloud`.`alert` SET type = 34 WHERE name = 'ALERT.VR.PRIVATE.IFACE.MTU';
UPDATE `cloud`.`configuration` SET description = 'True if the management server will restart the agent service via SSH into the KVM hosts after or during maintenance operations', is_dynamic = 1 WHERE name = 'kvm.ssh.to.agent';
UPDATE `cloud`.`vm_template` SET guest_os_id = 99 WHERE name = 'kvm-default-vm-import-dummy-template';
-- Update existing vm_template records with NULL type to "USER"
UPDATE `cloud`.`vm_template` SET `type` = 'USER' WHERE `type` IS NULL;

View File

@ -0,0 +1,31 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing,
-- software distributed under the License is distributed on an
-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
-- cloud.account_netstats_view source
DROP VIEW IF EXISTS `cloud`.`account_netstats_view`;
CREATE VIEW `cloud`.`account_netstats_view` AS
select
`user_statistics`.`account_id` AS `account_id`,
(sum(`user_statistics`.`net_bytes_received`) + sum(`user_statistics`.`current_bytes_received`)) AS `bytesReceived`,
(sum(`user_statistics`.`net_bytes_sent`) + sum(`user_statistics`.`current_bytes_sent`)) AS `bytesSent`
from
`user_statistics`
group by
`user_statistics`.`account_id`;

View File

@ -39,8 +39,8 @@ select
`data_center`.`id` AS `data_center_id`,
`data_center`.`uuid` AS `data_center_uuid`,
`data_center`.`name` AS `data_center_name`,
`account_netstats`.`bytesReceived` AS `bytesReceived`,
`account_netstats`.`bytesSent` AS `bytesSent`,
`account_netstats_view`.`bytesReceived` AS `bytesReceived`,
`account_netstats_view`.`bytesSent` AS `bytesSent`,
`vmlimit`.`max` AS `vmLimit`,
`vmcount`.`count` AS `vmTotal`,
`runningvm`.`vmcount` AS `runningVms`,
@ -89,15 +89,8 @@ from
`cloud`.`domain` ON account.domain_id = domain.id
left join
`cloud`.`data_center` ON account.default_zone_id = data_center.id
left join lateral (
select
coalesce(sum(`user_statistics`.`net_bytes_received` + `user_statistics`.`current_bytes_received`), 0) AS `bytesReceived`,
coalesce(sum(`user_statistics`.`net_bytes_sent` + `user_statistics`.`current_bytes_sent`), 0) AS `bytesSent`
from
`cloud`.`user_statistics`
where
`user_statistics`.`account_id` = `account`.`id`
) AS `account_netstats` ON TRUE
left join
`cloud`.`account_netstats_view` ON account.id = account_netstats_view.account_id
left join
`cloud`.`resource_limit` vmlimit ON account.id = vmlimit.account_id
and vmlimit.type = 'user_vm' and vmlimit.tag IS NULL

View File

@ -0,0 +1,48 @@
-- 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.
-- cloud_usage.quota_summary_view source
-- Create view for quota summary
DROP VIEW IF EXISTS `cloud_usage`.`quota_summary_view`;
CREATE VIEW `cloud_usage`.`quota_summary_view` AS
SELECT
cloud_usage.quota_account.account_id AS account_id,
cloud_usage.quota_account.quota_balance AS quota_balance,
cloud_usage.quota_account.quota_balance_date AS quota_balance_date,
cloud_usage.quota_account.quota_enforce AS quota_enforce,
cloud_usage.quota_account.quota_min_balance AS quota_min_balance,
cloud_usage.quota_account.quota_alert_date AS quota_alert_date,
cloud_usage.quota_account.quota_alert_type AS quota_alert_type,
cloud_usage.quota_account.last_statement_date AS last_statement_date,
cloud.account.uuid AS account_uuid,
cloud.account.account_name AS account_name,
cloud.account.state AS account_state,
cloud.account.removed AS account_removed,
cloud.domain.id AS domain_id,
cloud.domain.uuid AS domain_uuid,
cloud.domain.name AS domain_name,
cloud.domain.path AS domain_path,
cloud.domain.removed AS domain_removed,
cloud.projects.uuid AS project_uuid,
cloud.projects.name AS project_name,
cloud.projects.removed AS project_removed
FROM
cloud_usage.quota_account
INNER JOIN cloud.account ON (cloud.account.id = cloud_usage.quota_account.account_id)
INNER JOIN cloud.domain ON (cloud.domain.id = cloud.account.domain_id)
LEFT JOIN cloud.projects ON (cloud.account.type = 5 AND cloud.account.id = cloud.projects.project_account_id);

View File

@ -121,7 +121,7 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
private final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error, Snapshot.State.Hidden);
public SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
List<SnapshotDataStoreVO> snaps = snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
List<SnapshotDataStoreVO> snaps = snapshotStoreDao.listBySnapshotIdAndDataStoreRoleAndStateIn(snapshotId, DataStoreRole.Image, State.Ready, State.Hidden);
for (SnapshotDataStoreVO ref : snaps) {
if (zoneId == dataStoreMgr.getStoreZoneId(ref.getDataStoreId(), ref.getRole())) {
return ref;

View File

@ -40,6 +40,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.cloudstack.storage.datastore.api.Volume;
import com.cloud.agent.api.VMSnapshotTO;
import com.cloud.alert.AlertManager;
@ -200,11 +201,35 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot
if (volumeIds != null && !volumeIds.isEmpty()) {
List<VMSnapshotDetailsVO> vmSnapshotDetails = new ArrayList<VMSnapshotDetailsVO>();
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "SnapshotGroupId", snapshotGroupId, false));
Map<String, String> snapshotNameToSrcPathMap = new HashMap<>();
for (Map.Entry<String, String> entry : srcVolumeDestSnapshotMap.entrySet()) {
snapshotNameToSrcPathMap.put(entry.getValue(), entry.getKey());
}
for (int index = 0; index < volumeIds.size(); index++) {
String volumeSnapshotName = srcVolumeDestSnapshotMap.get(ScaleIOUtil.getVolumePath(volumeTOs.get(index).getPath()));
String pathWithScaleIOVolumeName = ScaleIOUtil.updatedPathWithVolumeName(volumeIds.get(index), volumeSnapshotName);
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(), "Vol_" + volumeTOs.get(index).getId() + "_Snapshot", pathWithScaleIOVolumeName, false));
for (String snapshotVolumeId : volumeIds) {
// Use getVolume() to fetch snapshot volume details and get its name
Volume snapshotVolume = client.getVolume(snapshotVolumeId);
if (snapshotVolume == null) {
throw new CloudRuntimeException("Cannot find snapshot volume with id: " + snapshotVolumeId);
}
String snapshotName = snapshotVolume.getName();
// Match back to source volume path
String srcVolumePath = snapshotNameToSrcPathMap.get(snapshotName);
if (srcVolumePath == null) {
throw new CloudRuntimeException("Cannot match snapshot " + snapshotName + " to a source volume");
}
// Find the matching VolumeObjectTO by path
VolumeObjectTO matchedVolume = volumeTOs.stream()
.filter(v -> ScaleIOUtil.getVolumePath(v.getPath()).equals(srcVolumePath))
.findFirst()
.orElseThrow(() -> new CloudRuntimeException("Cannot find source volume for path: " + srcVolumePath));
String pathWithScaleIOVolumeName = ScaleIOUtil.updatedPathWithVolumeName(snapshotVolumeId, snapshotName);
vmSnapshotDetails.add(new VMSnapshotDetailsVO(vmSnapshot.getId(),
"Vol_" + matchedVolume.getId() + "_Snapshot",
pathWithScaleIOVolumeName, false));
}
vmSnapshotDetailsDao.saveDetails(vmSnapshotDetails);

View File

@ -258,11 +258,6 @@ public class DefaultSnapshotStrategyTest {
@Test
public void testGetSnapshotImageStoreRefNull() {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
Mockito.when(snapshotDataStoreDao.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(2L);
Assert.assertNull(defaultSnapshotStrategySpy.getSnapshotImageStoreRef(1L, 1L));
}
@ -271,7 +266,7 @@ public class DefaultSnapshotStrategyTest {
SnapshotDataStoreVO ref1 = Mockito.mock(SnapshotDataStoreVO.class);
Mockito.when(ref1.getDataStoreId()).thenReturn(1L);
Mockito.when(ref1.getRole()).thenReturn(DataStoreRole.Image);
Mockito.when(snapshotDataStoreDao.listReadyBySnapshot(Mockito.anyLong(), Mockito.any(DataStoreRole.class))).thenReturn(List.of(ref1));
Mockito.when(snapshotDataStoreDao.listBySnapshotIdAndDataStoreRoleAndStateIn(Mockito.anyLong(), Mockito.any(DataStoreRole.class), Mockito.any(), Mockito.any())).thenReturn(List.of(ref1));
Mockito.when(dataStoreManager.getStoreZoneId(1L, DataStoreRole.Image)).thenReturn(1L);
Assert.assertNotNull(defaultSnapshotStrategySpy.getSnapshotImageStoreRef(1L, 1L));
}

View File

@ -0,0 +1,35 @@
// 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.quota;
import org.apache.commons.lang3.StringUtils;
public enum QuotaAccountStateFilter {
ALL, ACTIVE, REMOVED;
public static QuotaAccountStateFilter getValue(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
for (QuotaAccountStateFilter state : values()) {
if (state.name().equalsIgnoreCase(value)) {
return state;
}
}
return null;
}
}

View File

@ -0,0 +1,32 @@
// 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.quota.dao;
import java.util.List;
import org.apache.cloudstack.quota.QuotaAccountStateFilter;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.GenericDao;
public interface QuotaSummaryDao extends GenericDao<QuotaSummaryVO, Long> {
Pair<List<QuotaSummaryVO>, Integer> listQuotaSummariesForAccountAndOrDomain(Long accountId, String accountName, Long domainId, String domainPath,
QuotaAccountStateFilter accountStateFilter, Long startIndex, Long pageSize);
}

View File

@ -0,0 +1,80 @@
// 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.quota.dao;
import java.util.List;
import org.apache.cloudstack.quota.QuotaAccountStateFilter;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionLegacy;
public class QuotaSummaryDaoImpl extends GenericDaoBase<QuotaSummaryVO, Long> implements QuotaSummaryDao {
@Override
public Pair<List<QuotaSummaryVO>, Integer> listQuotaSummariesForAccountAndOrDomain(Long accountId, String accountName, Long domainId, String domainPath,
QuotaAccountStateFilter accountStateFilter, Long startIndex, Long pageSize) {
SearchCriteria<QuotaSummaryVO> searchCriteria = createListQuotaSummariesSearchCriteria(accountId, accountName, domainId, domainPath, accountStateFilter);
Filter filter = new Filter(QuotaSummaryVO.class, "accountName", true, startIndex, pageSize);
return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback<Pair<List<QuotaSummaryVO>, Integer>>) status -> searchAndCount(searchCriteria, filter));
}
protected SearchCriteria<QuotaSummaryVO> createListQuotaSummariesSearchCriteria(Long accountId, String accountName, Long domainId, String domainPath,
QuotaAccountStateFilter accountStateFilter) {
SearchCriteria<QuotaSummaryVO> searchCriteria = createListQuotaSummariesSearchBuilder(accountStateFilter).create();
searchCriteria.setParametersIfNotNull("accountId", accountId);
searchCriteria.setParametersIfNotNull("domainId", domainId);
if (accountName != null) {
searchCriteria.setParameters("accountName", "%" + accountName + "%");
}
if (domainPath != null) {
searchCriteria.setParameters("domainPath", domainPath + "%");
}
return searchCriteria;
}
protected SearchBuilder<QuotaSummaryVO> createListQuotaSummariesSearchBuilder(QuotaAccountStateFilter accountStateFilter) {
SearchBuilder<QuotaSummaryVO> searchBuilder = createSearchBuilder();
searchBuilder.and("accountId", searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ);
searchBuilder.and("accountName", searchBuilder.entity().getAccountName(), SearchCriteria.Op.LIKE);
searchBuilder.and("domainId", searchBuilder.entity().getDomainId(), SearchCriteria.Op.EQ);
searchBuilder.and("domainPath", searchBuilder.entity().getDomainPath(), SearchCriteria.Op.LIKE);
if (QuotaAccountStateFilter.REMOVED.equals(accountStateFilter)) {
searchBuilder.and("accountRemoved", searchBuilder.entity().getAccountRemoved(), SearchCriteria.Op.NNULL);
} else if (QuotaAccountStateFilter.ACTIVE.equals(accountStateFilter)) {
searchBuilder.and("accountRemoved", searchBuilder.entity().getAccountRemoved(), SearchCriteria.Op.NULL);
}
return searchBuilder;
}
}

View File

@ -0,0 +1,154 @@
// 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.quota.vo;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import com.cloud.user.Account;
@Entity
@Table(name = "quota_summary_view")
public class QuotaSummaryVO {
@Id
@Column(name = "account_id")
private Long accountId = null;
@Column(name = "quota_enforce")
private Integer quotaEnforce = 0;
@Column(name = "quota_balance")
private BigDecimal quotaBalance;
@Column(name = "quota_balance_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date quotaBalanceDate = null;
@Column(name = "quota_min_balance")
private BigDecimal quotaMinBalance;
@Column(name = "quota_alert_type")
private Integer quotaAlertType = null;
@Column(name = "quota_alert_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date quotaAlertDate = null;
@Column(name = "last_statement_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date lastStatementDate = null;
@Column(name = "account_uuid")
private String accountUuid;
@Column(name = "account_name")
private String accountName;
@Column(name = "account_state")
@Enumerated(EnumType.STRING)
private Account.State accountState;
@Column(name = "account_removed")
private Date accountRemoved;
@Column(name = "domain_id")
private Long domainId;
@Column(name = "domain_uuid")
private String domainUuid;
@Column(name = "domain_name")
private String domainName;
@Column(name = "domain_path")
private String domainPath;
@Column(name = "domain_removed")
private Date domainRemoved;
@Column(name = "project_uuid")
private String projectUuid;
@Column(name = "project_name")
private String projectName;
@Column(name = "project_removed")
private Date projectRemoved;
public Long getAccountId() {
return accountId;
}
public BigDecimal getQuotaBalance() {
return quotaBalance;
}
public String getAccountUuid() {
return accountUuid;
}
public String getAccountName() {
return accountName;
}
public Date getAccountRemoved() {
return accountRemoved;
}
public Account.State getAccountState() {
return accountState;
}
public Long getDomainId() {
return domainId;
}
public String getDomainUuid() {
return domainUuid;
}
public String getDomainPath() {
return domainPath;
}
public Date getDomainRemoved() {
return domainRemoved;
}
public String getProjectUuid() {
return projectUuid;
}
public String getProjectName() {
return projectName;
}
public Date getProjectRemoved() {
return projectRemoved;
}
}

View File

@ -19,7 +19,8 @@
<bean id="presetVariableHelper" class="org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableHelper" />
<bean id="QuotaTariffDao" class="org.apache.cloudstack.quota.dao.QuotaTariffDaoImpl" />
<bean id="QuotaAccountDao" class="org.apache.cloudstack.quota.dao.QuotaAccountDaoImpl" />
<bean id="QuotaSummaryDao" class="org.apache.cloudstack.quota.dao.QuotaSummaryDaoImpl" />
<bean id="QuotaAccountDao" class="org.apache.cloudstack.quota.dao.QuotaAccountDaoImpl" />
<bean id="QuotaBalanceDao" class="org.apache.cloudstack.quota.dao.QuotaBalanceDaoImpl" />
<bean id="QuotaCreditsDao" class="org.apache.cloudstack.quota.dao.QuotaCreditsDaoImpl" />
<bean id="QuotaEmailTemplatesDao"

View File

@ -357,7 +357,8 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
volumePools.add(dataStore != null ? (PrimaryDataStoreTO)dataStore.getTO() : null);
String volumePathPrefix = getVolumePathPrefix(storagePool);
volumePaths.add(String.format("%s/%s", volumePathPrefix, volume.getPath()));
String volumePathSuffix = getVolumePathSuffix(storagePool);
volumePaths.add(String.format("%s%s%s", volumePathPrefix, volume.getPath(), volumePathSuffix));
}
return new Pair<>(volumePools, volumePaths);
}
@ -367,14 +368,24 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
if (ScopeType.HOST.equals(storagePool.getScope()) ||
Storage.StoragePoolType.SharedMountPoint.equals(storagePool.getPoolType()) ||
Storage.StoragePoolType.RBD.equals(storagePool.getPoolType())) {
volumePathPrefix = storagePool.getPath();
volumePathPrefix = storagePool.getPath() + "/";
} else if (Storage.StoragePoolType.Linstor.equals(storagePool.getPoolType())) {
volumePathPrefix = "/dev/drbd/by-res/cs-";
} else {
// Should be Storage.StoragePoolType.NetworkFilesystem
volumePathPrefix = String.format("/mnt/%s", storagePool.getUuid());
volumePathPrefix = String.format("/mnt/%s/", storagePool.getUuid());
}
return volumePathPrefix;
}
private String getVolumePathSuffix(StoragePoolVO storagePool) {
if (Storage.StoragePoolType.Linstor.equals(storagePool.getPoolType())) {
return "/0";
} else {
return "";
}
}
@Override
public Pair<Boolean, String> restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, Pair<String, VirtualMachine.State> vmNameAndState) {
final VolumeVO volume = volumeDao.findByUuid(backupVolumeInfo.getUuid());
@ -419,7 +430,9 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
restoreCommand.setBackupRepoType(backupRepository.getType());
restoreCommand.setBackupRepoAddress(backupRepository.getAddress());
restoreCommand.setVmName(vmNameAndState.first());
restoreCommand.setRestoreVolumePaths(Collections.singletonList(String.format("%s/%s", getVolumePathPrefix(pool), volumeUUID)));
String restoreVolumePath = String.format("%s%s%s", getVolumePathPrefix(pool), volumeUUID, getVolumePathSuffix(pool));
restoreCommand.setRestoreVolumePaths(Collections.singletonList(restoreVolumePath));
restoreCommand.setRestoreVolumeSizes(Collections.singletonList(backedUpVolumeSize));
DataStore dataStore = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
restoreCommand.setRestoreVolumePools(Collections.singletonList(dataStore != null ? (PrimaryDataStoreTO)dataStore.getTO() : null));
restoreCommand.setDiskType(backupVolumeInfo.getType().name().toLowerCase(Locale.ROOT));
@ -478,6 +491,9 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
} else {
host = resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM, backup.getZoneId());
}
if (host == null) {
throw new CloudRuntimeException(String.format("Unable to find a running KVM host in zone %d to delete backup %s", backup.getZoneId(), backup.getUuid()));
}
DeleteBackupCommand command = new DeleteBackupCommand(backup.getExternalId(), backupRepository.getType(),
backupRepository.getAddress(), backupRepository.getMountOptions());
@ -564,7 +580,14 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co
@Override
public void syncBackupStorageStats(Long zoneId) {
final List<BackupRepository> repositories = backupRepositoryDao.listByZoneAndProvider(zoneId, getName());
if (CollectionUtils.isEmpty(repositories)) {
return;
}
final Host host = resourceManager.findOneRandomRunningHostByHypervisor(Hypervisor.HypervisorType.KVM, zoneId);
if (host == null) {
logger.warn("Unable to find a running KVM host in zone {} to sync backup storage stats", zoneId);
return;
}
for (final BackupRepository repository : repositories) {
GetBackupStorageStatsCommand command = new GetBackupStorageStatsCommand(repository.getType(), repository.getAddress(), repository.getMountOptions());
BackupStorageStatsAnswer answer;

View File

@ -16,61 +16,80 @@
//under the License.
package org.apache.cloudstack.api.command;
import com.cloud.user.Account;
import com.cloud.utils.Pair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.QuotaResponseBuilder;
import org.apache.cloudstack.api.response.QuotaSummaryResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.api.response.ProjectResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.quota.QuotaAccountStateFilter;
import org.apache.cloudstack.quota.QuotaService;
import org.apache.commons.lang3.ObjectUtils;
import java.util.List;
import javax.inject.Inject;
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all Accounts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
httpMethod = "GET")
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists Quota balance summary of Accounts and Projects.", since = "4.7.0",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, httpMethod = "GET")
public class QuotaSummaryCmd extends BaseListCmd {
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Optional, Account Id for which statement needs to be generated")
private String accountName;
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "Optional, If domain Id is given and the caller is domain admin then the statement is generated for domain.")
private Long domainId;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, required = false, description = "Optional, to list all Accounts irrespective of the quota activity")
private Boolean listAll;
@Inject
QuotaResponseBuilder quotaResponseBuilder;
@Inject
QuotaResponseBuilder _responseBuilder;
QuotaService quotaService;
public QuotaSummaryCmd() {
super();
}
@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the Account for which balance will be listed. Can not be specified with projectid.", since = "4.23.0")
private Long accountId;
@ACL
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Name of the Account for which balance will be listed.")
private String accountName;
@ACL
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = false, entityType = DomainResponse.class, description = "ID of the Domain for which balance will be listed. May be used individually or with accountname.")
private Long domainId;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "False (default) lists the Quota balance summary for calling Account. True lists balance summary for " +
"Accounts which the caller has access. If domain ID is informed, this parameter is considered as true.")
private Boolean listAll;
@Parameter(name = ApiConstants.ACCOUNT_STATE_TO_SHOW, type = CommandType.STRING, description = "Possible values are [ALL, ACTIVE, REMOVED]. ALL will list summaries for " +
"active and removed accounts; ACTIVE will list summaries only for active accounts; REMOVED will list summaries only for removed accounts. The default value is ACTIVE.",
since = "4.23.0")
private String accountStateToShow;
@ACL
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "ID of the Project for which balance will be listed. Can not be specified with accountId.", since = "4.23.0")
private Long projectId;
@Override
public void execute() {
Account caller = CallContext.current().getCallingAccount();
Pair<List<QuotaSummaryResponse>, Integer> responses;
if (caller.getType() == Account.Type.ADMIN) {
if (getAccountName() != null && getDomainId() != null)
responses = _responseBuilder.createQuotaSummaryResponse(getAccountName(), getDomainId());
else
responses = _responseBuilder.createQuotaSummaryResponse(isListAll(), getKeyword(), getStartIndex(), getPageSizeVal());
} else {
responses = _responseBuilder.createQuotaSummaryResponse(caller.getAccountName(), caller.getDomainId());
}
final ListResponse<QuotaSummaryResponse> response = new ListResponse<QuotaSummaryResponse>();
Pair<List<QuotaSummaryResponse>, Integer> responses = quotaResponseBuilder.createQuotaSummaryResponse(this);
ListResponse<QuotaSummaryResponse> response = new ListResponse<>();
response.setResponses(responses.first(), responses.second());
response.setResponseName(getCommandName());
setResponseObject(response);
}
public Long getAccountId() {
return accountId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public String getAccountName() {
return accountName;
}
@ -88,16 +107,31 @@ public class QuotaSummaryCmd extends BaseListCmd {
}
public Boolean isListAll() {
return listAll == null ? false: listAll;
// If a domain ID was specified, then allow listing all summaries of domain
return ObjectUtils.defaultIfNull(listAll, Boolean.FALSE) || domainId != null;
}
public void setListAll(Boolean listAll) {
this.listAll = listAll;
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
public Long getProjectId() {
return projectId;
}
public QuotaAccountStateFilter getAccountStateToShow() {
QuotaAccountStateFilter state = QuotaAccountStateFilter.getValue(accountStateToShow);
if (state != null) {
return state;
}
return QuotaAccountStateFilter.ACTIVE;
}
@Override
public long getEntityOwnerId() {
if (ObjectUtils.allNull(accountId, accountName, projectId)) {
return -1;
}
return _accountService.finalizeAccountId(accountId, accountName, domainId, projectId);
}
}

View File

@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
import org.apache.cloudstack.api.command.QuotaTariffListCmd;
import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd;
@ -52,11 +53,7 @@ public interface QuotaResponseBuilder {
QuotaBalanceResponse createQuotaBalanceResponse(List<QuotaBalanceVO> quotaUsage, Date startDate, Date endDate);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll, String keyword, Long startIndex, Long pageSize);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(String accountName, Long domainId);
Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd);
QuotaBalanceResponse createQuotaLastBalanceResponse(List<QuotaBalanceVO> quotaBalance, Date startDate);

View File

@ -42,11 +42,14 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.domain.Domain;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.projects.dao.ProjectDao;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.utils.DateUtil;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
@ -56,6 +59,7 @@ import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaPresetVariablesListCmd;
import org.apache.cloudstack.api.command.QuotaStatementCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaTariffCreateCmd;
import org.apache.cloudstack.api.command.QuotaTariffListCmd;
import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd;
@ -74,11 +78,13 @@ import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariable
import org.apache.cloudstack.quota.activationrule.presetvariables.Value;
import org.apache.cloudstack.quota.constant.QuotaConfig;
import org.apache.cloudstack.quota.constant.QuotaTypes;
import org.apache.cloudstack.quota.dao.QuotaAccountDao;
import org.apache.cloudstack.quota.dao.QuotaBalanceDao;
import org.apache.cloudstack.quota.dao.QuotaCreditsDao;
import org.apache.cloudstack.quota.dao.QuotaEmailConfigurationDao;
import org.apache.cloudstack.quota.dao.QuotaEmailTemplatesDao;
import org.apache.cloudstack.quota.dao.QuotaSummaryDao;
import org.apache.cloudstack.quota.dao.QuotaTariffDao;
import org.apache.cloudstack.quota.dao.QuotaUsageDao;
import org.apache.cloudstack.quota.vo.QuotaAccountVO;
@ -86,10 +92,13 @@ import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
import org.apache.cloudstack.quota.vo.QuotaCreditsVO;
import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO;
import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO;
import org.apache.cloudstack.quota.vo.QuotaSummaryVO;
import org.apache.cloudstack.quota.vo.QuotaTariffVO;
import org.apache.cloudstack.quota.vo.QuotaUsageVO;
import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Sets;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
@ -108,7 +117,6 @@ import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
@Component
public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@ -121,7 +129,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject
private QuotaCreditsDao quotaCreditsDao;
@Inject
private QuotaUsageDao _quotaUsageDao;
private QuotaUsageDao quotaUsageDao;
@Inject
private QuotaEmailTemplatesDao _quotaEmailTemplateDao;
@ -132,29 +140,29 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
@Inject
private AccountDao _accountDao;
@Inject
private ProjectDao projectDao;
@Inject
private QuotaAccountDao quotaAccountDao;
@Inject
private DomainDao _domainDao;
private DomainDao domainDao;
@Inject
private AccountManager _accountMgr;
@Inject
private QuotaStatement _statement;
private QuotaStatement quotaStatement;
@Inject
private QuotaManager _quotaManager;
@Inject
private QuotaEmailConfigurationDao quotaEmailConfigurationDao;
@Inject
private QuotaSummaryDao quotaSummaryDao;
@Inject
private JsInterpreterHelper jsInterpreterHelper;
@Inject
private ApiDiscoveryService apiDiscoveryService;
private final Class<?>[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class};
protected void checkActivationRulesAllowed(String activationRule) {
if (!_quotaService.isJsInterpretationEnabled() && StringUtils.isNotEmpty(activationRule)) {
throw new PermissionDeniedException("Quota Tariff Activation Rule cannot be set, as Javascript interpretation is disabled in the configuration.");
}
}
private Set<Account.Type> accountTypesThatCanListAllQuotaSummaries = Sets.newHashSet(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN);
@Override
public QuotaTariffResponse createQuotaTariffResponse(QuotaTariffVO tariff, boolean returnActivationRule) {
@ -180,75 +188,113 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
@Override
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(final String accountName, final Long domainId) {
List<QuotaSummaryResponse> result = new ArrayList<QuotaSummaryResponse>();
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
if (accountName != null && domainId != null) {
Account account = _accountDao.findActiveAccount(accountName, domainId);
QuotaSummaryResponse qr = getQuotaSummaryResponse(account);
result.add(qr);
if (!accountTypesThatCanListAllQuotaSummaries.contains(caller.getType()) || !cmd.isListAll()) {
return getQuotaSummaryResponse(cmd.getEntityOwnerId(), null, null, cmd);
}
return new Pair<>(result, result.size());
return getQuotaSummaryResponseWithListAll(cmd, caller);
}
@Override
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll) {
return createQuotaSummaryResponse(listAll, null, null, null);
}
@Override
public Pair<List<QuotaSummaryResponse>, Integer> createQuotaSummaryResponse(Boolean listAll, final String keyword, final Long startIndex, final Long pageSize) {
List<QuotaSummaryResponse> result = new ArrayList<QuotaSummaryResponse>();
Integer count = 0;
if (listAll) {
Filter filter = new Filter(AccountVO.class, "accountName", true, startIndex, pageSize);
Pair<List<AccountVO>, Integer> data = _accountDao.findAccountsLike(keyword, filter);
count = data.second();
for (final AccountVO account : data.first()) {
QuotaSummaryResponse qr = getQuotaSummaryResponse(account);
result.add(qr);
}
} else {
Pair<List<QuotaAccountVO>, Integer> data = quotaAccountDao.listAllQuotaAccount(startIndex, pageSize);
count = data.second();
for (final QuotaAccountVO quotaAccount : data.first()) {
AccountVO account = _accountDao.findById(quotaAccount.getId());
if (account == null) {
continue;
}
QuotaSummaryResponse qr = getQuotaSummaryResponse(account);
result.add(qr);
protected Pair<List<QuotaSummaryResponse>, Integer> getQuotaSummaryResponseWithListAll(QuotaSummaryCmd cmd, Account caller) {
Long domainId = cmd.getDomainId();
if (domainId != null) {
DomainVO domain = domainDao.findByIdIncludingRemoved(domainId);
if (domain == null) {
throw new InvalidParameterValueException(String.format("Domain [%s] does not exist.", domainId));
}
}
return new Pair<>(result, count);
String domainPath = getDomainPathByDomainIdForDomainAdmin(caller);
Long accountId = cmd.getEntityOwnerId();
if (accountId == -1) {
accountId = cmd.isListAll() ? null : caller.getAccountId();
}
return getQuotaSummaryResponse(accountId, domainId, domainPath, cmd);
}
protected QuotaSummaryResponse getQuotaSummaryResponse(final Account account) {
Calendar[] period = _statement.getCurrentStatementTime();
if (account != null) {
QuotaSummaryResponse qr = new QuotaSummaryResponse();
DomainVO domain = _domainDao.findById(account.getDomainId());
BigDecimal curBalance = _quotaBalanceDao.lastQuotaBalance(account.getAccountId(), account.getDomainId(), period[1].getTime());
BigDecimal quotaUsage = _quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, period[0].getTime(), period[1].getTime());
qr.setAccountId(account.getUuid());
qr.setAccountName(account.getAccountName());
qr.setDomainId(domain.getUuid());
qr.setDomainName(domain.getName());
qr.setBalance(curBalance);
qr.setQuotaUsage(quotaUsage);
qr.setState(account.getState());
qr.setStartDate(period[0].getTime());
qr.setEndDate(period[1].getTime());
qr.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
qr.setQuotaEnabled(QuotaConfig.QuotaAccountEnabled.valueIn(account.getId()));
qr.setObjectName("summary");
return qr;
} else {
return new QuotaSummaryResponse();
/**
* Retrieves the domain path of the caller's domain (if the caller is Domain Admin) for filtering in the quota summary query.
* @return null if the caller is an Admin or the domain path of the caller's domain if the caller is a Domain Admin.
* @throws InvalidParameterValueException if it cannot find the domain.
*/
protected String getDomainPathByDomainIdForDomainAdmin(Account caller) {
if (caller.getType() != Account.Type.DOMAIN_ADMIN) {
return null;
}
Long domainId = caller.getDomainId();
Domain domain = domainDao.findById(domainId);
_accountMgr.checkAccess(caller, domain);
if (domain == null) {
throw new InvalidParameterValueException(String.format("Domain ID [%s] is invalid.", domainId));
}
return domain.getPath();
}
/**
* Returns a <code>List</code> of <code>QuotaSummaryResponse</code> based on the provided parameters.
* @param accountId ID of the Account to return the summaries for. If <code>-1</code>, either because no specific
* Account was provided, or list all is disabled, then the summary is generated for the calling Account.
* @param domainId ID of the Domain to return the summaries for.
* @param domainPath path of the Domain to return the summaries for.
*/
protected Pair<List<QuotaSummaryResponse>, Integer> getQuotaSummaryResponse(Long accountId, Long domainId, String domainPath, QuotaSummaryCmd cmd) {
if (accountId != null && accountId == -1) {
accountId = CallContext.current().getCallingAccountId();
}
Pair<List<QuotaSummaryVO>, Integer> pairSummaries = quotaSummaryDao.listQuotaSummariesForAccountAndOrDomain(accountId, cmd.getKeyword(), domainId, domainPath,
cmd.getAccountStateToShow(), cmd.getStartIndex(), cmd.getPageSizeVal());
List<QuotaSummaryVO> summaries = pairSummaries.first();
if (CollectionUtils.isEmpty(summaries)) {
logger.info("There are no summaries to list for parameters [{}].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "accountName", "domainId", "listAll", "page", "pageSize"));
return new Pair<>(new ArrayList<>(), 0);
}
List<QuotaSummaryResponse> responses = summaries.stream().map(this::getQuotaSummaryResponse).collect(Collectors.toList());
return new Pair<>(responses, pairSummaries.second());
}
protected QuotaSummaryResponse getQuotaSummaryResponse(QuotaSummaryVO summary) {
QuotaSummaryResponse response = new QuotaSummaryResponse();
Account account = _accountDao.findByUuidIncludingRemoved(summary.getAccountUuid());
Calendar[] period = quotaStatement.getCurrentStatementTime();
Date startDate = period[0].getTime();
Date endDate = period[1].getTime();
BigDecimal quotaUsage = quotaUsageDao.findTotalQuotaUsage(account.getAccountId(), account.getDomainId(), null, startDate, endDate);
response.setQuotaUsage(quotaUsage);
response.setStartDate(startDate);
response.setEndDate(endDate);
response.setAccountId(summary.getAccountUuid());
response.setAccountName(summary.getAccountName());
response.setDomainId(summary.getDomainUuid());
response.setDomainPath(summary.getDomainPath());
response.setBalance(summary.getQuotaBalance());
response.setState(summary.getAccountState());
response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value());
response.setQuotaEnabled(QuotaConfig.QuotaAccountEnabled.valueIn(account.getId()));
response.setDomainRemoved(summary.getDomainRemoved() != null);
response.setAccountRemoved(summary.getAccountRemoved() != null);
response.setObjectName("summary");
if (summary.getProjectUuid() != null) {
response.setProjectId(summary.getProjectUuid());
response.setProjectName(summary.getProjectName());
response.setProjectRemoved(summary.getProjectRemoved() != null);
}
return response;
}
public boolean isUserAllowedToSeeActivationRules(User user) {
@ -450,6 +496,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
Integer position = cmd.getPosition();
warnQuotaTariffUpdateDeprecatedFields(cmd);
jsInterpreterHelper.ensureInterpreterEnabledIfParameterProvided(ApiConstants.ACTIVATION_RULE, StringUtils.isNotBlank(activationRule));
QuotaTariffVO currentQuotaTariff = _quotaTariffDao.findByName(name);
@ -457,8 +504,6 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
throw new InvalidParameterValueException(String.format("There is no quota tariffs with name [%s].", name));
}
checkActivationRulesAllowed(activationRule);
Date currentQuotaTariffStartDate = currentQuotaTariff.getEffectiveOn();
currentQuotaTariff.setRemoved(now);
@ -707,14 +752,14 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
String activationRule = cmd.getActivationRule();
Integer position = ObjectUtils.defaultIfNull(cmd.getPosition(), 1);
jsInterpreterHelper.ensureInterpreterEnabledIfParameterProvided(ApiConstants.ACTIVATION_RULE, StringUtils.isNotBlank(activationRule));
QuotaTariffVO currentQuotaTariff = _quotaTariffDao.findByName(name);
if (currentQuotaTariff != null) {
throw new InvalidParameterValueException(String.format("A quota tariff with name [%s] already exist.", name));
}
checkActivationRulesAllowed(activationRule);
if (startDate.compareTo(now) < 0) {
throw new InvalidParameterValueException(String.format("The value passed as Quota tariff's start date is in the past: [%s]. " +
"Please, inform a date in the future or do not pass the parameter to use the current date and time.", startDate));

View File

@ -17,7 +17,6 @@
package org.apache.cloudstack.api.response;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import com.google.gson.annotations.SerializedName;
@ -30,40 +29,48 @@ import com.cloud.user.Account.State;
public class QuotaSummaryResponse extends BaseResponse {
@SerializedName("accountid")
@Param(description = "Account ID")
@Param(description = "Account's ID")
private String accountId;
@SerializedName("account")
@Param(description = "Account name")
@Param(description = "Account's name")
private String accountName;
@SerializedName("domainid")
@Param(description = "Domain ID")
@Param(description = "Domain's ID")
private String domainId;
@SerializedName("domain")
@Param(description = "Domain name")
private String domainName;
@Param(description = "Domain's path")
private String domainPath;
@SerializedName("balance")
@Param(description = "Account balance")
@Param(description = "Account's balance")
private BigDecimal balance;
@SerializedName("state")
@Param(description = "Account state")
@Param(description = "Account's state")
private State state;
@SerializedName("domainremoved")
@Param(description = "If the domain is removed or not", since = "4.23.0")
private boolean domainRemoved;
@SerializedName("accountremoved")
@Param(description = "If the account is removed or not", since = "4.23.0")
private boolean accountRemoved;
@SerializedName("quota")
@Param(description = "Quota usage of this period")
@Param(description = "Quota consumed between the startdate and enddate")
private BigDecimal quotaUsage;
@SerializedName("startdate")
@Param(description = "Start date")
private Date startDate = null;
@Param(description = "Start date of the quota consumption")
private Date startDate;
@SerializedName("enddate")
@Param(description = "End date")
private Date endDate = null;
@Param(description = "End date of the quota consumption")
private Date endDate;
@SerializedName("currency")
@Param(description = "Currency")
@ -73,9 +80,17 @@ public class QuotaSummaryResponse extends BaseResponse {
@Param(description = "If the account has the quota config enabled")
private boolean quotaEnabled;
public QuotaSummaryResponse() {
super();
}
@SerializedName("projectname")
@Param(description = "Name of the project", since = "4.23.0")
private String projectName;
@SerializedName("projectid")
@Param(description = "Project's id", since = "4.23.0")
private String projectId;
@SerializedName("projectremoved")
@Param(description = "Whether the project is removed or not", since = "4.23.0")
private Boolean projectRemoved;
public String getAccountId() {
return accountId;
@ -101,28 +116,16 @@ public class QuotaSummaryResponse extends BaseResponse {
this.domainId = domainId;
}
public String getDomainName() {
return domainName;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public BigDecimal getQuotaUsage() {
return quotaUsage;
}
public State getState() {
return state;
public void setDomainPath(String domainPath) {
this.domainPath = domainPath;
}
public void setState(State state) {
this.state = state;
}
public void setQuotaUsage(BigDecimal startQuota) {
this.quotaUsage = startQuota.setScale(2, RoundingMode.HALF_EVEN);
public void setQuotaUsage(BigDecimal quotaUsage) {
this.quotaUsage = quotaUsage;
}
public BigDecimal getBalance() {
@ -130,38 +133,42 @@ public class QuotaSummaryResponse extends BaseResponse {
}
public void setBalance(BigDecimal balance) {
this.balance = balance.setScale(2, RoundingMode.HALF_EVEN);
}
public Date getStartDate() {
return startDate == null ? null : new Date(startDate.getTime());
this.balance = balance;
}
public void setStartDate(Date startDate) {
this.startDate = startDate == null ? null : new Date(startDate.getTime());
}
public Date getEndDate() {
return endDate == null ? null : new Date(endDate.getTime());
this.startDate = startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate == null ? null : new Date(endDate.getTime());
}
public String getCurrency() {
return currency;
this.endDate = endDate;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public boolean getQuotaEnabled() {
return quotaEnabled;
}
public void setQuotaEnabled(boolean quotaEnabled) {
this.quotaEnabled = quotaEnabled;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public void setProjectRemoved(Boolean projectRemoved) {
this.projectRemoved = projectRemoved;
}
public void setDomainRemoved(boolean domainRemoved) {
this.domainRemoved = domainRemoved;
}
public void setAccountRemoved(boolean accountRemoved) {
this.accountRemoved = accountRemoved;
}
}

View File

@ -40,6 +40,4 @@ public interface QuotaService extends PluggableService {
boolean saveQuotaAccount(AccountVO account, BigDecimal aggrUsage, Date endDate);
boolean isJsInterpretationEnabled();
}

View File

@ -26,6 +26,8 @@ import java.util.TimeZone;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.projects.ProjectManager;
import com.cloud.user.AccountService;
import org.apache.cloudstack.api.command.QuotaBalanceCmd;
import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsCmd;
@ -62,7 +64,6 @@ import com.cloud.configuration.Config;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.server.ManagementService;
import com.cloud.user.Account;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
@ -75,6 +76,8 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
@Inject
private AccountDao _accountDao;
@Inject
private AccountService accountService;
@Inject
private QuotaAccountDao _quotaAcc;
@Inject
private QuotaUsageDao _quotaUsageDao;
@ -86,11 +89,11 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
private QuotaBalanceDao _quotaBalanceDao;
@Inject
private QuotaResponseBuilder _respBldr;
@Inject
private ProjectManager projectMgr;
private TimeZone _usageTimezone;
private boolean jsInterpretationEnabled = false;
public QuotaServiceImpl() {
super();
}
@ -102,8 +105,6 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
String timeZoneStr = ObjectUtils.defaultIfNull(_configDao.getValue(Config.UsageAggregationTimezone.toString()), "GMT");
_usageTimezone = TimeZone.getTimeZone(timeZoneStr);
jsInterpretationEnabled = ManagementService.JsInterpretationEnabled.value();
return true;
}
@ -292,9 +293,4 @@ public class QuotaServiceImpl extends ManagerBase implements QuotaService, Confi
_quotaAcc.updateQuotaAccount(accountId, acc);
}
}
@Override
public boolean isJsInterpretationEnabled() {
return jsInterpretationEnabled;
}
}

View File

@ -16,13 +16,11 @@
// under the License.
package org.apache.cloudstack.api.response;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -31,6 +29,7 @@ import java.util.Set;
import java.util.HashSet;
import java.util.function.Consumer;
import com.cloud.domain.Domain;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.PermissionDeniedException;
@ -43,10 +42,10 @@ import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd;
import org.apache.cloudstack.api.command.QuotaCreditsListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd;
import org.apache.cloudstack.api.command.QuotaEmailTemplateUpdateCmd;
import org.apache.cloudstack.api.command.QuotaSummaryCmd;
import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.discovery.ApiDiscoveryService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.jsinterpreter.JsInterpreterHelper;
import org.apache.cloudstack.quota.QuotaService;
import org.apache.cloudstack.quota.QuotaStatement;
@ -79,7 +78,10 @@ import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
@ -89,8 +91,7 @@ import com.cloud.user.dao.UserDao;
import com.cloud.user.User;
import junit.framework.TestCase;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class QuotaResponseBuilderImplTest extends TestCase {
@ -153,7 +154,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Account accountMock;
@Mock
DomainVO domainVOMock;
DomainVO domainVoMock;
@Mock
QuotaConfigureEmailCmd quotaConfigureEmailCmdMock;
@ -161,6 +162,9 @@ public class QuotaResponseBuilderImplTest extends TestCase {
@Mock
QuotaAccountVO quotaAccountVOMock;
@Mock
CallContext callContextMock;
@Mock
QuotaEmailTemplatesVO quotaEmailTemplatesVoMock;
@ -184,17 +188,8 @@ public class QuotaResponseBuilderImplTest extends TestCase {
CallContext.register(callerUserMock, callerAccountMock);
}
private void overrideDefaultQuotaEnabledConfigValue(final Object value) throws IllegalAccessException, NoSuchFieldException {
Field f = ConfigKey.class.getDeclaredField("_defaultValue");
f.setAccessible(true);
f.set(QuotaConfig.QuotaAccountEnabled, value);
}
private Calendar[] createPeriodForQuotaSummary() {
final Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR, 0);
return new Calendar[] {calendar, calendar};
}
@Mock
Pair<List<QuotaSummaryResponse>, Integer> quotaSummaryResponseMock1, quotaSummaryResponseMock2;
@Mock
QuotaValidateActivationRuleCmd quotaValidateActivationRuleCmdMock = Mockito.mock(QuotaValidateActivationRuleCmd.class);
@ -466,36 +461,6 @@ public class QuotaResponseBuilderImplTest extends TestCase {
Mockito.verify(quotaTariffVoMock).setRemoved(Mockito.any(Date.class));
}
@Test
public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsDisabledShouldReturnFalse() throws NoSuchFieldException, IllegalAccessException {
Calendar[] period = createPeriodForQuotaSummary();
overrideDefaultQuotaEnabledConfigValue("false");
Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime();
Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class));
Mockito.doReturn(BigDecimal.ZERO).when(quotaUsageDaoMock).findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), Mockito.any(Date.class), Mockito.any(Date.class));
QuotaSummaryResponse quotaSummaryResponse = quotaResponseBuilderSpy.getQuotaSummaryResponse(accountMock);
assertFalse(quotaSummaryResponse.getQuotaEnabled());
}
@Test
public void getQuotaSummaryResponseTestAccountIsNotNullQuotaIsEnabledShouldReturnTrue() throws NoSuchFieldException, IllegalAccessException {
Calendar[] period = createPeriodForQuotaSummary();
overrideDefaultQuotaEnabledConfigValue("true");
Mockito.doReturn(period).when(quotaStatementMock).getCurrentStatementTime();
Mockito.doReturn(domainVOMock).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.doReturn(BigDecimal.ZERO).when(quotaBalanceDaoMock).lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class));
Mockito.doReturn(BigDecimal.ZERO).when(quotaUsageDaoMock).findTotalQuotaUsage(Mockito.anyLong(), Mockito.anyLong(), Mockito.isNull(), Mockito.any(Date.class), Mockito.any(Date.class));
QuotaSummaryResponse quotaSummaryResponse = quotaResponseBuilderSpy.getQuotaSummaryResponse(accountMock);
assertTrue(quotaSummaryResponse.getQuotaEnabled());
}
@Test
public void filterSupportedTypesTestReturnWhenQuotaTypeDoesNotMatch() throws NoSuchFieldException {
List<Pair<String, String>> variables = new ArrayList<>();
@ -576,6 +541,63 @@ public class QuotaResponseBuilderImplTest extends TestCase {
quotaResponseBuilderSpy.validateQuotaConfigureEmailCmdParameters(quotaConfigureEmailCmdMock);
}
@Test
public void createQuotaSummaryResponseTestNotListAllAndAllAccountTypesReturnsSingleRecord() {
QuotaSummaryCmd cmd = new QuotaSummaryCmd();
try(MockedStatic<CallContext> callContextMocked = Mockito.mockStatic(CallContext.class)) {
callContextMocked.when(CallContext::current).thenReturn(callContextMock);
Mockito.doReturn(accountMock).when(callContextMock).getCallingAccount();
Mockito.doReturn(quotaSummaryResponseMock1).when(quotaResponseBuilderSpy).getQuotaSummaryResponse(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
for (Account.Type type : Account.Type.values()) {
Mockito.doReturn(type).when(accountMock).getType();
Pair<List<QuotaSummaryResponse>, Integer> result = quotaResponseBuilderSpy.createQuotaSummaryResponse(cmd);
Assert.assertEquals(quotaSummaryResponseMock1, result);
}
Mockito.verify(quotaResponseBuilderSpy, Mockito.times(Account.Type.values().length)).getQuotaSummaryResponse(Mockito.any(), Mockito.any(), Mockito.any(),
Mockito.any());
};
}
@Test
public void getDomainPathByDomainIdForDomainAdminTestAccountNotDomainAdminReturnsNull() {
for (Account.Type type : Account.Type.values()) {
if (Account.Type.DOMAIN_ADMIN.equals(type)) {
continue;
}
Mockito.doReturn(type).when(accountMock).getType();
Assert.assertNull(quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock));
}
}
@Test(expected = InvalidParameterValueException.class)
public void getDomainPathByDomainIdForDomainAdminTestDomainFromCallerIsNullThrowsInvalidParameterValueException() {
Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(accountMock).getType();
Mockito.doReturn(null).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.lenient().doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock);
}
@Test
public void getDomainPathByDomainIdForDomainAdminTestDomainFromCallerIsNotNullReturnsPath() {
String expected = "/test/";
Mockito.doReturn(Account.Type.DOMAIN_ADMIN).when(accountMock).getType();
Mockito.doReturn(domainVoMock).when(domainDaoMock).findById(Mockito.anyLong());
Mockito.doNothing().when(accountManagerMock).checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class));
Mockito.doReturn(expected).when(domainVoMock).getPath();
String result = quotaResponseBuilderSpy.getDomainPathByDomainIdForDomainAdmin(accountMock);
Assert.assertEquals(expected, result);
}
@Test
public void getQuotaEmailConfigurationVoTestTemplateNameIsNull() {
Mockito.doReturn(null).when(quotaConfigureEmailCmdMock).getTemplateName();

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.quota;
import com.cloud.configuration.Config;
import com.cloud.domain.dao.DomainDao;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.db.TransactionLegacy;
import junit.framework.TestCase;
@ -33,8 +34,10 @@ import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import javax.naming.ConfigurationException;
@ -48,7 +51,7 @@ import java.util.List;
public class QuotaServiceImplTest extends TestCase {
@Mock
AccountDao accountDao;
AccountDao accountDaoMock;
@Mock
QuotaAccountDao quotaAcc;
@Mock
@ -61,8 +64,13 @@ public class QuotaServiceImplTest extends TestCase {
QuotaBalanceDao quotaBalanceDao;
@Mock
QuotaResponseBuilder respBldr;
@Mock
private AccountVO accountVoMock;
@Spy
@InjectMocks
QuotaServiceImpl quotaServiceImplSpy;
QuotaServiceImpl quotaService = new QuotaServiceImpl();
@Before
public void setup() throws IllegalAccessException, NoSuchFieldException, ConfigurationException {
@ -71,34 +79,34 @@ public class QuotaServiceImplTest extends TestCase {
Field accountDaoField = QuotaServiceImpl.class.getDeclaredField("_accountDao");
accountDaoField.setAccessible(true);
accountDaoField.set(quotaService, accountDao);
accountDaoField.set(quotaServiceImplSpy, accountDaoMock);
Field quotaAccountDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaAcc");
quotaAccountDaoField.setAccessible(true);
quotaAccountDaoField.set(quotaService, quotaAcc);
quotaAccountDaoField.set(quotaServiceImplSpy, quotaAcc);
Field quotaUsageDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaUsageDao");
quotaUsageDaoField.setAccessible(true);
quotaUsageDaoField.set(quotaService, quotaUsageDao);
quotaUsageDaoField.set(quotaServiceImplSpy, quotaUsageDao);
Field domainDaoField = QuotaServiceImpl.class.getDeclaredField("_domainDao");
domainDaoField.setAccessible(true);
domainDaoField.set(quotaService, domainDao);
domainDaoField.set(quotaServiceImplSpy, domainDao);
Field configDaoField = QuotaServiceImpl.class.getDeclaredField("_configDao");
configDaoField.setAccessible(true);
configDaoField.set(quotaService, configDao);
configDaoField.set(quotaServiceImplSpy, configDao);
Field balanceDaoField = QuotaServiceImpl.class.getDeclaredField("_quotaBalanceDao");
balanceDaoField.setAccessible(true);
balanceDaoField.set(quotaService, quotaBalanceDao);
balanceDaoField.set(quotaServiceImplSpy, quotaBalanceDao);
Field QuotaResponseBuilderField = QuotaServiceImpl.class.getDeclaredField("_respBldr");
QuotaResponseBuilderField.setAccessible(true);
QuotaResponseBuilderField.set(quotaService, respBldr);
QuotaResponseBuilderField.set(quotaServiceImplSpy, respBldr);
Mockito.when(configDao.getValue(Mockito.eq(Config.UsageAggregationTimezone.toString()))).thenReturn("IST");
quotaService.configure("randomName", null);
quotaServiceImplSpy.configure("randomName", null);
}
@Test
@ -120,9 +128,9 @@ public class QuotaServiceImplTest extends TestCase {
Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records);
// with enddate
assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb));
assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb));
// without enddate
assertTrue(quotaService.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb));
assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb));
}
@Test
@ -133,7 +141,7 @@ public class QuotaServiceImplTest extends TestCase {
final Date startDate = new DateTime().minusDays(2).toDate();
final Date endDate = new Date();
quotaService.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate);
quotaServiceImplSpy.getQuotaUsage(accountId, accountName, domainId, QuotaTypes.IP_ADDRESS, startDate, endDate);
Mockito.verify(quotaUsageDao, Mockito.times(1)).findQuotaUsage(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.eq(QuotaTypes.IP_ADDRESS), Mockito.any(Date.class), Mockito.any(Date.class));
}
@ -142,13 +150,13 @@ public class QuotaServiceImplTest extends TestCase {
// existing account
QuotaAccountVO quotaAccountVO = new QuotaAccountVO();
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO);
quotaService.setLockAccount(2L, true);
quotaServiceImplSpy.setLockAccount(2L, true);
Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class));
// new account
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null);
quotaService.setLockAccount(2L, true);
quotaServiceImplSpy.setLockAccount(2L, true);
Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
}
@ -160,13 +168,14 @@ public class QuotaServiceImplTest extends TestCase {
// existing account setting
QuotaAccountVO quotaAccountVO = new QuotaAccountVO();
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(quotaAccountVO);
quotaService.setMinBalance(accountId, balance);
quotaServiceImplSpy.setMinBalance(accountId, balance);
Mockito.verify(quotaAcc, Mockito.times(0)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
Mockito.verify(quotaAcc, Mockito.times(1)).updateQuotaAccount(Mockito.anyLong(), Mockito.any(QuotaAccountVO.class));
// no account with limit set
Mockito.when(quotaAcc.findByIdQuotaAccount(Mockito.anyLong())).thenReturn(null);
quotaService.setMinBalance(accountId, balance);
quotaServiceImplSpy.setMinBalance(accountId, balance);
Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class));
}
}

View File

@ -79,28 +79,47 @@ public class LibvirtGpuDef {
gpuBuilder.append(" <driver name='vfio'/>\n");
gpuBuilder.append(" <source>\n");
// Parse the bus address (e.g., 00:02.0) into domain, bus, slot, function
String domain = "0x0000";
String bus = "0x00";
String slot = "0x00";
String function = "0x0";
// Parse the bus address into domain, bus, slot, function. Two input formats are accepted:
// - "dddd:bb:ss.f" full PCI address with domain (e.g. 0000:00:02.0)
// - "bb:ss.f" legacy short BDF; domain defaults to 0000
// Each segment is parsed as a hex integer and formatted with fixed widths
// (domain: 4 hex digits, bus/slot: 2 hex digits, function: 1 hex digit) to
// produce canonical libvirt XML values regardless of input casing or padding.
int domainVal = 0, busVal = 0, slotVal = 0, funcVal = 0;
if (busAddress != null && !busAddress.isEmpty()) {
String[] parts = busAddress.split(":");
if (parts.length > 1) {
bus = "0x" + parts[0];
String[] slotFunctionParts = parts[1].split("\\.");
if (slotFunctionParts.length > 0) {
slot = "0x" + slotFunctionParts[0];
if (slotFunctionParts.length > 1) {
function = "0x" + slotFunctionParts[1].trim();
}
try {
String slotFunction;
if (parts.length == 3) {
domainVal = Integer.parseInt(parts[0], 16);
busVal = Integer.parseInt(parts[1], 16);
slotFunction = parts[2];
} else if (parts.length == 2) {
busVal = Integer.parseInt(parts[0], 16);
slotFunction = parts[1];
} else {
throw new IllegalArgumentException("Invalid PCI bus address format: '" + busAddress + "'");
}
String[] sf = slotFunction.split("\\.");
if (sf.length == 2) {
slotVal = Integer.parseInt(sf[0], 16);
funcVal = Integer.parseInt(sf[1].trim(), 16);
} else {
throw new IllegalArgumentException("Invalid PCI bus address format: '" + busAddress + "'");
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid PCI bus address format: '" + busAddress + "'", e);
}
}
String domain = String.format("0x%04x", domainVal);
String bus = String.format("0x%02x", busVal);
String slot = String.format("0x%02x", slotVal);
String function = String.format("0x%x", funcVal);
gpuBuilder.append(" <address domain='").append(domain).append("' bus='").append(bus).append("' slot='")
.append(slot).append("' function='").append(function.trim()).append("'/>\n");
.append(slot).append("' function='").append(function).append("'/>\n");
gpuBuilder.append(" </source>\n");
gpuBuilder.append("</hostdev>\n");
}

View File

@ -47,7 +47,10 @@ import java.util.Map;
@ResourceWrapper(handles = CheckVolumeCommand.class)
public final class LibvirtCheckVolumeCommandWrapper extends CommandWrapper<CheckVolumeCommand, Answer, LibvirtComputingResource> {
private static final List<Storage.StoragePoolType> STORAGE_POOL_TYPES_SUPPORTED = Arrays.asList(Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.NetworkFilesystem);
private static final List<Storage.StoragePoolType> STORAGE_POOL_TYPES_SUPPORTED = Arrays.asList(
Storage.StoragePoolType.Filesystem,
Storage.StoragePoolType.NetworkFilesystem,
Storage.StoragePoolType.SharedMountPoint);
@Override
public Answer execute(final CheckVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) {

View File

@ -52,7 +52,7 @@ import java.util.stream.Collectors;
public final class LibvirtGetVolumesOnStorageCommandWrapper extends CommandWrapper<GetVolumesOnStorageCommand, Answer, LibvirtComputingResource> {
static final List<StoragePoolType> STORAGE_POOL_TYPES_SUPPORTED_BY_QEMU_IMG = Arrays.asList(StoragePoolType.NetworkFilesystem,
StoragePoolType.Filesystem, StoragePoolType.RBD);
StoragePoolType.Filesystem, StoragePoolType.RBD, StoragePoolType.SharedMountPoint);
@Override
public Answer execute(final GetVolumesOnStorageCommand command, final LibvirtComputingResource libvirtComputingResource) {

View File

@ -41,9 +41,9 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.libvirt.LibvirtException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale;
@ -56,10 +56,25 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
private static final String UMOUNT_COMMAND = "sudo umount %s";
private static final String FILE_PATH_PLACEHOLDER = "%s/%s";
private static final String ATTACH_QCOW2_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --subdriver qcow2 --cache none";
private static final String ATTACH_RAW_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --cache none";
private static final String ATTACH_RBD_DISK_XML_COMMAND = " virsh attach-device %s /dev/stdin <<EOF%sEOF";
private static final String CURRRENT_DEVICE = "virsh domblklist --domain %s | tail -n 3 | head -n 1 | awk '{print $1}'";
private static final String RSYNC_COMMAND = "rsync -az %s %s";
private String getVolumeUuidFromPath(String volumePath, PrimaryDataStoreTO volumePool) {
if (Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
Path path = Paths.get(volumePath);
String rscName = path.getParent().getFileName().toString();
if (rscName.startsWith("cs-")) {
rscName = rscName.substring(3);
}
return rscName;
} else {
int lastIndex = volumePath.lastIndexOf("/");
return volumePath.substring(lastIndex + 1);
}
}
@Override
public Answer execute(RestoreBackupCommand command, LibvirtComputingResource serverResource) {
String vmName = command.getVmName();
@ -73,7 +88,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
List<PrimaryDataStoreTO> restoreVolumePools = command.getRestoreVolumePools();
List<String> restoreVolumePaths = command.getRestoreVolumePaths();
Integer mountTimeout = command.getMountTimeout() * 1000;
int timeout = command.getWait();
int timeout = command.getWait() > 0 ? command.getWait() * 1000 : serverResource.getCmdsTimeout();
KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
List<String> backupFiles = command.getBackupFiles();
@ -84,9 +99,9 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
PrimaryDataStoreTO volumePool = restoreVolumePools.get(0);
String volumePath = restoreVolumePaths.get(0);
String backupFile = backupFiles.get(0);
int lastIndex = volumePath.lastIndexOf("/");
newVolumeId = volumePath.substring(lastIndex + 1);
restoreVolume(storagePoolMgr, backupPath, volumePool, volumePath, diskType, backupFile,
newVolumeId = getVolumeUuidFromPath(volumePath, volumePool);
Long size = command.getRestoreVolumeSizes().get(0);
restoreVolume(storagePoolMgr, backupPath, volumePool, volumePath, diskType, backupFile, size,
new Pair<>(vmName, command.getVmState()), mountDirectory, timeout);
} else if (Boolean.TRUE.equals(vmExists)) {
restoreVolumesOfExistingVM(storagePoolMgr, restoreVolumePools, restoreVolumePaths, backedVolumeUUIDs, backupPath, backupFiles, mountDirectory, timeout);
@ -143,7 +158,7 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
String volumePath = volumePaths.get(i);
String backupFile = backupFiles.get(i);
String bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType);
String volumeUuid = volumePath.substring(volumePath.lastIndexOf(File.separator) + 1);
String volumeUuid = getVolumeUuidFromPath(volumePath, volumePool);
diskType = "datadisk";
verifyBackupFile(bkpPath, volumeUuid);
if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPath, timeout)) {
@ -157,14 +172,14 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
}
private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPath, PrimaryDataStoreTO volumePool, String volumePath, String diskType, String backupFile,
Pair<String, VirtualMachine.State> vmNameAndState, String mountDirectory, int timeout) {
Long size, Pair<String, VirtualMachine.State> vmNameAndState, String mountDirectory, int timeout) {
String bkpPath;
String volumeUuid;
try {
bkpPath = getBackupPath(mountDirectory, backupPath, backupFile, diskType);
volumeUuid = volumePath.substring(volumePath.lastIndexOf(File.separator) + 1);
volumeUuid = getVolumeUuidFromPath(volumePath, volumePool);
verifyBackupFile(bkpPath, volumeUuid);
if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPath, timeout, true)) {
if (!replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, bkpPath, timeout, true, size)) {
throw new CloudRuntimeException(String.format("Unable to restore contents from the backup volume [%s].", volumeUuid));
}
@ -247,42 +262,66 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
}
private boolean replaceVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout) {
return replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, false);
return replaceVolumeWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, false, null);
}
private boolean replaceVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout, boolean createTargetVolume) {
if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) {
int exitValue = Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, volumePath));
return exitValue == 0;
private boolean replaceVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout, boolean createTargetVolume, Long size) {
if (List.of(Storage.StoragePoolType.RBD, Storage.StoragePoolType.Linstor).contains(volumePool.getPoolType())) {
return replaceBlockDeviceWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, createTargetVolume, size);
}
return replaceRbdVolumeWithBackup(storagePoolMgr, volumePool, volumePath, backupPath, timeout, createTargetVolume);
int exitValue = Script.runSimpleBashScriptForExitValue(String.format(RSYNC_COMMAND, backupPath, volumePath), timeout, false);
return exitValue == 0;
}
private boolean replaceRbdVolumeWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout, boolean createTargetVolume) {
private boolean replaceBlockDeviceWithBackup(KVMStoragePoolManager storagePoolMgr, PrimaryDataStoreTO volumePool, String volumePath, String backupPath, int timeout, boolean createTargetVolume, Long size) {
KVMStoragePool volumeStoragePool = storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid());
QemuImg qemu;
try {
qemu = new QemuImg(timeout * 1000, true, false);
if (!createTargetVolume) {
KVMPhysicalDisk rdbDisk = volumeStoragePool.getPhysicalDisk(volumePath);
logger.debug("Restoring RBD volume: {}", rdbDisk.toString());
qemu = new QemuImg(timeout, true, false);
String volumeUuid = getVolumeUuidFromPath(volumePath, volumePool);
KVMPhysicalDisk disk = null;
if (createTargetVolume) {
if (Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
if (size == null) {
throw new CloudRuntimeException("Restore volume size is required for Linstor pool when creating target volume");
}
disk = volumeStoragePool.createPhysicalDisk(volumeUuid, QemuImg.PhysicalDiskFormat.RAW, Storage.ProvisioningType.THIN, size, null);
}
} else {
if (Storage.StoragePoolType.Linstor.equals(volumePool.getPoolType())) {
storagePoolMgr.connectPhysicalDisk(volumePool.getPoolType(), volumePool.getUuid(), volumeUuid, null);
} else {
disk = volumeStoragePool.getPhysicalDisk(volumePath);
}
qemu.setSkipTargetVolumeCreation(true);
}
if (disk != null) {
logger.debug("Restoring volume: {}", disk.toString());
}
} catch (LibvirtException ex) {
throw new CloudRuntimeException("Failed to create qemu-img command to restore RBD volume with backup", ex);
throw new CloudRuntimeException(String.format("Failed to create qemu-img command to restore %s volume with backup", volumePool.getPoolType()), ex);
}
QemuImgFile srcBackupFile = null;
QemuImgFile destVolumeFile = null;
try {
srcBackupFile = new QemuImgFile(backupPath, QemuImg.PhysicalDiskFormat.QCOW2);
String rbdDestVolumeFile = KVMPhysicalDisk.RBDStringBuilder(volumeStoragePool, volumePath);
destVolumeFile = new QemuImgFile(rbdDestVolumeFile, QemuImg.PhysicalDiskFormat.RAW);
logger.debug("Starting convert backup {} to RBD volume {}", backupPath, volumePath);
String destVolume;
switch(volumePool.getPoolType()) {
case Linstor:
destVolume = volumePath;
break;
case RBD:
destVolume = KVMPhysicalDisk.RBDStringBuilder(volumeStoragePool, volumePath);
break;
default:
throw new CloudRuntimeException(String.format("Unsupported storage pool type [%s] for block device restore with backup.", volumePool.getPoolType()));
}
destVolumeFile = new QemuImgFile(destVolume, QemuImg.PhysicalDiskFormat.RAW);
logger.debug("Starting convert backup {} to volume {}", backupPath, volumePath);
qemu.convert(srcBackupFile, destVolumeFile);
logger.debug("Successfully converted backup {} to RBD volume {}", backupPath, volumePath);
logger.debug("Successfully converted backup {} to volume {}", backupPath, volumePath);
} catch (QemuImgException | LibvirtException e) {
String srcFilename = srcBackupFile != null ? srcBackupFile.getFileName() : null;
String destFilename = destVolumeFile != null ? destVolumeFile.getFileName() : null;
@ -296,12 +335,14 @@ public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper<RestoreBa
private boolean attachVolumeToVm(KVMStoragePoolManager storagePoolMgr, String vmName, PrimaryDataStoreTO volumePool, String volumePath) {
String deviceToAttachDiskTo = getDeviceToAttachDisk(vmName);
int exitValue;
if (volumePool.getPoolType() != Storage.StoragePoolType.RBD) {
exitValue = Script.runSimpleBashScriptForExitValue(String.format(ATTACH_QCOW2_DISK_COMMAND, vmName, volumePath, deviceToAttachDiskTo));
} else {
if (volumePool.getPoolType() == Storage.StoragePoolType.RBD) {
String xmlForRbdDisk = getXmlForRbdDisk(storagePoolMgr, volumePool, volumePath, deviceToAttachDiskTo);
logger.debug("RBD disk xml to attach: {}", xmlForRbdDisk);
exitValue = Script.runSimpleBashScriptForExitValue(String.format(ATTACH_RBD_DISK_XML_COMMAND, vmName, xmlForRbdDisk));
} else if (volumePool.getPoolType() == Storage.StoragePoolType.Linstor) {
exitValue = Script.runSimpleBashScriptForExitValue(String.format(ATTACH_RAW_DISK_COMMAND, vmName, volumePath, deviceToAttachDiskTo));
} else {
exitValue = Script.runSimpleBashScriptForExitValue(String.format(ATTACH_QCOW2_DISK_COMMAND, vmName, volumePath, deviceToAttachDiskTo));
}
return exitValue == 0;
}

View File

@ -52,6 +52,7 @@ public class LibvirtTakeBackupCommandWrapper extends CommandWrapper<TakeBackupCo
List<PrimaryDataStoreTO> volumePools = command.getVolumePools();
final List<String> volumePaths = command.getVolumePaths();
KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
int timeout = command.getWait() > 0 ? command.getWait() * 1000 : libvirtComputingResource.getCmdsTimeout();
List<String> diskPaths = new ArrayList<>();
if (Objects.nonNull(volumePaths)) {
@ -81,7 +82,7 @@ public class LibvirtTakeBackupCommandWrapper extends CommandWrapper<TakeBackupCo
"-d", diskPaths.isEmpty() ? "" : String.join(",", diskPaths)
});
Pair<Integer, String> result = Script.executePipedCommands(commands, libvirtComputingResource.getCmdsTimeout());
Pair<Integer, String> result = Script.executePipedCommands(commands, timeout);
if (result.first() != 0) {
logger.debug("Failed to take VM backup: " + result.second());

View File

@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import com.cloud.agent.api.PrepareStorageClientCommand;
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
@ -644,18 +645,30 @@ public class ScaleIOStorageAdaptor implements StorageAdaptor {
// Assuming SDC service is started, add mdms
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
String[] mdmAddresses = mdms.split(",");
if (mdmAddresses.length > 0) {
if (ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
return new Ternary<>(true, getSDCDetails(details), "MDM added, no need to prepare the SDC client");
}
ScaleIOUtil.addMdms(mdmAddresses);
if (!ScaleIOUtil.isMdmPresent(mdmAddresses[0])) {
return new Ternary<>(false, null, "Failed to add MDMs");
// remove MDMs already present in the config and added to the SDC
String[] mdmAddressesToAdd = Arrays.stream(mdmAddresses)
.filter(Predicate.not(ScaleIOUtil::isMdmPresent))
.toArray(String[]::new);
// if all MDMs are already in the config and added to the SDC
if (mdmAddressesToAdd.length < 1 && mdmAddresses.length > 0) {
String msg = String.format("MDMs %s of the storage pool %s are already added", mdms, uuid);
logger.debug(msg);
return new Ternary<>(true, getSDCDetails(details), msg);
} else if (mdmAddressesToAdd.length > 0) {
ScaleIOUtil.addMdms(mdmAddressesToAdd);
String[] missingMdmAddresses = Arrays.stream(mdmAddressesToAdd)
.filter(Predicate.not(ScaleIOUtil::isMdmPresent))
.toArray(String[]::new);
if (missingMdmAddresses.length > 0) {
String msg = String.format("Failed to add MDMs %s of the storage pool %s", String.join(", ", missingMdmAddresses), uuid);
logger.debug(msg);
return new Ternary<>(false, null, msg);
} else {
logger.debug(String.format("MDMs %s added to storage pool %s", mdms, uuid));
logger.debug("MDMs {} of the storage pool {} are added", mdmAddressesToAdd, uuid);
applyMdmsChangeWaitTime(details);
}
} else {
return new Ternary<>(false, getSDCDetails(details), "No MDM addresses provided");
}
}

View File

@ -115,6 +115,145 @@ public class LibvirtGpuDefTest extends TestCase {
assertTrue(gpuXml.contains("</hostdev>"));
}
@Test
public void testGpuDef_withFullPciAddressDomainZero() {
LibvirtGpuDef gpuDef = new LibvirtGpuDef();
VgpuTypesInfo pciGpuInfo = new VgpuTypesInfo(
GpuDevice.DeviceType.PCI,
"passthrough",
"passthrough",
"0000:00:02.0",
"10de",
"NVIDIA Corporation",
"1b38",
"Tesla T4"
);
gpuDef.defGpu(pciGpuInfo);
String gpuXml = gpuDef.toString();
assertTrue(gpuXml.contains("<address domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>"));
}
@Test
public void testGpuDef_withFullPciAddressNonZeroDomain() {
LibvirtGpuDef gpuDef = new LibvirtGpuDef();
VgpuTypesInfo pciGpuInfo = new VgpuTypesInfo(
GpuDevice.DeviceType.PCI,
"passthrough",
"passthrough",
"0001:65:00.0",
"10de",
"NVIDIA Corporation",
"1b38",
"Tesla T4"
);
gpuDef.defGpu(pciGpuInfo);
String gpuXml = gpuDef.toString();
assertTrue(gpuXml.contains("<address domain='0x0001' bus='0x65' slot='0x00' function='0x0'/>"));
}
@Test
public void testGpuDef_withNvidiaStyleEightDigitDomain() {
// nvidia-smi reports PCI addresses with an 8-digit domain (e.g. "00000001:af:00.1").
// generatePciXml must normalize it to the canonical 4-digit form "0x0001".
LibvirtGpuDef gpuDef = new LibvirtGpuDef();
VgpuTypesInfo pciGpuInfo = new VgpuTypesInfo(
GpuDevice.DeviceType.PCI,
"passthrough",
"passthrough",
"00000001:af:00.1",
"10de",
"NVIDIA Corporation",
"1b38",
"Tesla T4"
);
gpuDef.defGpu(pciGpuInfo);
String gpuXml = gpuDef.toString();
assertTrue(gpuXml.contains("<address domain='0x0001' bus='0xaf' slot='0x00' function='0x1'/>"));
}
@Test
public void testGpuDef_withFullPciAddressVfNonZeroDomain() {
LibvirtGpuDef gpuDef = new LibvirtGpuDef();
VgpuTypesInfo vfGpuInfo = new VgpuTypesInfo(
GpuDevice.DeviceType.PCI,
"VF-Profile",
"VF-Profile",
"0002:81:00.3",
"10de",
"NVIDIA Corporation",
"1eb8",
"Tesla T4"
);
gpuDef.defGpu(vfGpuInfo);
String gpuXml = gpuDef.toString();
// Non-passthrough NVIDIA VFs should be unmanaged
assertTrue(gpuXml.contains("<hostdev mode='subsystem' type='pci' managed='no' display='off'>"));
assertTrue(gpuXml.contains("<address domain='0x0002' bus='0x81' slot='0x00' function='0x3'/>"));
}
@Test
public void testGpuDef_withLegacyShortBdfDefaultsDomainToZero() {
// Backward compatibility: short BDF with no domain segment must still
// produce a valid libvirt address with domain 0x0000.
LibvirtGpuDef gpuDef = new LibvirtGpuDef();
VgpuTypesInfo pciGpuInfo = new VgpuTypesInfo(
GpuDevice.DeviceType.PCI,
"passthrough",
"passthrough",
"af:00.0",
"10de",
"NVIDIA Corporation",
"1b38",
"Tesla T4"
);
gpuDef.defGpu(pciGpuInfo);
String gpuXml = gpuDef.toString();
assertTrue(gpuXml.contains("<address domain='0x0000' bus='0xaf' slot='0x00' function='0x0'/>"));
}
@Test
public void testGpuDef_withInvalidBusAddressThrows() {
String[] invalidAddresses = {
"notahex:00.0", // non-hex bus
"gg:00:02.0", // non-hex domain
"00:02:03:04", // too many colon-separated parts
"00", // missing slot/function
"00:02", // missing function (no dot)
"00:02.0.1", // extra dot in ss.f
};
for (String addr : invalidAddresses) {
LibvirtGpuDef gpuDef = new LibvirtGpuDef();
VgpuTypesInfo info = new VgpuTypesInfo(
GpuDevice.DeviceType.PCI,
"passthrough",
"passthrough",
addr,
"10de",
"NVIDIA Corporation",
"1b38",
"Tesla T4"
);
gpuDef.defGpu(info);
try {
String ignored = gpuDef.toString();
fail("Expected IllegalArgumentException for address: " + addr + " but got: " + ignored);
} catch (IllegalArgumentException e) {
assertTrue("Exception message should contain the bad address",
e.getMessage().contains(addr));
}
}
}
@Test
public void testGpuDef_withNullDeviceType() {
LibvirtGpuDef gpuDef = new LibvirtGpuDef();

View File

@ -18,6 +18,7 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
import com.cloud.agent.api.Answer;
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
import com.cloud.storage.Storage;
import com.cloud.utils.script.Script;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.backup.BackupAnswer;
@ -66,7 +67,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getMountOptions()).thenReturn("rw");
when(command.isVmExists()).thenReturn(null);
when(command.getDiskType()).thenReturn("root");
when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -109,6 +113,7 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.isVmExists()).thenReturn(true);
when(command.getDiskType()).thenReturn("root");
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupVolumesUUIDs()).thenReturn(Arrays.asList("volume-123"));
@ -148,6 +153,7 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.isVmExists()).thenReturn(false);
when(command.getDiskType()).thenReturn("root");
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -185,7 +191,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getMountOptions()).thenReturn("username=user,password=pass");
when(command.isVmExists()).thenReturn(null);
when(command.getDiskType()).thenReturn("root");
when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -226,7 +235,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
lenient().when(command.getMountOptions()).thenReturn("rw");
lenient().when(command.isVmExists()).thenReturn(null);
lenient().when(command.getDiskType()).thenReturn("root");
lenient().when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
lenient().when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
lenient().when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
lenient().when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -262,7 +274,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getMountOptions()).thenReturn("rw");
when(command.isVmExists()).thenReturn(null);
when(command.getDiskType()).thenReturn("root");
when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -308,7 +323,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getMountOptions()).thenReturn("rw");
when(command.isVmExists()).thenReturn(null);
when(command.getDiskType()).thenReturn("root");
when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -356,7 +374,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getMountOptions()).thenReturn("rw");
when(command.isVmExists()).thenReturn(null);
when(command.getDiskType()).thenReturn("root");
when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -370,7 +391,15 @@ public class LibvirtRestoreBackupCommandWrapperTest {
try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) {
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class)))
.thenReturn(0); // Mount success
.thenAnswer(invocation -> {
String command = invocation.getArgument(0);
if (command.contains("mount")) {
return 0; // mount success
} else if (command.contains("rsync")) {
return 1; // Rsync failure
}
return 0; // Other commands success
});
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString()))
.thenAnswer(invocation -> {
String command = invocation.getArgument(0);
@ -378,8 +407,6 @@ public class LibvirtRestoreBackupCommandWrapperTest {
return 0; // File exists
} else if (command.contains("qemu-img check")) {
return 0; // File is valid
} else if (command.contains("rsync")) {
return 1; // Rsync failure
}
return 0; // Other commands success
});
@ -406,7 +433,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getMountOptions()).thenReturn("rw");
when(command.isVmExists()).thenReturn(null);
when(command.getDiskType()).thenReturn("root");
when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -420,7 +450,15 @@ public class LibvirtRestoreBackupCommandWrapperTest {
try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) {
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class)))
.thenReturn(0); // Mount success
.thenAnswer(invocation -> {
String command = invocation.getArgument(0);
if (command.contains("mount")) {
return 0; // Mount success
} else if (command.contains("rsync")) {
return 0; // Rsync success
}
return 0; // Other commands success
});
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString()))
.thenAnswer(invocation -> {
String command = invocation.getArgument(0);
@ -428,8 +466,6 @@ public class LibvirtRestoreBackupCommandWrapperTest {
return 0; // File exists
} else if (command.contains("qemu-img check")) {
return 0; // File is valid
} else if (command.contains("rsync")) {
return 0; // Rsync success
} else if (command.contains("virsh attach-disk")) {
return 1; // Attach failure
}
@ -460,7 +496,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
lenient().when(command.getMountOptions()).thenReturn("rw");
lenient().when(command.isVmExists()).thenReturn(null);
lenient().when(command.getDiskType()).thenReturn("root");
lenient().when(command.getRestoreVolumeSizes()).thenReturn(Arrays.asList(1024L));
lenient().when(command.getWait()).thenReturn(60);
PrimaryDataStoreTO primaryDataStore = Mockito.mock(PrimaryDataStoreTO.class);
lenient().when(primaryDataStore.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(primaryDataStore));
lenient().when(command.getRestoreVolumePaths()).thenReturn(Arrays.asList("/var/lib/libvirt/images/volume-123"));
when(command.getBackupFiles()).thenReturn(Arrays.asList("volume-123"));
@ -492,6 +531,8 @@ public class LibvirtRestoreBackupCommandWrapperTest {
when(command.getDiskType()).thenReturn("root");
PrimaryDataStoreTO primaryDataStore1 = Mockito.mock(PrimaryDataStoreTO.class);
PrimaryDataStoreTO primaryDataStore2 = Mockito.mock(PrimaryDataStoreTO.class);
when(primaryDataStore1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(primaryDataStore2.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(command.getRestoreVolumePools()).thenReturn(Arrays.asList(
primaryDataStore1,
primaryDataStore2
@ -510,10 +551,10 @@ public class LibvirtRestoreBackupCommandWrapperTest {
filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath);
try (MockedStatic<Script> scriptMock = mockStatic(Script.class)) {
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class)))
.thenReturn(0); // Mount success
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString()))
.thenReturn(0); // All other commands success
.thenReturn(0); // All commands success
scriptMock.when(() -> Script.runSimpleBashScriptForExitValue(anyString(), anyInt(), any(Boolean.class)))
.thenReturn(0); // All commands success
filesMock.when(() -> Files.deleteIfExists(any(Path.class))).thenReturn(true);

View File

@ -485,6 +485,12 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
return null;
}
@Override
public Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId) {
// TODO Auto-generated method stub
return null;
}
@Override
public void checkAccess(Account account, ServiceOffering so, DataCenter zone) throws PermissionDeniedException {
// TODO Auto-generated method stub

View File

@ -54,6 +54,13 @@ public class DeleteNsxNatRuleCommand extends NsxNetworkCommand {
return protocol;
}
public String getNetworkServiceName() {
if (service != null) {
return service.getName();
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) {

View File

@ -415,10 +415,10 @@ public class NsxResource implements ServerResource {
private NsxAnswer executeRequest(DeleteNsxNatRuleCommand cmd) {
String ruleName = null;
if (cmd.getService() == Network.Service.StaticNat) {
if (Network.Service.StaticNat.getName().equals(cmd.getNetworkServiceName())) {
ruleName = NsxControllerUtils.getStaticNatRuleName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(),
cmd.getNetworkResourceId(), cmd.isResourceVpc());
} else if (cmd.getService() == Network.Service.PortForwarding) {
} else if (Network.Service.PortForwarding.getName().equals(cmd.getNetworkServiceName())) {
ruleName = NsxControllerUtils.getPortForwardRuleName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(),
cmd.getNetworkResourceId(), cmd.getRuleId(), cmd.isResourceVpc());
}
@ -456,7 +456,7 @@ public class NsxResource implements ServerResource {
try {
nsxApiClient.deleteNsxLbResources(tier1GatewayName, cmd.getLbId());
} catch (Exception e) {
logger.error(String.format("Failed to add NSX load balancer rule %s for network: %s", ruleName, cmd.getNetworkResourceName()));
logger.error(String.format("Failed to delete NSX load balancer rule %s for network: %s", ruleName, cmd.getNetworkResourceName()));
return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage()));
}
return new NsxAnswer(cmd, true, null);

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.resource;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkVO;
import com.cloud.utils.exception.CloudRuntimeException;
import com.vmware.nsx.model.TransportZone;
@ -61,6 +62,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@ -247,8 +249,12 @@ public class NsxResourceTest {
@Test
public void testDeleteNsxNatRule() {
DeleteNsxNatRuleCommand cmd = new DeleteNsxNatRuleCommand(domainId, accountId, zoneId, 3L, "VPC01", true, 2L, 5L, "22", "tcp");
Network.Service service = mock(Network.Service.class);
when(service.getName()).thenReturn("PortForwarding");
cmd.setService(service);
NsxAnswer answer = (NsxAnswer) nsxResource.executeRequest(cmd);
assertTrue(answer.getResult());
verify(nsxApi).deleteNatRule(service, "22", "tcp", "VPC01", "D1-A2-Z1-V3", "D1-A2-Z1-V3-PF5");
}
@Test

View File

@ -129,6 +129,7 @@
<module>storage/volume/default</module>
<module>storage/volume/nexenta</module>
<module>storage/volume/sample</module>
<module>storage/volume/ontap</module>
<module>storage/volume/solidfire</module>
<module>storage/volume/scaleio</module>
<module>storage/volume/linstor</module>

View File

@ -0,0 +1,123 @@
<!--
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.
-->
# Apache CloudStack - NetApp ONTAP Storage Plugin
## Overview
The NetApp ONTAP Storage Plugin provides integration between Apache CloudStack and NetApp ONTAP storage systems. This plugin enables CloudStack to provision and manage primary storage on ONTAP clusters, supporting both NAS (NFS) and SAN (iSCSI) protocols.
## Features
- **Primary Storage Support**: Provision and manage primary storage pools on NetApp ONTAP
- **Multiple Protocols**: Support for NFS 3.0 and iSCSI protocols
- **Unified Storage**: Integration with traditional ONTAP unified storage architecture
- **KVM Hypervisor Support**: Supports KVM hypervisor environments
- **Managed Storage**: Operates as managed storage with full lifecycle management
- **Flexible Scoping**: Support for Zone-wide and Cluster-scoped storage pools
## Architecture
### Component Structure
| Package | Description |
|---------|-------------------------------------------------------|
| `driver` | Primary datastore driver implementation |
| `feign` | REST API clients and data models for ONTAP operations |
| `lifecycle` | Storage pool lifecycle management |
| `listener` | Host connection event handlers |
| `provider` | Main provider and strategy factory |
| `service` | ONTAP Storage strategy implementations (NAS/SAN) |
| `utils` | Constants and helper utilities |
## Requirements
### ONTAP Requirements
- NetApp ONTAP 9.15.1 or higher
- Storage Virtual Machine (SVM) configured with appropriate protocols enabled
- Management LIF accessible from CloudStack management server
- Data LIF(s) accessible from hypervisor hosts and are of IPv4 type
- Aggregates assigned to the SVM with sufficient capacity
### CloudStack Requirements
- Apache CloudStack current version or higher
- KVM hypervisor hosts
- For iSCSI: Hosts must have iSCSI initiator configured with valid IQN
- For NFS: Hosts must have NFS client packages installed
### Minimum Volume Size
ONTAP requires a minimum volume size of **1.56 GB** (1,677,721,600 bytes). The plugin will automatically adjust requested sizes below this threshold.
## Configuration
### Storage Pool Creation Parameters
When creating an ONTAP primary storage pool, provide the following details in the URL field (semicolon-separated key=value pairs):
| Parameter | Required | Description |
|-----------|----------|-------------|
| `username` | Yes | ONTAP cluster admin username |
| `password` | Yes | ONTAP cluster admin password |
| `svmName` | Yes | Storage Virtual Machine name |
| `protocol` | Yes | Storage protocol (`NFS3` or `ISCSI`) |
| `managementLIF` | Yes | ONTAP cluster management LIF IP address |
### Example URL Format
```
username=admin;password=secretpass;svmName=svm1;protocol=ISCSI;managementLIF=192.168.1.100
```
## Port Configuration
| Protocol | Default Port |
|----------|--------------|
| NFS | 2049 |
| iSCSI | 3260 |
| ONTAP Management API | 443 (HTTPS) |
## Limitations
- Supports only **KVM** hypervisor
- Supports only **Unified ONTAP** storage (disaggregated not supported)
- Supports only **NFS3** and **iSCSI** protocols
- IPv6 type and FQDN LIFs are not supported
## Troubleshooting
### Common Issues
1. **Connection Failures**
- Verify management LIF is reachable from CloudStack management server
- Check firewall rules for port 443
2. **Protocol Errors**
- Ensure the protocol (NFS/iSCSI) is enabled on the SVM
- Verify Data LIFs are configured for the protocol
3. **Capacity Errors**
- Check aggregate space availability
- Ensure requested volume size meets minimum requirements (1.56 GB)
4. **Host Connection Issues**
- For iSCSI: Verify host IQN is properly configured in host's storage URL
- For NFS: Ensure NFS client is installed and running

View File

@ -0,0 +1,169 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-storage-volume-ontap</artifactId>
<name>Apache CloudStack Plugin - Storage Volume ONTAP Provider</name>
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.23.0.0-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<properties>
<spring-cloud.version>2021.0.7</spring-cloud.version>
<openfeign.version>11.0</openfeign.version>
<httpclient.version>4.5.14</httpclient.version>
<swagger-annotations.version>1.6.2</swagger-annotations.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<jackson-databind.version>2.13.4</jackson-databind.version>
<assertj.version>3.24.2</assertj.version>
<junit-jupiter.version>5.8.1</junit-jupiter.version>
<mockito.version>3.12.4</mockito.version>
<mockito-junit-jupiter.version>5.2.0</mockito-junit-jupiter.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-storage-volume-default</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${openfeign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${openfeign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${openfeign.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-engine-storage-volume</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito-junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito Inline (for static method mocking) -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>false</skipTests>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,188 @@
/*
* 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.driver;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.host.Host;
import com.cloud.storage.Storage;
import com.cloud.storage.StoragePool;
import com.cloud.storage.Volume;
import com.cloud.utils.Pair;
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.Map;
public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
private static final Logger logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class);
@Override
public Map<String, String> getCapabilities() {
logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
Map<String, String> mapCapabilities = new HashMap<>();
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());
return mapCapabilities;
}
@Override
public DataTO getTO(DataObject data) {
return null;
}
@Override
public DataStoreTO getStoreTO(DataStore store) { return null; }
@Override
public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
throw new UnsupportedOperationException("Create operation is not supported for ONTAP primary storage.");
}
@Override
public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback<CommandResult> callback) {
throw new UnsupportedOperationException("Delete operation is not supported for ONTAP primary storage.");
}
@Override
public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) {
throw new UnsupportedOperationException("Copy operation is not supported for ONTAP primary storage.");
}
@Override
public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) {
throw new UnsupportedOperationException("Copy operation is not supported for ONTAP primary storage.");
}
@Override
public boolean canCopy(DataObject srcData, DataObject destData) {
return false;
}
@Override
public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {}
@Override
public ChapInfo getChapInfo(DataObject dataObject) {
return null;
}
@Override
public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore) {
return false;
}
@Override
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {
throw new UnsupportedOperationException("Revoke access operation is not supported for ONTAP primary storage.");
}
@Override
public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool storagePool) {
return 0;
}
@Override
public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool) {
return 0;
}
@Override
public long getUsedBytes(StoragePool storagePool) {
return 0;
}
@Override
public long getUsedIops(StoragePool storagePool) {
return 0;
}
@Override
public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback<CreateCmdResult> callback) {}
@Override
public void revertSnapshot(SnapshotInfo snapshotOnImageStore, SnapshotInfo snapshotOnPrimaryStore, AsyncCompletionCallback<CommandResult> callback) {}
@Override
public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, QualityOfServiceState qualityOfServiceState) {}
@Override
public boolean canProvideStorageStats() {
return false;
}
@Override
public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
return null;
}
@Override
public boolean canProvideVolumeStats() {
return true;
}
@Override
public Pair<Long, Long> getVolumeStats(StoragePool storagePool, String volumeId) {
return null;
}
@Override
public boolean canHostAccessStoragePool(Host host, StoragePool pool) {
return true;
}
@Override
public boolean isVmInfoNeeded() {
return true;
}
@Override
public void provideVmInfo(long vmId, long volumeId) {}
@Override
public boolean isVmTagsNeeded(String tagKey) {
return true;
}
@Override
public void provideVmTags(long vmId, long volumeId, String tagValue) {}
@Override
public boolean isStorageSupportHA(Storage.StoragePoolType type) {
return true;
}
@Override
public void detachVolumeFromAllStorageNodes(Volume volume) {}
}

View File

@ -0,0 +1,45 @@
/*
* 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.feign;
import feign.Feign;
public class FeignClientFactory {
private final FeignConfiguration feignConfiguration;
public FeignClientFactory() {
this.feignConfiguration = new FeignConfiguration();
}
public FeignClientFactory(FeignConfiguration feignConfiguration) {
this.feignConfiguration = feignConfiguration;
}
public <T> T createClient(Class<T> clientClass, String baseURL) {
return Feign.builder()
.client(feignConfiguration.createClient())
.encoder(feignConfiguration.createEncoder())
.decoder(feignConfiguration.createDecoder())
.retryer(feignConfiguration.createRetryer())
.requestInterceptor(feignConfiguration.createRequestInterceptor())
.target(clientClass, baseURL);
}
}

View File

@ -0,0 +1,158 @@
/*
* 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.feign;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.Client;
import feign.httpclient.ApacheHttpClient;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.EncodeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
public class FeignConfiguration {
private static final Logger logger = LogManager.getLogger(FeignConfiguration.class);
private final int retryMaxAttempt = 3;
private final int retryMaxIntervalInSecs = 5;
private final String ontapFeignMaxConnection = "80";
private final String ontapFeignMaxConnectionPerRoute = "20";
private final ObjectMapper objectMapper;
public FeignConfiguration() {
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public Client createClient() {
int maxConn;
int maxConnPerRoute;
try {
maxConn = Integer.parseInt(this.ontapFeignMaxConnection);
} catch (Exception e) {
logger.error("ontapFeignClient: parse max connection failed, using default");
maxConn = 20;
}
try {
maxConnPerRoute = Integer.parseInt(this.ontapFeignMaxConnectionPerRoute);
} catch (Exception e) {
logger.error("ontapFeignClient: parse max connection per route failed, using default");
maxConnPerRoute = 2;
}
logger.debug("ontapFeignClient: maxConn={}, maxConnPerRoute={}", maxConn, maxConnPerRoute);
ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> 0;
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(maxConn)
.setMaxConnPerRoute(maxConnPerRoute)
.setKeepAliveStrategy(keepAliveStrategy)
.setSSLSocketFactory(getSSLSocketFactory())
.setConnectionTimeToLive(60, TimeUnit.SECONDS)
.build();
return new ApacheHttpClient(httpClient);
}
private SSLConnectionSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustAllStrategy()).build();
return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public RequestInterceptor createRequestInterceptor() {
return template -> {
logger.info("Feign Request URL: {}", template.url());
logger.info("HTTP Method: {}", template.method());
logger.trace("Headers: {}", template.headers());
if (template.body() != null) {
logger.info("Body: {}", new String(template.body(), StandardCharsets.UTF_8));
}
};
}
public Retryer createRetryer() {
return new Retryer.Default(1000L, retryMaxIntervalInSecs * 1000L, retryMaxAttempt);
}
public Encoder createEncoder() {
return new Encoder() {
@Override
public void encode(Object object, Type bodyType, feign.RequestTemplate template) throws EncodeException {
if (object == null) {
template.body(null, StandardCharsets.UTF_8);
return;
}
try {
byte[] jsonBytes = objectMapper.writeValueAsBytes(object);
template.body(jsonBytes, StandardCharsets.UTF_8);
template.header("Content-Type", "application/json");
} catch (JsonProcessingException e) {
throw new EncodeException("Error encoding object to JSON", e);
}
}
};
}
public Decoder createDecoder() {
return new Decoder() {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException {
if (response.body() == null) {
logger.debug("Response body is null, returning null");
return null;
}
String json = null;
try (InputStream bodyStream = response.body().asInputStream()) {
json = new String(bodyStream.readAllBytes(), StandardCharsets.UTF_8);
logger.debug("Decoding JSON response: {}", json);
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
logger.error("IOException during decoding. Status: {}, Raw body: {}", response.status(), json, e);
throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e);
} catch (Exception e) {
logger.error("Unexpected error during decoding. Status: {}, Type: {}, Raw body: {}", response.status(), type, json, e);
throw new DecodeException(response.status(), "Unexpected error during decoding", response.request(), e);
}
}
};
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.feign.client;
import org.apache.cloudstack.storage.feign.model.Aggregate;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
public interface AggregateFeignClient {
@RequestLine("GET /api/storage/aggregates")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Aggregate> getAggregateResponse(@Param("authHeader") String authHeader);
@RequestLine("GET /api/storage/aggregates/{uuid}")
@Headers({"Authorization: {authHeader}"})
Aggregate getAggregateByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
}

View File

@ -0,0 +1,32 @@
/*
* 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.feign.client;
import org.apache.cloudstack.storage.feign.model.Cluster;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
public interface ClusterFeignClient {
@RequestLine("GET /api/cluster")
@Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
Cluster getCluster(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords);
}

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.storage.feign.client;
import org.apache.cloudstack.storage.feign.model.Job;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
public interface JobFeignClient {
@RequestLine("GET /api/cluster/jobs/{uuid}")
@Headers({"Authorization: {authHeader}"})
Job getJobByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
}

View File

@ -0,0 +1,86 @@
/*
* 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.feign.client;
import feign.QueryMap;
import org.apache.cloudstack.storage.feign.model.ExportPolicy;
import org.apache.cloudstack.storage.feign.model.FileInfo;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import java.util.Map;
public interface NASFeignClient {
// File Operations
@RequestLine("GET /api/storage/volumes/{volumeUuid}/files/{path}")
@Headers({"Authorization: {authHeader}"})
OntapResponse<FileInfo> getFileResponse(@Param("authHeader") String authHeader,
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath);
@RequestLine("DELETE /api/storage/volumes/{volumeUuid}/files/{path}")
@Headers({"Authorization: {authHeader}"})
void deleteFile(@Param("authHeader") String authHeader,
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath);
@RequestLine("PATCH /api/storage/volumes/{volumeUuid}/files/{path}")
@Headers({"Authorization: {authHeader}"})
void updateFile(@Param("authHeader") String authHeader,
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath,
FileInfo fileInfo);
@RequestLine("POST /api/storage/volumes/{volumeUuid}/files/{path}")
@Headers({"Authorization: {authHeader}"})
void createFile(@Param("authHeader") String authHeader,
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath,
FileInfo file);
// Export Policy Operations
@RequestLine("POST /api/protocols/nfs/export-policies")
@Headers({"Authorization: {authHeader}"})
void createExportPolicy(@Param("authHeader") String authHeader,
ExportPolicy exportPolicy);
@RequestLine("GET /api/protocols/nfs/export-policies")
@Headers({"Authorization: {authHeader}"})
OntapResponse<ExportPolicy> getExportPolicyResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
@RequestLine("GET /api/protocols/nfs/export-policies/{id}")
@Headers({"Authorization: {authHeader}"})
ExportPolicy getExportPolicyById(@Param("authHeader") String authHeader,
@Param("id") String id);
@RequestLine("DELETE /api/protocols/nfs/export-policies/{id}")
@Headers({"Authorization: {authHeader}"})
void deleteExportPolicyById(@Param("authHeader") String authHeader,
@Param("id") String id);
@RequestLine("PATCH /api/protocols/nfs/export-policies/{id}")
@Headers({"Authorization: {authHeader}"})
OntapResponse<ExportPolicy> updateExportPolicy(@Param("authHeader") String authHeader,
@Param("id") String id,
ExportPolicy request);
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* 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.feign.client;
import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;
import org.apache.cloudstack.storage.feign.model.IpInterface;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import java.util.Map;
public interface NetworkFeignClient {
@RequestLine("GET /api/network/ip/interfaces")
@Headers({"Authorization: {authHeader}"})
OntapResponse<IpInterface> getNetworkIpInterfaces(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryParams);
}

View File

@ -0,0 +1,91 @@
/*
* 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.feign.client;
import feign.QueryMap;
import org.apache.cloudstack.storage.feign.model.Igroup;
import org.apache.cloudstack.storage.feign.model.IscsiService;
import org.apache.cloudstack.storage.feign.model.Lun;
import org.apache.cloudstack.storage.feign.model.LunMap;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import java.util.Map;
public interface SANFeignClient {
// iSCSI Service APIs
@RequestLine("GET /api/protocols/san/iscsi/services")
@Headers({"Authorization: {authHeader}"})
OntapResponse<IscsiService> getIscsiServices(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
// LUN Operation APIs
@RequestLine("POST /api/storage/luns?return_records={returnRecords}")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Lun> createLun(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, Lun lun);
@RequestLine("GET /api/storage/luns")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Lun> getLunResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
@RequestLine("GET /{uuid}")
@Headers({"Authorization: {authHeader}"})
Lun getLunByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
@RequestLine("PATCH /{uuid}")
@Headers({"Authorization: {authHeader}"})
void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Lun lun);
@RequestLine("DELETE /api/storage/luns/{uuid}")
@Headers({"Authorization: {authHeader}"})
void deleteLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, @QueryMap Map<String, Object> queryMap);
// iGroup Operation APIs
@RequestLine("POST /api/protocols/san/igroups?return_records={returnRecords}")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Igroup> createIgroup(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, Igroup igroupRequest);
@RequestLine("GET /api/protocols/san/igroups")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Igroup> getIgroupResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
@RequestLine("GET /{uuid}")
@Headers({"Authorization: {authHeader}"})
Igroup getIgroupByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
@RequestLine("DELETE /api/protocols/san/igroups/{uuid}")
@Headers({"Authorization: {authHeader}"})
void deleteIgroup(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
// LUN Maps Operation APIs
@RequestLine("POST /api/protocols/san/lun-maps")
@Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
OntapResponse<LunMap> createLunMap(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, LunMap lunMap);
@RequestLine("GET /api/protocols/san/lun-maps")
@Headers({"Authorization: {authHeader}"})
OntapResponse<LunMap> getLunMapResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
@RequestLine("DELETE /api/protocols/san/lun-maps/{lunUuid}/{igroupUuid}")
@Headers({"Authorization: {authHeader}"})
void deleteLunMap(@Param("authHeader") String authHeader,
@Param("lunUuid") String lunUUID,
@Param("igroupUuid") String igroupUUID);
}

View File

@ -0,0 +1,40 @@
/*
* 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.feign.client;
import feign.QueryMap;
import org.apache.cloudstack.storage.feign.model.Svm;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import java.util.Map;
public interface SvmFeignClient {
// SVM Operation APIs
@RequestLine("GET /api/svm/svms")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Svm> getSvmResponse(@QueryMap Map<String, Object> queryMap, @Param("authHeader") String authHeader);
@RequestLine("GET /api/svm/svms/{uuid}")
@Headers({"Authorization: {authHeader}"})
Svm getSvmByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
}

View File

@ -0,0 +1,56 @@
/*
* 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.feign.client;
import feign.QueryMap;
import org.apache.cloudstack.storage.feign.model.Volume;
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import java.util.Map;
public interface VolumeFeignClient {
@RequestLine("DELETE /api/storage/volumes/{uuid}")
@Headers({"Authorization: {authHeader}"})
JobResponse deleteVolume(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
@RequestLine("POST /api/storage/volumes")
@Headers({"Authorization: {authHeader}"})
JobResponse createVolumeWithJob(@Param("authHeader") String authHeader, Volume volumeRequest);
@RequestLine("GET /api/storage/volumes")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Volume> getAllVolumes(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryParams);
@RequestLine("GET /api/storage/volumes/{uuid}")
@Headers({"Authorization: {authHeader}"})
Volume getVolumeByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
@RequestLine("GET /api/storage/volumes")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Volume> getVolume(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);
@RequestLine("PATCH /api/storage/volumes/{uuid}")
@Headers({ "Authorization: {authHeader}"})
JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest);
}

View File

@ -0,0 +1,165 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Aggregate {
// Replace previous enum with case-insensitive mapping
public enum StateEnum {
ONLINE("online");
private final String value;
StateEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
@JsonCreator
public static StateEnum fromValue(String text) {
for (StateEnum b : StateEnum.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
}
@JsonProperty("name")
private String name = null;
@Override
public int hashCode() {
return Objects.hash(getName(), getUuid());
}
@JsonProperty("uuid")
private String uuid = null;
@JsonProperty("state")
private StateEnum state = null;
@JsonProperty("space")
private AggregateSpace space = null;
public Aggregate name(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Aggregate uuid(String uuid) {
this.uuid = uuid;
return this;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public StateEnum getState() {
return state;
}
public AggregateSpace getSpace() {
return space;
}
public Double getAvailableBlockStorageSpace() {
if (space != null && space.blockStorage != null) {
return space.blockStorage.available;
}
return null;
}
@Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Aggregate diskAggregates = (Aggregate) o;
return Objects.equals(this.name, diskAggregates.name) &&
Objects.equals(this.uuid, diskAggregates.uuid);
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(java.lang.Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
@Override
public String toString() {
return "DiskAggregates [name=" + name + ", uuid=" + uuid + "]";
}
public static class AggregateSpace {
@JsonProperty("block_storage")
private AggregateSpaceBlockStorage blockStorage = null;
}
public static class AggregateSpaceBlockStorage {
@JsonProperty("available")
private Double available = null;
@JsonProperty("size")
private Double size = null;
@JsonProperty("used")
private Double used = null;
}
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AntiRansomware {
@JsonProperty("state")
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
/**
* Complete cluster information
*/
@SuppressWarnings("checkstyle:RegexpSingleline")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Cluster {
@JsonProperty("name")
private String name = null;
@JsonProperty("uuid")
private String uuid = null;
@JsonProperty("version")
private Version version = null;
@JsonProperty("health")
private String health = null;
@JsonProperty("san_optimized")
private Boolean sanOptimized = null;
@JsonProperty("disaggregated")
private Boolean disaggregated = null;
public String getHealth() {
return health;
}
public void setHealth(String health) {
this.health = health;
}
public Cluster name(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUuid() {
return uuid;
}
public Cluster version(Version version) {
this.version = version;
return this;
}
public Version getVersion() {
return version;
}
public void setVersion(Version version) {
this.version = version;
}
public Boolean getSanOptimized() {
return sanOptimized;
}
public void setSanOptimized(Boolean sanOptimized) {
this.sanOptimized = sanOptimized;
}
public Boolean getDisaggregated() {
return disaggregated;
}
public void setDisaggregated(Boolean disaggregated) {
this.disaggregated = disaggregated;
}
@Override
public int hashCode() {
return Objects.hash(getName(), getUuid());
}
@Override
public boolean equals(java.lang.Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Cluster cluster = (Cluster) o;
return Objects.equals(this.name, cluster.name) &&
Objects.equals(this.uuid, cluster.uuid);
}
@Override
public String toString() {
return "Cluster{" +
"name='" + name + '\'' +
", uuid='" + uuid + '\'' +
", version=" + version +
", sanOptimized=" + sanOptimized +
", disaggregated=" + disaggregated +
'}';
}
}

View File

@ -0,0 +1,122 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ExportPolicy {
@JsonProperty("id")
private BigInteger id = null;
@JsonProperty("name")
private String name = null;
@JsonProperty("rules")
private List<ExportRule> rules = null;
@JsonProperty("svm")
private Svm svm = null;
public BigInteger getId() {
return id;
}
public ExportPolicy name(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ExportPolicy rules(List<ExportRule> rules) {
this.rules = rules;
return this;
}
public List<ExportRule> getRules() {
return rules;
}
public void setRules(List<ExportRule> rules) {
this.rules = rules;
}
public ExportPolicy svm(Svm svm) {
this.svm = svm;
return this;
}
public Svm getSvm() {
return svm;
}
public void setSvm(Svm svm) {
this.svm = svm;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExportPolicy exportPolicy = (ExportPolicy) o;
return Objects.equals(this.id, exportPolicy.id) &&
Objects.equals(this.name, exportPolicy.name);
}
@Override
public int hashCode() {
return Objects.hash( id, name);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ExportPolicy {\n");
sb.append(" id: ").append(toIndentedString(id)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" rules: ").append(toIndentedString(rules)).append("\n");
sb.append(" svm: ").append(toIndentedString(svm)).append("\n");
sb.append("}");
return sb.toString();
}
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@ -0,0 +1,195 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
/**
* ExportRule
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ExportRule {
@JsonProperty("anonymous_user")
private String anonymousUser ;
@JsonProperty("clients")
private List<ExportClient> clients = null;
@JsonProperty("index")
private Integer index = null;
public enum ProtocolsEnum {
ANY("any"),
NFS("nfs"),
NFS3("nfs3"),
NFS4("nfs4");
private String value;
ProtocolsEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
public static ProtocolsEnum fromValue(String text) {
for (ProtocolsEnum b : ProtocolsEnum.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
}
@JsonProperty("protocols")
private List<ProtocolsEnum> protocols = null;
@JsonProperty("ro_rule")
private List<String> roRule = null;
@JsonProperty("rw_rule")
private List<String> rwRule = null;
@JsonProperty("superuser")
private List<String> superuser = null;
public ExportRule anonymousUser(String anonymousUser) {
this.anonymousUser = anonymousUser;
return this;
}
public String getAnonymousUser() {
return anonymousUser;
}
public void setAnonymousUser(String anonymousUser) {
this.anonymousUser = anonymousUser;
}
public ExportRule clients(List<ExportClient> clients) {
this.clients = clients;
return this;
}
public List<ExportClient> getClients() {
return clients;
}
public void setClients(List<ExportClient> clients) {
this.clients = clients;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index)
{
this.index=index;
}
public ExportRule protocols(List<ProtocolsEnum> protocols) {
this.protocols = protocols;
return this;
}
public List<ProtocolsEnum> getProtocols() {
return protocols;
}
public void setProtocols(List<ProtocolsEnum> protocols) {
this.protocols = protocols;
}
public static class ExportClient {
@JsonProperty("match")
private String match = null;
public ExportClient match (String match) {
this.match = match;
return this;
}
public String getMatch () {
return match;
}
public void setMatch (String match) {
this.match = match;
}
}
public List<String> getRwRule() {
return rwRule;
}
public void setRwRule(List<String> rwRule) {
this.rwRule = rwRule;
}
public List<String> getRoRule() {
return roRule;
}
public void setRoRule(List<String> roRule) {
this.roRule = roRule;
}
public List<String> getSuperuser() {
return superuser;
}
public void setSuperuser(List<String> superuser) {
this.superuser = superuser;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ExportRule {\n");
sb.append(" anonymousUser: ").append(toIndentedString(anonymousUser)).append("\n");
sb.append(" clients: ").append(toIndentedString(clients)).append("\n");
sb.append(" index: ").append(toIndentedString(index)).append("\n");
sb.append(" protocols: ").append(toIndentedString(protocols)).append("\n");
sb.append("}");
return sb.toString();
}
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@ -0,0 +1,297 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import java.time.OffsetDateTime;
import java.util.Objects;
/**
* Information about a single file.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileInfo {
@JsonProperty("bytes_used")
private Long bytesUsed = null;
@JsonProperty("creation_time")
private OffsetDateTime creationTime = null;
@JsonProperty("fill_enabled")
private Boolean fillEnabled = null;
@JsonProperty("is_empty")
private Boolean isEmpty = null;
@JsonProperty("is_snapshot")
private Boolean isSnapshot = null;
@JsonProperty("is_vm_aligned")
private Boolean isVmAligned = null;
@JsonProperty("modified_time")
private OffsetDateTime modifiedTime = null;
@JsonProperty("name")
private String name = null;
@JsonProperty("overwrite_enabled")
private Boolean overwriteEnabled = null;
@JsonProperty("path")
private String path = null;
@JsonProperty("size")
private Long size = null;
@JsonProperty("target")
private String target = null;
/**
* Type of the file.
*/
public enum TypeEnum {
FILE("file"),
DIRECTORY("directory");
private String value;
TypeEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
@JsonCreator
public static TypeEnum fromValue(String value) {
for (TypeEnum b : TypeEnum.values()) {
if (b.value.equals(value)) {
return b;
}
}
return null;
}
}
@JsonProperty("type")
private TypeEnum type = null;
@JsonProperty("unique_bytes")
private Long uniqueBytes = null;
@JsonProperty("unix_permissions")
private Integer unixPermissions = null;
/**
* The actual number of bytes used on disk by this file. If byte_offset and length parameters are specified, this will return the bytes used by the file within the given range.
* @return bytesUsed
**/
public Long getBytesUsed() {
return bytesUsed;
}
public OffsetDateTime getCreationTime() {
return creationTime;
}
public FileInfo fillEnabled(Boolean fillEnabled) {
this.fillEnabled = fillEnabled;
return this;
}
public Boolean isFillEnabled() {
return fillEnabled;
}
public void setFillEnabled(Boolean fillEnabled) {
this.fillEnabled = fillEnabled;
}
public Boolean isIsEmpty() {
return isEmpty;
}
public void setIsEmpty(Boolean isEmpty) {
this.isEmpty = isEmpty;
}
public Boolean isIsSnapshot() {
return isSnapshot;
}
public void setIsSnapshot(Boolean isSnapshot) {
this.isSnapshot = isSnapshot;
}
public Boolean isIsVmAligned() {
return isVmAligned;
}
public OffsetDateTime getModifiedTime() {
return modifiedTime;
}
public FileInfo name(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public FileInfo overwriteEnabled(Boolean overwriteEnabled) {
this.overwriteEnabled = overwriteEnabled;
return this;
}
public Boolean isOverwriteEnabled() {
return overwriteEnabled;
}
public void setOverwriteEnabled(Boolean overwriteEnabled) {
this.overwriteEnabled = overwriteEnabled;
}
public FileInfo path(String path) {
this.path = path;
return this;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public FileInfo target(String target) {
this.target = target;
return this;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public FileInfo type(TypeEnum type) {
this.type = type;
return this;
}
public TypeEnum getType() {
return type;
}
public void setType(TypeEnum type) {
this.type = type;
}
public Long getUniqueBytes() {
return uniqueBytes;
}
public FileInfo unixPermissions(Integer unixPermissions) {
this.unixPermissions = unixPermissions;
return this;
}
public Integer getUnixPermissions() {
return unixPermissions;
}
public void setUnixPermissions(Integer unixPermissions) {
this.unixPermissions = unixPermissions;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FileInfo fileInfo = (FileInfo) o;
return Objects.equals(this.name, fileInfo.name) &&
Objects.equals(this.path, fileInfo.path);
}
@Override
public int hashCode() {
return Objects.hash(name, path);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class FileInfo {\n");
sb.append(" bytesUsed: ").append(toIndentedString(bytesUsed)).append("\n");
sb.append(" creationTime: ").append(toIndentedString(creationTime)).append("\n");
sb.append(" fillEnabled: ").append(toIndentedString(fillEnabled)).append("\n");
sb.append(" isEmpty: ").append(toIndentedString(isEmpty)).append("\n");
sb.append(" isSnapshot: ").append(toIndentedString(isSnapshot)).append("\n");
sb.append(" isVmAligned: ").append(toIndentedString(isVmAligned)).append("\n");
sb.append(" modifiedTime: ").append(toIndentedString(modifiedTime)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" overwriteEnabled: ").append(toIndentedString(overwriteEnabled)).append("\n");
sb.append(" path: ").append(toIndentedString(path)).append("\n");
sb.append(" size: ").append(toIndentedString(size)).append("\n");
sb.append(" target: ").append(toIndentedString(target)).append("\n");
sb.append(" type: ").append(toIndentedString(type)).append("\n");
sb.append(" uniqueBytes: ").append(toIndentedString(uniqueBytes)).append("\n");
sb.append(" unixPermissions: ").append(toIndentedString(unixPermissions)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@ -0,0 +1,257 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Igroup {
@JsonProperty("delete_on_unmap")
private Boolean deleteOnUnmap = null;
@JsonProperty("initiators")
private List<Initiator> initiators = null;
@JsonProperty("lun_maps")
private List<LunMap> lunMaps = null;
@JsonProperty("os_type")
private OsTypeEnum osType = null;
@JsonProperty("parent_igroups")
private List<Igroup> parentIgroups = null;
@JsonProperty("igroups")
private List<Igroup> igroups = null;
@JsonProperty("name")
private String name = null;
@JsonProperty("protocol")
private ProtocolEnum protocol = null;
@JsonProperty("svm")
private Svm svm = null;
@JsonProperty("uuid")
private String uuid = null;
public enum OsTypeEnum {
HyperV("hyper_v"),
Linux("linux"),
VMware("vmware"),
Windows("windows"),
Xen("xen");
private String value;
OsTypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
public static OsTypeEnum fromValue(String text) {
for (OsTypeEnum b : OsTypeEnum.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
}
public List<Igroup> getParentIgroups() {
return parentIgroups;
}
public void setParentIgroups(List<Igroup> parentIgroups) {
this.parentIgroups = parentIgroups;
}
public Igroup igroups(List<Igroup> igroups) {
this.igroups = igroups;
return this;
}
public List<Igroup> getIgroups() {
return igroups;
}
public void setIgroups(List<Igroup> igroups) {
this.igroups = igroups;
}
public enum ProtocolEnum {
iscsi("iscsi"),
mixed("mixed");
private String value;
ProtocolEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
public static ProtocolEnum fromValue(String text) {
for (ProtocolEnum b : ProtocolEnum.values()) {
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
return null;
}
}
public Igroup deleteOnUnmap(Boolean deleteOnUnmap) {
this.deleteOnUnmap = deleteOnUnmap;
return this;
}
public Boolean isDeleteOnUnmap() {
return deleteOnUnmap;
}
public void setDeleteOnUnmap(Boolean deleteOnUnmap) {
this.deleteOnUnmap = deleteOnUnmap;
}
public Igroup initiators(List<Initiator> initiators) {
this.initiators = initiators;
return this;
}
public List<Initiator> getInitiators() {
return initiators;
}
public void setInitiators(List<Initiator> initiators) {
this.initiators = initiators;
}
public Igroup lunMaps(List<LunMap> lunMaps) {
this.lunMaps = lunMaps;
return this;
}
public List<LunMap> getLunMaps() {
return lunMaps;
}
public void setLunMaps(List<LunMap> lunMaps) {
this.lunMaps = lunMaps;
}
public Igroup name(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Igroup osType(OsTypeEnum osType) {
this.osType = osType;
return this;
}
public OsTypeEnum getOsType() {
return osType;
}
public void setOsType(OsTypeEnum osType) {
this.osType = osType;
}
public Igroup protocol(ProtocolEnum protocol) {
this.protocol = protocol;
return this;
}
public ProtocolEnum getProtocol() {
return protocol;
}
public void setProtocol(ProtocolEnum protocol) {
this.protocol = protocol;
}
public Igroup svm(Svm svm) {
this.svm = svm;
return this;
}
public Svm getSvm() {
return svm;
}
public void setSvm(Svm svm) {
this.svm = svm;
}
public String getUuid() {
return uuid;
}
@Override
public int hashCode() {
return Objects.hash(name, uuid);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Igroup other = (Igroup) obj;
return Objects.equals(name, other.name) && Objects.equals(uuid, other.uuid);
}
@Override
public String toString() {
return "Igroup [deleteOnUnmap=" + deleteOnUnmap + ", initiators=" + initiators + ", lunMaps=" + lunMaps
+ ", name=" + name + ", replication=" + ", osType=" + osType + ", parentIgroups="
+ parentIgroups + ", igroups=" + igroups + ", protocol=" + protocol + ", svm=" + svm + ", uuid=" + uuid
+ ", portset=" + "]";
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Initiator {
@JsonProperty("name")
private String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,155 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IpInterface {
@JsonProperty("uuid")
private String uuid;
@JsonProperty("name")
private String name;
@JsonProperty("ip")
private IpInfo ip;
@JsonProperty("svm")
private Svm svm;
@JsonProperty("services")
private List<String> services;
// Getters and setters
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public IpInfo getIp() {
return ip;
}
public void setIp(IpInfo ip) {
this.ip = ip;
}
public Svm getSvm() {
return svm;
}
public void setSvm(Svm svm) {
this.svm = svm;
}
public List<String> getServices() {
return services;
}
public void setServices(List<String> services) {
this.services = services;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
IpInterface that = (IpInterface) o;
return Objects.equals(uuid, that.uuid) &&
Objects.equals(name, that.name) &&
Objects.equals(ip, that.ip) &&
Objects.equals(svm, that.svm) &&
Objects.equals(services, that.services);
}
@Override
public int hashCode() {
return Objects.hash(uuid, name, ip, svm, services);
}
@Override
public String toString() {
return "IpInterface{" +
"uuid='" + uuid + '\'' +
", name='" + name + '\'' +
", ip=" + ip +
", svm=" + svm +
", services=" + services +
'}';
}
// Nested class for IP information
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class IpInfo {
@JsonProperty("address")
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IpInfo ipInfo = (IpInfo) o;
return Objects.equals(address, ipInfo.address);
}
@Override
public int hashCode() {
return Objects.hash(address);
}
@Override
public String toString() {
return "IpInfo{" +
"address='" + address + '\'' +
'}';
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* An iSCSI service defines the properties of the iSCSI target for an SVM.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IscsiService {
@JsonProperty("enabled")
private Boolean enabled = null;
@JsonProperty("svm")
private Svm svm = null;
@JsonProperty("target")
private IscsiServiceTarget target = null;
public Boolean isEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public Svm getSvm() {
return svm;
}
public void setSvm(Svm svm) {
this.svm = svm;
}
public IscsiServiceTarget getTarget() {
return target;
}
public void setTarget(IscsiServiceTarget target) {
this.target = target;
}
@Override
public String toString() {
return "IscsiService{" +
"enabled=" + enabled +
", svm=" + svm +
", target=" + target +
'}';
}
/**
* iSCSI target information
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class IscsiServiceTarget {
@JsonProperty("alias")
private String alias = null;
@JsonProperty("name")
private String name = null;
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "IscsiServiceTarget{" +
"alias='" + alias + '\'' +
", name='" + name + '\'' +
'}';
}
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author Administrator
*
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class Job {
@JsonProperty("uuid")
String uuid;
@JsonProperty("description")
String description;
@JsonProperty("state")
String state;
@JsonProperty("message")
String message;
@JsonProperty("code")
String code;
@JsonProperty("_links")
private Links links;
@JsonProperty("error")
private JobError error;
public JobError getError () { return error; }
public void setError (JobError error) { this.error = error; }
public Links getLinks() { return links; }
public void setLinks(Links links) { this.links = links; }
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "JobDTO [uuid=" + uuid + ", description=" + description + ", state=" + state + ", message="
+ message + ", code=" + code + "]";
}
public static class Links {
@JsonProperty("self")
private Self self;
public Self getSelf() { return self; }
public void setSelf(Self self) { this.self = self; }
}
public static class Self {
@JsonProperty("href")
private String href;
public String getHref() { return href; }
public void setHref(String href) { this.href = href; }
}
public static class JobError {
@JsonProperty("message")
String errorMesssage;
@JsonProperty("code")
String code;
public String getErrorMesssage () { return errorMesssage; }
public void setErrorMesssage (String errorMesssage) { this.errorMesssage = errorMesssage; }
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "JobError [errorMesssage=" + errorMesssage + ", code=" + code + "]";
}
}
}

View File

@ -0,0 +1,341 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.List;
import java.util.Objects;
/**
* A LUN is the logical representation of storage in a storage area network (SAN).&lt;br/&gt; In ONTAP, a LUN is located within a volume. Optionally, it can be located within a qtree in a volume.&lt;br/&gt; A LUN can be created to a specified size using thin or thick provisioning. A LUN can then be renamed, resized, cloned, and moved to a different volume. LUNs support the assignment of a quality of service (QoS) policy for performance management or a QoS policy can be assigned to the volume containing the LUN. See the LUN object model to learn more about each of the properties supported by the LUN REST API.&lt;br/&gt; A LUN must be mapped to an initiator group to grant access to the initiator group&#39;s initiators (client hosts). Initiators can then access the LUN and perform I/O over a Fibre Channel (FC) fabric using the Fibre Channel Protocol or a TCP/IP network using iSCSI.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Lun {
@JsonProperty("auto_delete")
private Boolean autoDelete = null;
/**
* The class of LUN.&lt;br/&gt; Optional in POST.
*/
public enum PropertyClassEnum {
REGULAR("regular");
private String value;
PropertyClassEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
@JsonCreator
public static PropertyClassEnum fromValue(String value) {
for (PropertyClassEnum b : PropertyClassEnum.values()) {
if (b.value.equals(value)) {
return b;
}
}
return null;
}
}
@JsonProperty("class")
private PropertyClassEnum propertyClass = null;
@JsonProperty("enabled")
private Boolean enabled = null;
@JsonProperty("lun_maps")
private List<LunMap> lunMaps = null;
@JsonProperty("name")
private String name = null;
@JsonProperty("clone")
private Clone clone = null;
/**
* The operating system type of the LUN.&lt;br/&gt; Required in POST when creating a LUN that is not a clone of another. Disallowed in POST when creating a LUN clone.
*/
public enum OsTypeEnum {
HYPER_V("hyper_v"),
LINUX("linux"),
VMWARE("vmware"),
WINDOWS("windows"),
XEN("xen");
private String value;
OsTypeEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
@Override
public String toString() {
return String.valueOf(value);
}
@JsonCreator
public static OsTypeEnum fromValue(String value) {
for (OsTypeEnum b : OsTypeEnum.values()) {
if (b.value.equals(value)) {
return b;
}
}
return null;
}
}
@JsonProperty("os_type")
private OsTypeEnum osType = null;
@JsonProperty("serial_number")
private String serialNumber = null;
@JsonProperty("space")
private LunSpace space = null;
@JsonProperty("svm")
private Svm svm = null;
@JsonProperty("uuid")
private String uuid = null;
public Lun autoDelete(Boolean autoDelete) {
this.autoDelete = autoDelete;
return this;
}
public Boolean isAutoDelete() {
return autoDelete;
}
public void setAutoDelete(Boolean autoDelete) {
this.autoDelete = autoDelete;
}
public Lun propertyClass(PropertyClassEnum propertyClass) {
this.propertyClass = propertyClass;
return this;
}
public PropertyClassEnum getPropertyClass() {
return propertyClass;
}
public void setPropertyClass(PropertyClassEnum propertyClass) {
this.propertyClass = propertyClass;
}
public Lun enabled(Boolean enabled) {
this.enabled = enabled;
return this;
}
public Boolean isEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public List<LunMap> getLunMaps() {
return lunMaps;
}
public void setLunMaps(List<LunMap> lunMaps) {
this.lunMaps = lunMaps;
}
public Lun name(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Lun osType(OsTypeEnum osType) {
this.osType = osType;
return this;
}
public OsTypeEnum getOsType() {
return osType;
}
public void setOsType(OsTypeEnum osType) {
this.osType = osType;
}
public String getSerialNumber() {
return serialNumber;
}
public Lun space(LunSpace space) {
this.space = space;
return this;
}
public LunSpace getSpace() {
return space;
}
public void setSpace(LunSpace space) {
this.space = space;
}
public Lun svm(Svm svm) {
this.svm = svm;
return this;
}
public Svm getSvm() {
return svm;
}
public void setSvm(Svm svm) {
this.svm = svm;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public Clone getClone() {
return clone;
}
public void setClone(Clone clone) {
this.clone = clone;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Lun lun = (Lun) o;
return Objects.equals(this.name, lun.name) && Objects.equals(this.uuid, lun.uuid);
}
@Override
public int hashCode() {
return Objects.hash(name, uuid);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Lun {\n");
sb.append(" autoDelete: ").append(toIndentedString(autoDelete)).append("\n");
sb.append(" propertyClass: ").append(toIndentedString(propertyClass)).append("\n");
sb.append(" enabled: ").append(toIndentedString(enabled)).append("\n");
sb.append(" lunMaps: ").append(toIndentedString(lunMaps)).append("\n");
sb.append(" name: ").append(toIndentedString(name)).append("\n");
sb.append(" osType: ").append(toIndentedString(osType)).append("\n");
sb.append(" serialNumber: ").append(toIndentedString(serialNumber)).append("\n");
sb.append(" space: ").append(toIndentedString(space)).append("\n");
sb.append(" svm: ").append(toIndentedString(svm)).append("\n");
sb.append(" uuid: ").append(toIndentedString(uuid)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
public static class Clone {
@JsonProperty("source")
private Source source = null;
public Source getSource() {
return source;
}
public void setSource(Source source) {
this.source = source;
}
}
public static class Source {
@JsonProperty("name")
private String name = null;
@JsonProperty("uuid")
private String uuid = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.annotations.SerializedName;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LunMap {
@JsonProperty("igroup")
private Igroup igroup = null;
@JsonProperty("logical_unit_number")
private Integer logicalUnitNumber = null;
@JsonProperty("lun")
private Lun lun = null;
@JsonProperty("svm")
@SerializedName("svm")
private Svm svm = null;
public LunMap igroup (Igroup igroup) {
this.igroup = igroup;
return this;
}
public Igroup getIgroup () {
return igroup;
}
public void setIgroup (Igroup igroup) {
this.igroup = igroup;
}
public LunMap logicalUnitNumber (Integer logicalUnitNumber) {
this.logicalUnitNumber = logicalUnitNumber;
return this;
}
public Integer getLogicalUnitNumber () {
return logicalUnitNumber;
}
public void setLogicalUnitNumber (Integer logicalUnitNumber) {
this.logicalUnitNumber = logicalUnitNumber;
}
public LunMap lun (Lun lun) {
this.lun = lun;
return this;
}
public Lun getLun () {
return lun;
}
public void setLun (Lun lun) {
this.lun = lun;
}
public LunMap svm (Svm svm) {
this.svm = svm;
return this;
}
public Svm getSvm () {
return svm;
}
public void setSvm (Svm svm) {
this.svm = svm;
}
@Override
public String toString () {
StringBuilder sb = new StringBuilder();
sb.append("class LunMap {\n");
sb.append(" igroup: ").append(toIndentedString(igroup)).append("\n");
sb.append(" logicalUnitNumber: ").append(toIndentedString(logicalUnitNumber)).append("\n");
sb.append(" lun: ").append(toIndentedString(lun)).append("\n");
sb.append(" svm: ").append(toIndentedString(svm)).append("\n");
sb.append("}");
return sb.toString();
}
private String toIndentedString (Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* The storage space related properties of the LUN.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LunSpace {
@JsonProperty("scsi_thin_provisioning_support_enabled")
private Boolean scsiThinProvisioningSupportEnabled = null;
@JsonProperty("size")
private Long size = null;
@JsonProperty("used")
private Long used = null;
@JsonProperty("physical_used")
private Long physicalUsed = null;
public LunSpace scsiThinProvisioningSupportEnabled(Boolean scsiThinProvisioningSupportEnabled) {
this.scsiThinProvisioningSupportEnabled = scsiThinProvisioningSupportEnabled;
return this;
}
public Boolean isScsiThinProvisioningSupportEnabled() {
return scsiThinProvisioningSupportEnabled;
}
public void setScsiThinProvisioningSupportEnabled(Boolean scsiThinProvisioningSupportEnabled) {
this.scsiThinProvisioningSupportEnabled = scsiThinProvisioningSupportEnabled;
}
public LunSpace size(Long size) {
this.size = size;
return this;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public Long getUsed() {
return used;
}
public Long getPhysicalUsed() {
return physicalUsed;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class LunSpace {\n");
sb.append(" scsiThinProvisioningSupportEnabled: ").append(toIndentedString(scsiThinProvisioningSupportEnabled)).append("\n");
sb.append(" size: ").append(toIndentedString(size)).append("\n");
sb.append(" used: ").append(toIndentedString(used)).append("\n");
sb.append(" physicalUsed: ").append(toIndentedString(physicalUsed)).append("\n");
sb.append("}");
return sb.toString();
}
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@ -0,0 +1,50 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Nas {
@JsonProperty("path")
private String path;
@JsonProperty("export_policy")
private ExportPolicy exportPolicy;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public ExportPolicy getExportPolicy() {
return exportPolicy;
}
public void setExportPolicy(ExportPolicy exportPolicy) {
this.exportPolicy = exportPolicy;
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.feign.model;
import org.apache.cloudstack.storage.service.model.ProtocolType;
public class OntapStorage {
private final String username;
private final String password;
private final String managementLIF;
private final String svmName;
private final Long size;
private final ProtocolType protocolType;
private final Boolean isDisaggregated;
public OntapStorage(String username, String password, String managementLIF, String svmName, Long size, ProtocolType protocolType, Boolean isDisaggregated) {
this.username = username;
this.password = password;
this.managementLIF = managementLIF;
this.svmName = svmName;
this.size = size;
this.protocolType = protocolType;
this.isDisaggregated = isDisaggregated;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getManagementLIF() {
return managementLIF;
}
public String getSvmName() {
return svmName;
}
public Long getSize() {
return size;
}
public ProtocolType getProtocol() {
return protocolType;
}
public Boolean getIsDisaggregated() {
return isDisaggregated;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Policy {
private int minThroughputIops;
private int minThroughputMbps;
private int maxThroughputIops;
private int maxThroughputMbps;
private String uuid;
private String name;
public int getMinThroughputIops() { return minThroughputIops; }
public void setMinThroughputIops(int minThroughputIops) { this.minThroughputIops = minThroughputIops; }
public int getMinThroughputMbps() { return minThroughputMbps; }
public void setMinThroughputMbps(int minThroughputMbps) { this.minThroughputMbps = minThroughputMbps; }
public int getMaxThroughputIops() { return maxThroughputIops; }
public void setMaxThroughputIops(int maxThroughputIops) { this.maxThroughputIops = maxThroughputIops; }
public int getMaxThroughputMbps() { return maxThroughputMbps; }
public void setMaxThroughputMbps(int maxThroughputMbps) { this.maxThroughputMbps = maxThroughputMbps; }
public String getUuid() { return uuid; }
public void setUuid(String uuid) { this.uuid = uuid; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Policy policy = (Policy) o;
return Objects.equals(getUuid(), policy.getUuid());
}
@Override
public int hashCode() {
return Objects.hashCode(getUuid());
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Qos {
@JsonProperty("policy")
private Policy policy;
public Policy getPolicy() {
return policy;
}
public void setPolicy(Policy policy) {
this.policy = policy;
}
}

View File

@ -0,0 +1,146 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Svm {
@JsonProperty("uuid")
private String uuid = null;
@JsonProperty("name")
private String name = null;
@JsonProperty("iscsi.enabled")
private Boolean iscsiEnabled = null;
@JsonProperty("fcp.enabled")
private Boolean fcpEnabled = null;
@JsonProperty("nfs.enabled")
private Boolean nfsEnabled = null;
@JsonProperty("aggregates")
private List<Aggregate> aggregates = null;
@JsonProperty("aggregates_delegated")
private Boolean aggregatesDelegated = null;
@JsonProperty("state")
private String state = null;
@JsonIgnore
private Links links = null;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getIscsiEnabled() {
return iscsiEnabled;
}
public void setIscsiEnabled(Boolean iscsiEnabled) {
this.iscsiEnabled = iscsiEnabled;
}
public Boolean getFcpEnabled() {
return fcpEnabled;
}
public void setFcpEnabled(Boolean fcpEnabled) {
this.fcpEnabled = fcpEnabled;
}
public Boolean getNfsEnabled() {
return nfsEnabled;
}
public void setNfsEnabled(Boolean nfsEnabled) {
this.nfsEnabled = nfsEnabled;
}
public List<Aggregate> getAggregates() {
return aggregates;
}
public void setAggregates(List<Aggregate> aggregates) {
this.aggregates = aggregates;
}
public Boolean getAggregatesDelegated() {
return aggregatesDelegated;
}
public void setAggregatesDelegated(Boolean aggregatesDelegated) {
this.aggregatesDelegated = aggregatesDelegated;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Links getLinks() {
return links;
}
public void setLinks(Links links) {
this.links = links;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Svm svm = (Svm) o;
return Objects.equals(getUuid(), svm.getUuid());
}
@Override
public int hashCode() {
return Objects.hashCode(getUuid());
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Links { }
}

View File

@ -0,0 +1,108 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Objects;
/**
* This returns the cluster version information. When the cluster has more than one node, the cluster version is equivalent to the lowest of generation, major, and minor versions on all nodes.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Version {
@JsonProperty("full")
private String full = null;
@JsonProperty("generation")
private Integer generation = null;
@JsonProperty("major")
private Integer major = null;
@JsonProperty("minor")
private Integer minor = null;
public String getFull() {
return full;
}
public Integer getGeneration() {
return generation;
}
public Integer getMajor() {
return major;
}
public Integer getMinor() {
return minor;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Version clusterVersion = (Version) o;
return Objects.equals(this.full, clusterVersion.full) &&
Objects.equals(this.generation, clusterVersion.generation) &&
Objects.equals(this.major, clusterVersion.major) &&
Objects.equals(this.minor, clusterVersion.minor);
}
@Override
public int hashCode() {
return Objects.hash(full, generation, major, minor);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ClusterVersion {\n");
sb.append(" full: ").append(toIndentedString(full)).append("\n");
sb.append(" generation: ").append(toIndentedString(generation)).append("\n");
sb.append(" major: ").append(toIndentedString(major)).append("\n");
sb.append(" minor: ").append(toIndentedString(minor)).append("\n");
sb.append("}");
return sb.toString();
}
/**
* Convert the given object to string with each line indented by 4 spaces
* (except the first line).
*/
private String toIndentedString(Object o) {
if (o == null) {
return "null";
}
return o.toString().replace("\n", "\n ");
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Volume {
@JsonProperty("uuid")
private String uuid;
@JsonProperty("name")
private String name;
@JsonProperty("state")
private String state;
@JsonProperty("nas")
private Nas nas;
@JsonProperty("svm")
private Svm svm;
@JsonProperty("qos")
private Qos qos;
@JsonProperty("space")
private VolumeSpace space;
@JsonProperty("anti_ransomware")
private AntiRansomware antiRansomware;
@JsonProperty("aggregates")
private List<Aggregate> aggregates = null;
@JsonProperty("size")
private Long size = null;
// Getters and setters
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Nas getNas() {
return nas;
}
public void setNas(Nas nas) {
this.nas = nas;
}
public Svm getSvm() {
return svm;
}
public void setSvm(Svm svm) {
this.svm = svm;
}
public Qos getQos() {
return qos;
}
public void setQos(Qos qos) {
this.qos = qos;
}
public VolumeSpace getSpace() {
return space;
}
public void setSpace(VolumeSpace space) {
this.space = space;
}
public AntiRansomware getAntiRansomware() {
return antiRansomware;
}
public void setAntiRansomware(AntiRansomware antiRansomware) {
this.antiRansomware = antiRansomware;
}
public List<Aggregate> getAggregates () { return aggregates; }
public void setAggregates (List<Aggregate> aggregates) { this.aggregates = aggregates; }
public Long getSize () { return size; }
public void setSize (Long size) { this.size = size; }
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Volume volume = (Volume) o;
return Objects.equals(uuid, volume.uuid);
}
@Override
public int hashCode() {
return Objects.hashCode(uuid);
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VolumeQosPolicy {
@JsonProperty("max_throughput_iops")
private Integer maxThroughputIops = null;
@JsonProperty("max_throughput_mbps")
private Integer maxThroughputMbps = null;
@JsonProperty("min_throughput_iops")
private Integer minThroughputIops = null;
@JsonProperty("name")
private String name = null;
@JsonProperty("uuid")
private String uuid = null;
public Integer getMaxThroughputIops() {
return maxThroughputIops;
}
public void setMaxThroughputIops(Integer maxThroughputIops) {
this.maxThroughputIops = maxThroughputIops;
}
public Integer getMaxThroughputMbps() {
return maxThroughputMbps;
}
public void setMaxThroughputMbps(Integer maxThroughputMbps) {
this.maxThroughputMbps = maxThroughputMbps;
}
public Integer getMinThroughputIops() {
return minThroughputIops;
}
public void setMinThroughputIops(Integer minThroughputIops) {
this.minThroughputIops = minThroughputIops;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VolumeSpace {
@JsonProperty("size")
private long size;
@JsonProperty("available")
private long available;
@JsonProperty("used")
private long used;
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public long getAvailable() {
return available;
}
public void setAvailable(long available) {
this.available = available;
}
public long getUsed() {
return used;
}
public void setUsed(long used) {
this.used = used;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.feign.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VolumeSpaceLogicalSpace {
@JsonProperty("available")
private Long available = null;
@JsonProperty("used")
private Double used = null;
public Long getAvailable() {
return available;
}
public void setAvailable(Long available) {
this.available = available;
}
public Double getUsed() {
return used;
}
public void setUsed(Double used) {
this.used = used;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.feign.model.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.cloudstack.storage.feign.model.Job;
public class JobResponse {
@JsonProperty("job")
private Job job;
public Job getJob() { return job; }
public void setJob(Job job) { this.job = job; }
}

View File

@ -0,0 +1,64 @@
/*
* 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.feign.model.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
/**
* OntapResponse
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OntapResponse<T> {
@JsonProperty("num_records")
private Integer numRecords;
@JsonProperty("records")
private List<T> records;
public OntapResponse () {
// Default constructor
}
public OntapResponse (List<T> records) {
this.records = records;
this.numRecords = (records != null) ? records.size() : 0;
}
public Integer getNumRecords() {
return numRecords;
}
public void setNumRecords(Integer numRecords) {
this.numRecords = numRecords;
}
public List<T> getRecords() {
return records;
}
public void setRecords(List<T> records) {
this.records = records;
this.numRecords = (records != null) ? records.size() : 0;
}
}

View File

@ -0,0 +1,536 @@
/*
* 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.lifecycle;
import com.cloud.agent.api.StoragePoolInfo;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.resource.ResourceManager;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.StoragePoolAutomation;
import com.cloud.utils.StringUtils;
import com.cloud.utils.exception.CloudRuntimeException;
import com.google.common.base.Preconditions;
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.lifecycle.BasePrimaryDataStoreLifeCycleImpl;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
import org.apache.cloudstack.storage.feign.model.Volume;
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
import org.apache.cloudstack.storage.service.StorageStrategy;
import org.apache.cloudstack.storage.service.model.AccessGroup;
import org.apache.cloudstack.storage.service.model.ProtocolType;
import org.apache.cloudstack.storage.utils.OntapStorageConstants;
import org.apache.cloudstack.storage.utils.OntapStorageUtils;
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
@Inject private ClusterDao _clusterDao;
@Inject private StorageManager _storageMgr;
@Inject private ResourceManager _resourceMgr;
@Inject private PrimaryDataStoreHelper _dataStoreHelper;
@Inject private PrimaryDataStoreDetailsDao _datastoreDetailsDao;
@Inject private StoragePoolAutomation _storagePoolAutomation;
@Inject private PrimaryDataStoreDao storagePoolDao;
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
private static final Logger logger = LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class);
private static final long ONTAP_MIN_VOLUME_SIZE_IN_BYTES = 1677721600L;
@Override
public DataStore initialize(Map<String, Object> dsInfos) {
if (dsInfos == null) {
throw new CloudRuntimeException("Datastore info map is null, cannot create primary storage");
}
String url = (String) dsInfos.get("url");
Long zoneId = (Long) dsInfos.get("zoneId");
Long podId = (Long) dsInfos.get("podId");
Long clusterId = (Long) dsInfos.get("clusterId");
String storagePoolName = (String) dsInfos.get("name");
String providerName = (String) dsInfos.get("providerName");
Long capacityBytes = (Long) dsInfos.get("capacityBytes");
boolean managed = (boolean) dsInfos.get("managed");
String tags = (String) dsInfos.get("tags");
Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName +
", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId);
logger.debug("Received capacityBytes from UI: " + capacityBytes);
@SuppressWarnings("unchecked")
Map<String, String> details = (Map<String, String>) dsInfos.get("details");
capacityBytes = validateInitializeInputs(capacityBytes, podId, clusterId, zoneId, storagePoolName, providerName, managed, url, details);
PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
if (clusterId != null) {
ClusterVO clusterVO = _clusterDao.findById(clusterId);
Preconditions.checkNotNull(clusterVO, "Unable to locate the specified cluster");
if (clusterVO.getHypervisorType() != Hypervisor.HypervisorType.KVM) {
throw new CloudRuntimeException("ONTAP primary storage is supported only for KVM hypervisor");
}
parameters.setHypervisorType(clusterVO.getHypervisorType());
}
details.put(OntapStorageConstants.SIZE, capacityBytes.toString());
details.putIfAbsent(OntapStorageConstants.IS_DISAGGREGATED, "false");
ProtocolType protocol = ProtocolType.valueOf(details.get(OntapStorageConstants.PROTOCOL));
// long volumeSize = Long.parseLong(details.get(OntapStorageConstants.SIZE));
OntapStorage ontapStorage = new OntapStorage(
details.get(OntapStorageConstants.USERNAME),
details.get(OntapStorageConstants.PASSWORD),
details.get(OntapStorageConstants.MANAGEMENT_LIF),
details.get(OntapStorageConstants.SVM_NAME),
capacityBytes,
protocol,
Boolean.parseBoolean(details.get(OntapStorageConstants.IS_DISAGGREGATED).toLowerCase()));
StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage);
boolean isValid = storageStrategy.connect();
if (isValid) {
String dataLIF = storageStrategy.getNetworkInterface();
if (dataLIF == null || dataLIF.isEmpty()) {
throw new CloudRuntimeException("Failed to retrieve Data LIF from ONTAP, cannot create primary storage");
}
logger.info("Using Data LIF for storage access: " + dataLIF);
details.put(OntapStorageConstants.DATA_LIF, dataLIF);
logger.info("Creating ONTAP volume '" + storagePoolName + "' with size: " + capacityBytes + " bytes (" +
(capacityBytes / (1024 * 1024 * 1024)) + " GB)");
try {
Volume volume = storageStrategy.createStorageVolume(storagePoolName, capacityBytes);
if (volume == null) {
logger.error("createStorageVolume returned null for volume: " + storagePoolName);
throw new CloudRuntimeException("Failed to create ONTAP volume: " + storagePoolName);
}
logger.info("Volume object retrieved successfully. UUID: " + volume.getUuid() + ", Name: " + volume.getName());
details.putIfAbsent(OntapStorageConstants.VOLUME_UUID, volume.getUuid());
details.putIfAbsent(OntapStorageConstants.VOLUME_NAME, volume.getName());
} catch (Exception e) {
logger.error("Exception occurred while creating ONTAP volume: " + storagePoolName, e);
throw new CloudRuntimeException("Failed to create ONTAP volume: " + storagePoolName + ". Error: " + e.getMessage(), e);
}
} else {
throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage");
}
String path;
int port;
switch (protocol) {
case NFS3:
parameters.setType(Storage.StoragePoolType.NetworkFilesystem);
path = OntapStorageConstants.SLASH + storagePoolName;
port = OntapStorageConstants.NFS3_PORT;
logger.info("Setting NFS path for storage pool: " + path + ", port: " + port);
break;
case ISCSI:
parameters.setType(Storage.StoragePoolType.Iscsi);
path = storageStrategy.getStoragePath();
port = OntapStorageConstants.ISCSI_PORT;
logger.info("Setting iSCSI path for storage pool: " + path + ", port: " + port);
break;
default:
throw new CloudRuntimeException("Unsupported protocol: " + protocol + ", cannot create primary storage");
}
parameters.setHost(details.get(OntapStorageConstants.DATA_LIF));
parameters.setPort(port);
parameters.setPath(path);
parameters.setTags(tags);
parameters.setIsTagARule(isTagARule);
parameters.setDetails(details);
parameters.setUuid(UUID.randomUUID().toString());
parameters.setZoneId(zoneId);
parameters.setPodId(podId);
parameters.setClusterId(clusterId);
parameters.setName(storagePoolName);
parameters.setProviderName(providerName);
parameters.setManaged(managed);
parameters.setCapacityBytes(capacityBytes);
parameters.setUsedBytes(0);
return _dataStoreHelper.createPrimaryDataStore(parameters);
}
private long validateInitializeInputs(Long capacityBytes, Long podId, Long clusterId, Long zoneId,
String storagePoolName, String providerName, boolean managed, String url, Map<String, String> details) {
// Capacity validation
if (capacityBytes == null || capacityBytes <= 0) {
logger.warn("capacityBytes not provided or invalid (" + capacityBytes + "), using ONTAP minimum size: " + ONTAP_MIN_VOLUME_SIZE_IN_BYTES);
capacityBytes = ONTAP_MIN_VOLUME_SIZE_IN_BYTES;
} else if (capacityBytes < ONTAP_MIN_VOLUME_SIZE_IN_BYTES) {
logger.warn("capacityBytes (" + capacityBytes + ") is below ONTAP minimum (" + ONTAP_MIN_VOLUME_SIZE_IN_BYTES + "), adjusting to minimum");
capacityBytes = ONTAP_MIN_VOLUME_SIZE_IN_BYTES;
}
// Scope (pod/cluster/zone) validation
if (podId == null ^ clusterId == null) {
throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage");
}
if (podId == null && clusterId == null) {
if (zoneId != null) {
logger.info("Both Pod Id and Cluster Id are null, Primary storage pool will be associated with a Zone");
} else {
throw new CloudRuntimeException("Pod Id, Cluster Id and Zone Id are all null, cannot create primary storage");
}
}
// Basic parameter validation
if (StringUtils.isBlank(storagePoolName)) {
throw new CloudRuntimeException("Storage pool name is null or empty, cannot create primary storage");
}
if (StringUtils.isBlank(providerName)) {
throw new CloudRuntimeException("Provider name is null or empty, cannot create primary storage");
}
logger.debug("ONTAP primary storage will be created as " + (managed ? "managed" : "unmanaged"));
if (!managed) {
throw new CloudRuntimeException("ONTAP primary storage must be managed");
}
// Details key validation
Set<String> requiredKeys = Set.of(
OntapStorageConstants.USERNAME,
OntapStorageConstants.PASSWORD,
OntapStorageConstants.SVM_NAME,
OntapStorageConstants.PROTOCOL,
OntapStorageConstants.MANAGEMENT_LIF
);
Set<String> optionalKeys = Set.of(
OntapStorageConstants.IS_DISAGGREGATED
);
Set<String> allowedKeys = new java.util.HashSet<>(requiredKeys);
allowedKeys.addAll(optionalKeys);
if (StringUtils.isNotBlank(url)) {
for (String segment : url.split(OntapStorageConstants.SEMICOLON)) {
if (segment.isEmpty()) {
continue;
}
String[] kv = segment.split(OntapStorageConstants.EQUALS, 2);
if (kv.length == 2) {
details.put(kv[0].trim(), kv[1].trim());
}
}
}
for (Map.Entry<String, String> e : details.entrySet()) {
String key = e.getKey();
String val = e.getValue();
if (!allowedKeys.contains(key)) {
throw new CloudRuntimeException("Unexpected ONTAP detail key in URL: " + key);
}
if (StringUtils.isBlank(val)) {
throw new CloudRuntimeException("ONTAP primary storage creation failed, empty detail: " + key);
}
}
Set<String> providedKeys = new HashSet<>(details.keySet());
if (!providedKeys.containsAll(requiredKeys)) {
Set<String> missing = new HashSet<>(requiredKeys);
missing.removeAll(providedKeys);
throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail(s): " + missing);
}
return capacityBytes;
}
@Override
public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
logger.debug("In attachCluster for ONTAP primary storage");
if (dataStore == null) {
throw new InvalidParameterValueException("attachCluster: dataStore should not be null");
}
if (scope == null) {
throw new InvalidParameterValueException("attachCluster: scope should not be null");
}
List<String> hostsIdentifier = new ArrayList<>();
StoragePoolVO storagePool = storagePoolDao.findById(dataStore.getId());
if (storagePool == null) {
logger.error("attachCluster : Storage Pool not found for id: " + dataStore.getId());
throw new CloudRuntimeException("attachCluster : Storage Pool not found for id: " + dataStore.getId());
}
PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)dataStore;
List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInClusterForStorageConnection(primaryStore);
logger.debug("attachCluster: Eligible Up and Enabled hosts: {} in cluster {}", hostsToConnect, primaryStore.getClusterId());
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(primaryStore.getId());
StorageStrategy strategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);
ProtocolType protocol = ProtocolType.valueOf(details.get(OntapStorageConstants.PROTOCOL));
if (!validateProtocolSupportAndFetchHostsIdentifier(hostsToConnect, protocol, hostsIdentifier)) {
String errMsg = "attachCluster: Not all hosts in the cluster support the protocol: " + protocol.name();
logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
logger.debug("attachCluster: Attaching the pool to each of the host in the cluster: {}", primaryStore.getClusterId());
if (hostsIdentifier != null && hostsIdentifier.size() > 0) {
try {
AccessGroup accessGroupRequest = new AccessGroup();
accessGroupRequest.setHostsToConnect(hostsToConnect);
accessGroupRequest.setScope(scope);
primaryStore.setDetails(details);
accessGroupRequest.setPrimaryDataStoreInfo(primaryStore);
strategy.createAccessGroup(accessGroupRequest);
} catch (Exception e) {
logger.error("attachCluster: Failed to create access group on storage system for cluster: " + primaryStore.getClusterId() + ". Exception: " + e.getMessage());
throw new CloudRuntimeException("attachCluster: Failed to create access group on storage system for cluster: " + primaryStore.getClusterId() + ". Exception: " + e.getMessage());
}
}
logger.debug("attachCluster: Attaching the pool to each of the host in the cluster: {}", primaryStore.getClusterId());
for (HostVO host : hostsToConnect) {
try {
_storageMgr.connectHostToSharedPool(host, dataStore.getId());
} catch (Exception e) {
logger.warn("attachCluster: Unable to establish a connection between " + host + " and " + dataStore, e);
return false;
}
}
_dataStoreHelper.attachCluster(dataStore);
return true;
}
@Override
public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) {
return false;
}
@Override
public boolean attachZone(DataStore dataStore, ZoneScope scope, Hypervisor.HypervisorType hypervisorType) {
logger.debug("In attachZone for ONTAP primary storage");
if (dataStore == null) {
throw new InvalidParameterValueException("attachZone: dataStore should not be null");
}
if (scope == null) {
throw new InvalidParameterValueException("attachZone: scope should not be null");
}
List<String> hostsIdentifier = new ArrayList<>();
StoragePoolVO storagePool = storagePoolDao.findById(dataStore.getId());
if (storagePool == null) {
logger.error("attachZone : Storage Pool not found for id: " + dataStore.getId());
throw new CloudRuntimeException("attachZone : Storage Pool not found for id: " + dataStore.getId());
}
PrimaryDataStoreInfo primaryStore = (PrimaryDataStoreInfo)dataStore;
List<HostVO> hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInZoneForStorageConnection(dataStore, scope.getScopeId(), Hypervisor.HypervisorType.KVM);
logger.debug(String.format("In createPool. Attaching the pool to each of the hosts in %s.", hostsToConnect));
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(primaryStore.getId());
StorageStrategy strategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);
logger.debug("attachZone: Eligible Up and Enabled hosts: {}", hostsToConnect);
ProtocolType protocol = ProtocolType.valueOf(details.get(OntapStorageConstants.PROTOCOL));
if (!validateProtocolSupportAndFetchHostsIdentifier(hostsToConnect, protocol, hostsIdentifier)) {
String errMsg = "attachZone: Not all hosts in the zone support the protocol: " + protocol.name();
logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
if (hostsIdentifier != null && !hostsIdentifier.isEmpty()) {
try {
AccessGroup accessGroupRequest = new AccessGroup();
accessGroupRequest.setHostsToConnect(hostsToConnect);
accessGroupRequest.setScope(scope);
primaryStore.setDetails(details);
accessGroupRequest.setPrimaryDataStoreInfo(primaryStore);
strategy.createAccessGroup(accessGroupRequest);
} catch (Exception e) {
logger.error("attachZone: Failed to create access group on storage system for zone with Exception: " + e.getMessage());
throw new CloudRuntimeException("attachZone: Failed to create access group on storage system for zone with Exception: " + e.getMessage());
}
}
for (HostVO host : hostsToConnect) {
try {
_storageMgr.connectHostToSharedPool(host, dataStore.getId());
} catch (Exception e) {
logger.warn("Unable to establish a connection between " + host + " and " + dataStore, e);
return false;
}
}
_dataStoreHelper.attachZone(dataStore);
return true;
}
private boolean validateProtocolSupportAndFetchHostsIdentifier(List<HostVO> hosts, ProtocolType protocolType, List<String> hostIdentifiers) {
switch (protocolType) {
case ISCSI:
String protocolPrefix = OntapStorageConstants.IQN;
for (HostVO host : hosts) {
if (host == null || host.getStorageUrl() == null || host.getStorageUrl().trim().isEmpty()
|| !host.getStorageUrl().startsWith(protocolPrefix)) {
return false;
}
hostIdentifiers.add(host.getStorageUrl());
}
break;
case NFS3:
String ip = "";
for (HostVO host : hosts) {
if (host != null) {
ip = host.getStorageIpAddress() != null ? host.getStorageIpAddress().trim() : "";
if (ip.isEmpty()) {
if (host.getPrivateIpAddress() == null || host.getPrivateIpAddress().trim().isEmpty()) {
return false;
}
ip = host.getPrivateIpAddress().trim();
}
}
hostIdentifiers.add(ip);
}
break;
default:
throw new CloudRuntimeException("validateProtocolSupportAndFetchHostsIdentifier : Unsupported protocol: " + protocolType.name());
}
logger.info("validateProtocolSupportAndFetchHostsIdentifier: All hosts support the protocol: " + protocolType.name());
return true;
}
@Override
public boolean maintain(DataStore store) {
logger.info("Placing storage pool {} in maintenance mode", store);
if (_storagePoolAutomation.maintain(store)) {
return _dataStoreHelper.maintain(store);
} else {
return false;
}
}
@Override
public boolean cancelMaintain(DataStore store) {
logger.info("Cancelling storage pool maintenance for {}", store);
if (_dataStoreHelper.cancelMaintain(store)) {
return _storagePoolAutomation.cancelMaintain(store);
} else {
return false;
}
}
@Override
public boolean deleteDataStore(DataStore store) {
logger.info("deleteDataStore: Starting deletion process for storage pool id: {}", store.getId());
long storagePoolId = store.getId();
StoragePool storagePool = _storageMgr.getStoragePool(storagePoolId);
if (storagePool == null) {
logger.warn("deleteDataStore: Storage pool not found for id: {}, skipping deletion", storagePoolId);
return true;
}
try {
Map<String, String> details = _datastoreDetailsDao.listDetailsKeyPairs(storagePoolId);
if (details == null || details.isEmpty()) {
logger.warn("deleteDataStore: No details found for storage pool id: {}, proceeding with CS entity deletion only", storagePoolId);
return _dataStoreHelper.deletePrimaryDataStore(store);
}
logger.info("deleteDataStore: Deleting access groups for storage pool '{}'", storagePool.getName());
StorageStrategy storageStrategy = OntapStorageUtils.getStrategyByStoragePoolDetails(details);
PrimaryDataStoreInfo primaryDataStoreInfo = (PrimaryDataStoreInfo) store;
primaryDataStoreInfo.setDetails(details);
logger.info("deleteDataStore: Deleting ONTAP volume for storage pool '{}'", storagePool.getName());
Volume volume = new Volume();
volume.setUuid(details.get(OntapStorageConstants.VOLUME_UUID));
volume.setName(details.get(OntapStorageConstants.VOLUME_NAME));
try {
if (volume.getUuid() == null || volume.getUuid().isEmpty() || volume.getName() == null || volume.getName().isEmpty()) {
logger.error("deleteDataStore: Volume UUID/Name not found in details for storage pool id: {}, cannot delete volume", storagePoolId);
throw new CloudRuntimeException("Volume UUID/Name not found in details, cannot delete ONTAP volume");
}
storageStrategy.deleteStorageVolume(volume);
logger.info("deleteDataStore: Successfully deleted ONTAP volume '{}' (UUID: {}) for storage pool '{}'",
volume.getName(), volume.getUuid(), storagePool.getName());
} catch (Exception e) {
logger.error("deleteDataStore: Exception while retrieving volume UUID for storage pool id: {}. Error: {}",
storagePoolId, e.getMessage(), e);
}
AccessGroup accessGroup = new AccessGroup();
accessGroup.setPrimaryDataStoreInfo(primaryDataStoreInfo);
storageStrategy.deleteAccessGroup(accessGroup);
logger.info("deleteDataStore: Successfully deleted access groups for storage pool '{}'", storagePool.getName());
} catch (Exception e) {
logger.error("deleteDataStore: Failed to delete access groups for storage pool id: {}. Error: {}",
storagePoolId, e.getMessage(), e);
logger.warn("deleteDataStore: Proceeding with CloudStack entity deletion despite ONTAP cleanup failure");
}
return _dataStoreHelper.deletePrimaryDataStore(store);
}
@Override
public boolean migrateToObjectStore(DataStore store) {
return false;
}
@Override
public void updateStoragePool(StoragePool storagePool, Map<String, String> details) {
}
@Override
public void enableStoragePool(DataStore store) {
_dataStoreHelper.enable(store);
}
@Override
public void disableStoragePool(DataStore store) {
_dataStoreHelper.disable(store);
}
@Override
public void changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) {
}
@Override
public void changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) {
}
}

View File

@ -0,0 +1,186 @@
/*
* 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.listener;
import javax.inject.Inject;
import com.cloud.agent.api.ModifyStoragePoolCommand;
import com.cloud.agent.api.ModifyStoragePoolAnswer;
import com.cloud.agent.api.StoragePoolInfo;
import com.cloud.alert.AlertManager;
import com.cloud.storage.StoragePoolHostVO;
import com.cloud.storage.dao.StoragePoolHostDao;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.DeleteStoragePoolCommand;
import com.cloud.host.Host;
import com.cloud.storage.StoragePool;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
import com.cloud.host.dao.HostDao;
public class OntapHostListener implements HypervisorHostListener {
protected Logger logger = LogManager.getLogger(getClass());
@Inject
private AgentManager _agentMgr;
@Inject
private AlertManager _alertMgr;
@Inject
private PrimaryDataStoreDao _storagePoolDao;
@Inject
private HostDao _hostDao;
@Inject
private StoragePoolHostDao storagePoolHostDao;
@Override
public boolean hostConnect(long hostId, long poolId) {
logger.info("Connect to host " + hostId + " from pool " + poolId);
Host host = _hostDao.findById(hostId);
if (host == null) {
logger.error("host was not found with id : {}", hostId);
return false;
}
StoragePool pool = _storagePoolDao.findById(poolId);
if (pool == null) {
logger.error("Failed to connect host - storage pool not found with id: {}", poolId);
return false;
}
logger.info("Connecting host {} to ONTAP storage pool {}", host.getName(), pool.getName());
try {
ModifyStoragePoolCommand cmd = new ModifyStoragePoolCommand(true, pool);
Answer answer = _agentMgr.easySend(hostId, cmd);
if (answer == null) {
throw new CloudRuntimeException(String.format("Unable to get an answer to the modify storage pool command (%s)", pool));
}
if (!answer.getResult()) {
String msg = String.format("Unable to attach storage pool %s to host %d", pool, hostId);
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
throw new CloudRuntimeException(String.format(
"Unable to establish a connection from agent to storage pool %s due to %s", pool, answer.getDetails()));
}
if (!(answer instanceof ModifyStoragePoolAnswer)) {
logger.error("Received unexpected answer type {} for storage pool {}", answer.getClass().getName(), pool.getName());
throw new CloudRuntimeException("Failed to connect to storage pool. Please check agent logs for details.");
}
ModifyStoragePoolAnswer mspAnswer = (ModifyStoragePoolAnswer) answer;
StoragePoolInfo poolInfo = mspAnswer.getPoolInfo();
if (poolInfo == null) {
throw new CloudRuntimeException("ModifyStoragePoolAnswer returned null poolInfo");
}
String localPath = poolInfo.getLocalPath();
logger.info("Storage pool {} successfully mounted at: {}", pool.getName(), localPath);
StoragePoolHostVO storagePoolHost = storagePoolHostDao.findByPoolHost(poolId, hostId);
if (storagePoolHost == null) {
storagePoolHost = new StoragePoolHostVO(poolId, hostId, localPath);
storagePoolHostDao.persist(storagePoolHost);
logger.info("Created storage_pool_host_ref entry for pool {} and host {}", pool.getName(), host.getName());
} else {
storagePoolHost.setLocalPath(localPath);
storagePoolHostDao.update(storagePoolHost.getId(), storagePoolHost);
logger.info("Updated storage_pool_host_ref entry with local_path: {}", localPath);
}
StoragePoolVO poolVO = _storagePoolDao.findById(poolId);
if (poolVO != null && poolInfo.getCapacityBytes() > 0) {
poolVO.setCapacityBytes(poolInfo.getCapacityBytes());
poolVO.setUsedBytes(poolInfo.getCapacityBytes() - poolInfo.getAvailableBytes());
_storagePoolDao.update(poolVO.getId(), poolVO);
logger.info("Updated storage pool capacity: {} GB, used: {} GB", poolInfo.getCapacityBytes() / (1024 * 1024 * 1024), (poolInfo.getCapacityBytes() - poolInfo.getAvailableBytes()) / (1024 * 1024 * 1024));
}
} catch (Exception e) {
logger.error("Exception while connecting host {} to storage pool {}", host.getName(), pool.getName(), e);
return false;
}
return true;
}
@Override
public boolean hostDisconnected(Host host, StoragePool pool) {
logger.info("Disconnect from host " + host.getId() + " from pool " + pool.getName());
Host hostToremove = _hostDao.findById(host.getId());
if (hostToremove == null) {
logger.error("Failed to add host by HostListener as host was not found with id : {}", host.getId());
return false;
}
logger.info("Disconnecting host {} from ONTAP storage pool {}", host.getName(), pool.getName());
try {
DeleteStoragePoolCommand cmd = new DeleteStoragePoolCommand(pool);
long hostId = host.getId();
Answer answer = _agentMgr.easySend(hostId, cmd);
if (answer != null && answer.getResult()) {
logger.info("Successfully disconnected host {} from ONTAP storage pool {}", host.getName(), pool.getName());
return true;
} else {
String errMsg = (answer != null) ? answer.getDetails() : "Unknown error";
logger.warn("Failed to disconnect host {} from storage pool {}. Error: {}", host.getName(), pool.getName(), errMsg);
return false;
}
} catch (Exception e) {
logger.error("Exception while disconnecting host {} from storage pool {}", host.getName(), pool.getName(), e);
return false;
}
}
@Override
public boolean hostDisconnected(long hostId, long poolId) {
return false;
}
@Override
public boolean hostAboutToBeRemoved(long hostId) {
return false;
}
@Override
public boolean hostRemoved(long hostId, long clusterId) {
return false;
}
@Override
public boolean hostEnabled(long hostId) {
return false;
}
@Override
public boolean hostAdded(long hostId) {
return false;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.provider;
import com.cloud.utils.component.ComponentContext;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle;
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
import org.apache.cloudstack.storage.driver.OntapPrimaryDatastoreDriver;
import org.apache.cloudstack.storage.lifecycle.OntapPrimaryDatastoreLifecycle;
import org.apache.cloudstack.storage.listener.OntapHostListener;
import org.apache.cloudstack.storage.utils.OntapStorageConstants;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@Component
public class OntapPrimaryDatastoreProvider implements PrimaryDataStoreProvider {
private static final Logger logger = LogManager.getLogger(OntapPrimaryDatastoreProvider.class);
private OntapPrimaryDatastoreDriver primaryDatastoreDriver;
private OntapPrimaryDatastoreLifecycle primaryDatastoreLifecycle;
private HypervisorHostListener listener;
public OntapPrimaryDatastoreProvider() {
logger.info("OntapPrimaryDatastoreProvider initialized");
}
@Override
public DataStoreLifeCycle getDataStoreLifeCycle() {
return primaryDatastoreLifecycle;
}
@Override
public DataStoreDriver getDataStoreDriver() {
return primaryDatastoreDriver;
}
@Override
public HypervisorHostListener getHostListener() {
return listener;
}
@Override
public String getName() {
logger.trace("OntapPrimaryDatastoreProvider: getName: Called");
return OntapStorageConstants.ONTAP_PLUGIN_NAME;
}
@Override
public boolean configure(Map<String, Object> params) {
logger.trace("OntapPrimaryDatastoreProvider: configure: Called");
primaryDatastoreDriver = ComponentContext.inject(OntapPrimaryDatastoreDriver.class);
primaryDatastoreLifecycle = ComponentContext.inject(OntapPrimaryDatastoreLifecycle.class);
listener = ComponentContext.inject(OntapHostListener.class);
return true;
}
@Override
public Set<DataStoreProviderType> getTypes() {
logger.trace("OntapPrimaryDatastoreProvider: getTypes: Called");
Set<DataStoreProviderType> typeSet = new HashSet<DataStoreProviderType>();
typeSet.add(DataStoreProviderType.PRIMARY);
return typeSet;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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.provider;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
import org.apache.cloudstack.storage.service.StorageStrategy;
import org.apache.cloudstack.storage.service.UnifiedNASStrategy;
import org.apache.cloudstack.storage.service.UnifiedSANStrategy;
import org.apache.cloudstack.storage.service.model.ProtocolType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class StorageProviderFactory {
private static final Logger logger = LogManager.getLogger(StorageProviderFactory.class);
public static StorageStrategy getStrategy(OntapStorage ontapStorage) {
ProtocolType protocol = ontapStorage.getProtocol();
logger.info("Initializing StorageProviderFactory with protocol: " + protocol);
switch (protocol) {
case NFS3:
if (!ontapStorage.getIsDisaggregated()) {
UnifiedNASStrategy unifiedNASStrategy = new UnifiedNASStrategy(ontapStorage);
ComponentContext.inject(unifiedNASStrategy);
unifiedNASStrategy.setOntapStorage(ontapStorage);
return unifiedNASStrategy;
}
throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported.");
case ISCSI:
if (!ontapStorage.getIsDisaggregated()) {
UnifiedSANStrategy unifiedSANStrategy = new UnifiedSANStrategy(ontapStorage);
ComponentContext.inject(unifiedSANStrategy);
unifiedSANStrategy.setOntapStorage(ontapStorage);
return unifiedSANStrategy;
}
throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported.");
default:
throw new CloudRuntimeException("Unsupported protocol: " + protocol);
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.service;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
public abstract class NASStrategy extends StorageStrategy {
public NASStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
}
}

Some files were not shown because too many files have changed in this diff Show More