Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
*/
package org.apache.cloudstack.storage.driver;

import com.cloud.agent.api.Answer;
import com.cloud.agent.api.to.DataObjectType;
import com.cloud.agent.api.to.DataStoreTO;
import com.cloud.agent.api.to.DataTO;
import com.cloud.exception.InvalidParameterValueException;
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 com.cloud.utils.exception.CloudRuntimeException;
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;
Expand All @@ -37,15 +41,28 @@
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.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.service.StorageStrategy;
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
import org.apache.cloudstack.storage.service.model.ProtocolType;
import org.apache.cloudstack.storage.utils.Constants;
import org.apache.cloudstack.storage.utils.Utility;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.inject.Inject;
import java.util.HashMap;
import java.util.Map;

public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {

private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreDriver.class);
private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class);

@Inject private Utility utils;
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject private PrimaryDataStoreDao storagePoolDao;
@Override
public Map<String, String> getCapabilities() {
s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
Expand All @@ -68,9 +85,58 @@ public DataStoreTO getStoreTO(DataStore store) {
}

@Override
public void createAsync(DataStore store, DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
public void createAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
CreateCmdResult createCmdResult = null;
String path = null;
String errMsg = null;
if (dataStore == null) {
throw new InvalidParameterValueException("createAsync: dataStore should not be null");
}
if (dataObject == null) {
throw new InvalidParameterValueException("createAsync: dataObject should not be null");
}
if (callback == null) {
throw new InvalidParameterValueException("createAsync: callback should not be null");
}
try {
s_logger.info("createAsync: Started for data store [{}] and data object [{}] of type [{}]",
dataStore, dataObject, dataObject.getType());
if (dataObject.getType() == DataObjectType.VOLUME) {
path = createCloudStackVolumeForTypeVolume(dataStore, dataObject);
createCmdResult = new CreateCmdResult(path, new Answer(null, true, null));
} else {
errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync";
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
} catch (Exception e) {
errMsg = e.getMessage();
s_logger.error("createAsync: Failed for dataObject [{}]: {}", dataObject, errMsg);
createCmdResult = new CreateCmdResult(null, new Answer(null, false, errMsg));
createCmdResult.setResult(e.toString());
} finally {
callback.complete(createCmdResult);
}
}

s_logger.trace("OntapPrimaryDatastoreDriver: createAsync: Store: "+store+", data: "+data);
private String createCloudStackVolumeForTypeVolume(DataStore dataStore, DataObject dataObject) {
StoragePoolVO storagePool = storagePoolDao.findById(dataStore.getId());
if(storagePool == null) {
s_logger.error("createCloudStackVolume : Storage Pool not found for id: " + dataStore.getId());
throw new CloudRuntimeException("createCloudStackVolume : Storage Pool not found for id: " + dataStore.getId());
}
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(dataStore.getId());
StorageStrategy storageStrategy = utils.getStrategyByStoragePoolDetails(details);
s_logger.info("createCloudStackVolumeForTypeVolume: Connection to Ontap SVM [{}] successful, preparing CloudStackVolumeRequest", details.get(Constants.SVM_NAME));
CloudStackVolume cloudStackVolumeRequest = utils.createCloudStackVolumeRequestByProtocol(storagePool, details, dataObject);
CloudStackVolume cloudStackVolume = storageStrategy.createCloudStackVolume(cloudStackVolumeRequest);
if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL)) && cloudStackVolume.getLun() != null && cloudStackVolume.getLun().getName() != null) {
return cloudStackVolume.getLun().getName();
} else {
String errMsg = "createCloudStackVolumeForTypeVolume: Volume creation failed. Lun or Lun Path is null for dataObject: " + dataObject;
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public abstract class StorageStrategy {
@Inject
private JobFeignClient jobFeignClient;

private final OntapStorage storage;
protected final OntapStorage storage;

/**
* Presents aggregate object for the unified storage, not eligible for disaggregated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,60 @@

package org.apache.cloudstack.storage.service;

import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.storage.feign.client.SANFeignClient;
import org.apache.cloudstack.storage.feign.model.Lun;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
import org.apache.cloudstack.storage.service.model.AccessGroup;
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
import org.apache.cloudstack.storage.utils.Constants;
import org.apache.cloudstack.storage.utils.Utility;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.inject.Inject;
import java.net.URI;
import java.util.Map;

public class UnifiedSANStrategy extends SANStrategy{
public class UnifiedSANStrategy extends SANStrategy {

private static final Logger s_logger = LogManager.getLogger(UnifiedSANStrategy.class);
@Inject private Utility utils;
@Inject private SANFeignClient sanFeignClient;
public UnifiedSANStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
}

@Override
public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume) {
//TODO
return null;
s_logger.info("createCloudStackVolume : Creating Lun with cloudstackVolume request {} ", cloudstackVolume);
if (cloudstackVolume == null || cloudstackVolume.getLun() == null) {
s_logger.error("createCloudStackVolume: LUN creation failed. Invalid request: {}", cloudstackVolume);
throw new CloudRuntimeException("createCloudStackVolume : Failed to create Lun, invalid request");
}
try {
// Get AuthHeader
String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
// Create URI for lun creation
URI url = utils.generateURI(Constants.CREATE_LUN);
//TODO: It is possible that Lun creation will take time and we may need to handle through async job.
OntapResponse<Lun> createdLun = sanFeignClient.createLun(url, authHeader, true, cloudstackVolume.getLun());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add TODO, based on testing need to covert this createLun to Async

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

if (createdLun == null || createdLun.getRecords() == null || createdLun.getRecords().size() == 0) {
s_logger.error("createCloudStackVolume: LUN creation failed for Lun {}", cloudstackVolume.getLun().getName());
throw new CloudRuntimeException("Failed to create Lun: " + cloudstackVolume.getLun().getName());
}
Lun lun = createdLun.getRecords().get(0);
s_logger.debug("createCloudStackVolume: LUN created successfully. Lun: {}", lun);
s_logger.info("createCloudStackVolume: LUN created successfully. LunName: {}", lun.getName());

CloudStackVolume createdCloudStackVolume = new CloudStackVolume();
createdCloudStackVolume.setLun(lun);
return createdCloudStackVolume;
} catch (Exception e) {
s_logger.error("Exception occurred while creating LUN: {}. Exception: {}", cloudstackVolume.getLun().getName(), e.getMessage());
throw new CloudRuntimeException("Failed to create Lun: " + e.getMessage());
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ public class Constants {
public static final int JOB_MAX_RETRIES = 100;
public static final int CREATE_VOLUME_CHECK_SLEEP_TIME = 2000;

public static final String PATH_SEPARATOR = "/";

public static final String VOLUME_PATH_PREFIX = "/vol/";

public static final String KVM = "KVM";

public static final String HTTPS = "https://";
public static final String GET_SVMs = "/api/svm/svms";
public static final String CREATE_VOLUME = "/api/storage/volumes";
public static final String GET_JOB_BY_UUID = "/api/cluster/jobs";
public static final String CREATE_LUN = "/api/storage/luns";

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,111 @@
package org.apache.cloudstack.storage.utils;

import com.cloud.utils.StringUtils;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.feign.model.Lun;
import org.apache.cloudstack.storage.feign.model.LunSpace;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
import org.apache.cloudstack.storage.feign.model.Svm;
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
import org.apache.cloudstack.storage.service.StorageStrategy;
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
import org.apache.cloudstack.storage.service.model.ProtocolType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

import javax.inject.Inject;
import java.net.URI;
import java.util.Map;

@Component
public class Utility {
@Inject
OntapStorage ontapStorage;

private static final Logger s_logger = LogManager.getLogger(Utility.class);
@Inject private OntapStorage ontapStorage;
@Inject private PrimaryDataStoreDao storagePoolDao;
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;

private static final String BASIC = "Basic";
private static final String AUTH_HEADER_COLON = ":";

/**
* Method generates authentication headers using storage backend credentials passed as normal string
* @param username -->> username of the storage backend
* @param password -->> normal decoded password of the storage backend
*
* @param username -->> username of the storage backend
* @param password -->> normal decoded password of the storage backend
* @return
*/
public String generateAuthHeader(String username, String password) {
public String generateAuthHeader (String username, String password) {
byte[] encodedBytes = Base64Utils.encode((username + AUTH_HEADER_COLON + password).getBytes());
return BASIC + StringUtils.SPACE + new String(encodedBytes);
}

public URI generateURI(String path) {
public URI generateURI (String path) {
String uriString = Constants.HTTPS + ontapStorage.getManagementLIF() + path;
return URI.create(uriString);
}

public CloudStackVolume createCloudStackVolumeRequestByProtocol(StoragePoolVO storagePool, Map<String, String> details, DataObject dataObject) {
CloudStackVolume cloudStackVolumeRequest = null;

String protocol = details.get(Constants.PROTOCOL);
if (ProtocolType.ISCSI.name().equalsIgnoreCase(protocol)) {
cloudStackVolumeRequest = new CloudStackVolume();
Lun lunRequest = new Lun();
Svm svm = new Svm();
svm.setName(details.get(Constants.SVM_NAME));
lunRequest.setSvm(svm);

LunSpace lunSpace = new LunSpace();
lunSpace.setSize(dataObject.getSize());
lunRequest.setSpace(lunSpace);
//Lun name is full path like in unified "/vol/VolumeName/LunName"
String lunFullName = Constants.VOLUME_PATH_PREFIX + storagePool.getName() + Constants.PATH_SEPARATOR + dataObject.getName();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add example for lun full name

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

lunRequest.setName(lunFullName);

String hypervisorType = storagePool.getHypervisor().name();
String osType = null;
switch (hypervisorType) {
case Constants.KVM:
osType = Lun.OsTypeEnum.LINUX.getValue();
break;
default:
String errMsg = "createCloudStackVolume : Unsupported hypervisor type " + hypervisorType + " for ONTAP storage";
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
lunRequest.setOsType(Lun.OsTypeEnum.valueOf(osType));

cloudStackVolumeRequest.setLun(lunRequest);
return cloudStackVolumeRequest;
} else {
throw new CloudRuntimeException("createCloudStackVolumeRequestByProtocol: Unsupported protocol " + protocol);
}
}

public StorageStrategy getStrategyByStoragePoolDetails(Map<String, String> details) {
if (details == null || details.isEmpty()) {
s_logger.error("getStrategyByStoragePoolDetails: Storage pool details are null or empty");
throw new CloudRuntimeException("getStrategyByStoragePoolDetails: Storage pool details are null or empty");
}
String protocol = details.get(Constants.PROTOCOL);
OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD),
details.get(Constants.MANAGEMENT_LIF), details.get(Constants.SVM_NAME), ProtocolType.valueOf(protocol),
Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED)));
StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage);
boolean isValid = storageStrategy.connect();
if (isValid) {
s_logger.info("Connection to Ontap SVM [{}] successful", details.get(Constants.SVM_NAME));
return storageStrategy;
} else {
s_logger.error("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details.get(Constants.SVM_NAME) + "] failed");
throw new CloudRuntimeException("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details.get(Constants.SVM_NAME) + "] failed");
}
}
}
Loading