Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 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,13 @@ 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;


public ExportRule anonymousUser(String anonymousUser) {
this.anonymousUser = anonymousUser;
return this;
Expand Down Expand Up @@ -140,6 +147,22 @@ 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;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Expand Down
Empty file.
Empty file.
Empty file.
Loading
Loading