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
4 changes: 2 additions & 2 deletions plugins/storage/volume/ontap/pom.xml

Choose a reason for hiding this comment

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

same changes are available in Piyush's PR also. whoever is merging second, do ensure to take latest and remove redundant changes before merge

Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
<parent>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloudstack-plugins</artifactId>
<version>4.22.0.0-SNAPSHOT</version>
<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>
<json.version>20230227</json.version>
<jackson-databind.version>2.15.2</jackson-databind.version>
<jackson-databind.version>2.13.4</jackson-databind.version>
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.

Downgrading jackson-databind from 2.15.2 to 2.13.4 may introduce known security vulnerabilities. Version 2.13.4 was released in September 2022 and several CVEs have been fixed in later versions. Consider using a more recent version or document the reason for this downgrade.

Suggested change
<jackson-databind.version>2.13.4</jackson-databind.version>
<jackson-databind.version>2.15.2</jackson-databind.version>

Copilot uses AI. Check for mistakes.
<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
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@
*/
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 @@ -43,14 +40,11 @@
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.feign.model.OntapStorage;
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.cloudstack.storage.utils.Constants;
import org.apache.cloudstack.storage.utils.Utility;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Expand Down Expand Up @@ -87,57 +81,7 @@ public DataStoreTO getStoreTO(DataStore store) {

@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) {
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);
}
}

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 = 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 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 Expand Up @@ -277,7 +221,7 @@ private StorageStrategy getStrategyByStoragePoolDetails(Map<String, String> deta
}
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),
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();
Expand Down

Choose a reason for hiding this comment

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

same as earlier comment

Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
/*
* 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;
Expand All @@ -11,7 +31,6 @@
import feign.codec.EncodeException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
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 objectMapper;

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

public Client createClient() {
Expand Down Expand Up @@ -105,7 +122,7 @@ public void encode(Object object, Type bodyType, feign.RequestTemplate template)
return;
}
try {
byte[] jsonBytes = jsonMapper.writeValueAsBytes(object);
byte[] jsonBytes = objectMapper.writeValueAsBytes(object);
template.body(jsonBytes, StandardCharsets.UTF_8);
template.header("Content-Type", "application/json");
} catch (JsonProcessingException e) {
Expand All @@ -126,7 +143,7 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc
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));
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
logger.error("Error decoding JSON response. Status: {}, Raw body: {}", response.status(), json, e);
throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.apache.cloudstack.storage.feign.client;
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

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

Missing Apache license header. All source files should include the standard Apache Software Foundation license header at the top of the file.

Copilot uses AI. Check for mistakes.

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,32 @@
*/
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.net.URI;
import java.util.Map;

//TODO: Proper URLs should be added in the RequestLine annotations below
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 /")
@Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
OntapResponse<Lun> createLun(@Param("authHeader") String authHeader,
@Param("returnRecords") boolean returnRecords,
Lun lun);
@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 /")
@RequestLine("GET /api/storage/luns")
@Headers({"Authorization: {authHeader}"})
OntapResponse<Lun> getLunResponse(@Param("authHeader") String authHeader);
OntapResponse<Lun> getLunResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);

@RequestLine("GET /{uuid}")
@Headers({"Authorization: {authHeader}"})
Expand All @@ -54,36 +58,35 @@ OntapResponse<Lun> createLun(@Param("authHeader") String authHeader,
void deleteLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid);

// iGroup Operation APIs
@RequestLine("POST /")
@Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
OntapResponse<Igroup> createIgroup(@Param("authHeader") String authHeader,
@Param("returnRecords") boolean returnRecords,
Igroup igroupRequest);
@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 /")
@Headers({"Authorization: {authHeader}"}) // TODO: Check this again, uuid should be part of the path?
OntapResponse<Igroup> getIgroupResponse(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
@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 /{uuid}")
@RequestLine("DELETE /api/protocols/san/igroups/{uuid}")
@Headers({"Authorization: {authHeader}"})
void deleteIgroup(@Param("baseUri") URI baseUri, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
void deleteIgroup(@Param("authHeader") String authHeader, @Param("uuid") String uuid);

// LUN Maps Operation APIs
@RequestLine("POST /")
@Headers({"Authorization: {authHeader}"})
OntapResponse<LunMap> createLunMap(@Param("authHeader") String authHeader, LunMap lunMap);
@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 /")
@RequestLine("GET /api/protocols/san/lun-maps")
@Headers({"Authorization: {authHeader}"})
OntapResponse<LunMap> getLunMapResponse(@Param("authHeader") String authHeader);
OntapResponse<LunMap> getLunMapResponse(@Param("authHeader") String authHeader, @QueryMap Map<String, Object> queryMap);

@RequestLine("DELETE /{lunUuid}/{igroupUuid}")
@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);
@Param("lunUuid") String lunUUID,
@Param("igroupUuid") String igroupUUID);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,30 @@
*/
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}"})
void deleteVolume(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
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);
Expand All @@ -42,4 +50,3 @@ public interface VolumeFeignClient {
@Headers({"Accept: {acceptHeader}", "Authorization: {authHeader}"})
JobResponse updateVolumeRebalancing(@Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, Volume volumeRequest);
}

Loading
Loading