Skip to content
Closed
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
3 changes: 1 addition & 2 deletions plugins/storage/volume/ontap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
<spring-cloud.version>2021.0.7</spring-cloud.version>
<openfeign.version>11.0</openfeign.version>
<json.version>20230227</json.version>
<jackson-databind.version>2.15.2</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>
Expand Down Expand Up @@ -77,7 +76,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>2.13.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public void createAsync(DataStore dataStore, DataObject dataObject, AsyncComplet
createCmdResult = new CreateCmdResult(null, new Answer(null, false, errMsg));
createCmdResult.setResult(e.toString());
} finally {
s_logger.info("Volume creation successfully completed");
callback.complete(createCmdResult);
}
}
Expand All @@ -129,10 +130,12 @@ private String createCloudStackVolumeForTypeVolume(DataStore dataStore, DataObje
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(dataStore.getId());
StorageStrategy storageStrategy = getStrategyByStoragePoolDetails(details);
s_logger.info("createCloudStackVolumeForTypeVolume: Connection to Ontap SVM [{}] successful, preparing CloudStackVolumeRequest", details.get(Constants.SVM_NAME));
CloudStackVolume cloudStackVolumeRequest = Utility.createCloudStackVolumeRequestByProtocol(storagePool, details, dataObject);
CloudStackVolume cloudStackVolumeRequest = Utility.createCloudStackVolumeRequestByProtocol(storagePool, details, (VolumeInfo) 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 if (ProtocolType.NFS.name().equalsIgnoreCase(details.get(Constants.PROTOCOL))) {
return cloudStackVolume.getFile().getName();
} else {
String errMsg = "createCloudStackVolumeForTypeVolume: Volume creation failed. Lun or Lun Path is null for dataObject: " + dataObject;
s_logger.error(errMsg);
Expand Down Expand Up @@ -177,7 +180,6 @@ public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore

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

}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
/*
* 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.RequestInterceptor;
Expand All @@ -11,7 +30,7 @@
import feign.codec.EncodeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
Expand All @@ -36,13 +55,11 @@ public class FeignConfiguration {
private final int retryMaxInterval = 5;
private final String ontapFeignMaxConnection = "80";
private final String ontapFeignMaxConnectionPerRoute = "20";
private final JsonMapper jsonMapper;
private final ObjectMapper jsonMapper;

public FeignConfiguration() {
this.jsonMapper = JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.findAndAddModules()
.build();
this.jsonMapper = new ObjectMapper();
this.jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

public Client createClient() {
Expand Down Expand Up @@ -120,16 +137,43 @@ 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");
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

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

Excessive debug logging added throughout the decoder (lines 140-169). This level of verbose logging should be removed or converted to trace level before merging to production to avoid log pollution.

Copilot uses AI. Check for mistakes.
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 jsonMapper.readValue(json, jsonMapper.getTypeFactory().constructType(type));
logger.debug("Target type: {}", type);
logger.debug("About to call jsonMapper.readValue()...");

Object result = null;
try {
logger.debug("Calling jsonMapper.constructType()...");
var javaType = jsonMapper.getTypeFactory().constructType(type);
logger.debug("constructType() returned: {}", javaType);

logger.debug("Calling jsonMapper.readValue() with json and javaType...");
result = jsonMapper.readValue(json, javaType);
logger.debug("jsonMapper.readValue() completed successfully");
} catch (Throwable ex) {
logger.error("EXCEPTION in jsonMapper.readValue()! Type: {}, Message: {}", ex.getClass().getName(), ex.getMessage(), ex);
throw ex;
}

if (result == null) {
logger.warn("Decoded result is null!");
} else {
logger.debug("Successfully decoded to object of type: {}", result.getClass().getName());
}
logger.debug("Returning result from decoder");
return result;
} 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,
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

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

Return type changed from 'OntapResponse' to 'ExportPolicy'. This could be a breaking change if the method is used elsewhere expecting the wrapped response type.

Suggested change
ExportPolicy getExportPolicyById(@Param("authHeader") String authHeader,
OntapResponse<ExportPolicy> getExportPolicyById(@Param("authHeader") String authHeader,

Copilot uses AI. Check for mistakes.
@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
Expand Up @@ -18,11 +18,15 @@
*/
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 {

Expand All @@ -38,8 +42,12 @@ 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);
Comment on lines +50 to +51
Copy link

Copilot AI Nov 10, 2025

Choose a reason for hiding this comment

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

Removed 'acceptHeader' parameter from method signature. This is a breaking change that could affect existing callers of this method.

Suggested change
@Headers({ "Authorization: {authHeader}"})
JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Volume volumeRequest);
@Headers({ "Authorization: {authHeader}", "Accept: {acceptHeader}" })
JobResponse updateVolumeRebalancing(@Param("authHeader") String authHeader, @Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, Volume volumeRequest);

Copilot uses AI. Check for mistakes.
}

Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ public static ProtocolsEnum fromValue(String text) {
@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;
Expand Down Expand Up @@ -140,6 +150,30 @@ public void setMatch (String 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();
Expand Down
Empty file.
Empty file.
Empty file.
Loading
Loading