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
2 changes: 1 addition & 1 deletion plugins/storage/volume/ontap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
<spring-cloud.version>2021.0.7</spring-cloud.version>
<openfeign.version>11.0</openfeign.version>
<json.version>20230227</json.version>
<jackson-databind.version>2.13.4</jackson-databind.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>
</properties>
<dependencyManagement>
<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
*/
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;
Expand All @@ -40,11 +43,12 @@
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.feign.model.OntapStorage;
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
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;

Expand All @@ -58,13 +62,15 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {

@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
@Inject private PrimaryDataStoreDao storagePoolDao;

@Override
public Map<String, String> getCapabilities() {
s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
Map<String, String> mapCapabilities = new HashMap<>();

mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
// RAW managed initial implementation: snapshot features not yet supported
// TODO Set it to false once we start supporting snapshot feature
mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());

return mapCapabilities;
}
Expand All @@ -75,18 +81,93 @@ public DataTO getTO(DataObject data) {
}

@Override
public DataStoreTO getStoreTO(DataStore store) {
return null;
}
public DataStoreTO getStoreTO(DataStore store) { return null; }

@Override
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) {
VolumeInfo volumeInfo = (VolumeInfo) dataObject;
path = createCloudStackVolumeForTypeVolume(dataStore, volumeInfo);
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 {
if (createCmdResult != null && createCmdResult.isSuccess()) {
s_logger.info("createAsync: Volume created successfully. Path: {}", path);
}
callback.complete(createCmdResult);
}
}

private String createCloudStackVolumeForTypeVolume(DataStore dataStore, VolumeInfo volumeObject) {
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 = Utility.getStrategyByStoragePoolDetails(details);
s_logger.info("createCloudStackVolumeForTypeVolume: Connection to Ontap SVM [{}] successful, preparing CloudStackVolumeRequest", details.get(Constants.SVM_NAME));
CloudStackVolume cloudStackVolumeRequest = Utility.createCloudStackVolumeRequestByProtocol(storagePool, details, volumeObject);
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 if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
return volumeObject.getUuid(); // return the volume UUID for agent as path for mounting
} else {
String errMsg = "createCloudStackVolumeForTypeVolume: Volume creation failed. Lun or Lun Path is null for dataObject: " + volumeObject;
s_logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
}

@Override
public void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback<CommandResult> callback) {

CommandResult commandResult = new CommandResult();
try {
if (store == null || data == null) {
throw new CloudRuntimeException("deleteAsync: store or data is null");
}
if (data.getType() == DataObjectType.VOLUME) {
StoragePoolVO storagePool = storagePoolDao.findById(store.getId());
if(storagePool == null) {
s_logger.error("deleteAsync : Storage Pool not found for id: " + store.getId());
throw new CloudRuntimeException("deleteAsync : Storage Pool not found for id: " + store.getId());
}
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(store.getId());
if (ProtocolType.NFS3.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
// ManagedNFS qcow2 backing file deletion handled by KVM host/libvirt; nothing to do via ONTAP REST.
s_logger.info("deleteAsync: ManagedNFS volume {} no-op ONTAP deletion", data.getId());
}
}
} catch (Exception e) {
commandResult.setResult(e.getMessage());
} finally {
callback.complete(commandResult);
}
}

@Override
Expand Down Expand Up @@ -121,7 +202,6 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore

@Override
public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) {

}

@Override
Expand Down Expand Up @@ -161,7 +241,7 @@ public void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, Qual

@Override
public boolean canProvideStorageStats() {
return true;
return false;
}

@Override
Expand All @@ -171,7 +251,7 @@ public Pair<Long, Long> getStorageStats(StoragePool storagePool) {

@Override
public boolean canProvideVolumeStats() {
return true;
return false; // Not yet implemented for RAW managed NFS
}

@Override
Expand Down Expand Up @@ -213,24 +293,4 @@ public boolean isStorageSupportHA(Storage.StoragePoolType type) {
public void detachVolumeFromAllStorageNodes(Volume volume) {

}

private 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), Long.parseLong(details.get(Constants.SIZE)), 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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public Decoder createDecoder() {
@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;
Expand All @@ -145,8 +146,11 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc
logger.debug("Decoding JSON response: {}", json);
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
logger.error("Error decoding JSON response. Status: {}, Raw body: {}", response.status(), json, 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);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,65 +19,68 @@

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;

//TODO: Proper URLs should be added in the RequestLine annotations below
import java.util.Map;

public interface NASFeignClient {

// File Operations
@RequestLine("GET /{volumeUuid}/files/{path}")
@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);
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath);

@RequestLine("DELETE /{volumeUuid}/files/{path}")
@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);
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath);

@RequestLine("PATCH /{volumeUuid}/files/{path}")
@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);
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath,
FileInfo fileInfo);

@RequestLine("POST /{volumeUuid}/files/{path}")
@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);
@Param("volumeUuid") String volumeUUID,
@Param("path") String filePath,
FileInfo file);

// Export Policy Operations
@RequestLine("POST /")
@Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
ExportPolicy createExportPolicy(@Param("authHeader") String authHeader,
@Param("returnRecords") boolean returnRecords,
@RequestLine("POST /api/protocols/nfs/export-policies")
@Headers({"Authorization: {authHeader}"})
void createExportPolicy(@Param("authHeader") String authHeader,
ExportPolicy exportPolicy);

@RequestLine("GET /")
@RequestLine("GET /api/protocols/nfs/export-policies")
@Headers({"Authorization: {authHeader}"})
OntapResponse<ExportPolicy> getExportPolicyResponse(@Param("authHeader") String authHeader);
OntapResponse<ExportPolicy> getExportPolicyResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);

@RequestLine("GET /{id}")
@RequestLine("GET /api/protocols/nfs/export-policies/{id}")
@Headers({"Authorization: {authHeader}"})
OntapResponse<ExportPolicy> getExportPolicyById(@Param("authHeader") String authHeader,
@Param("id") String id);
ExportPolicy getExportPolicyById(@Param("authHeader") String authHeader,
@Param("id") String id);

@RequestLine("DELETE /{id}")
@RequestLine("DELETE /api/protocols/nfs/export-policies/{id}")
@Headers({"Authorization: {authHeader}"})
void deleteExportPolicyById(@Param("authHeader") String authHeader,
@Param("id") String id);
@Param("id") String id);

@RequestLine("PATCH /{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);
@Param("id") String id,
ExportPolicy request);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ public interface VolumeFeignClient {
@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({"Accept: {acceptHeader}", "Authorization: {authHeader}"})
JobResponse updateVolumeRebalancing(@Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, Volume volumeRequest);
@Headers({ "Authorization: {authHeader}"})
JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest);
}
Loading
Loading