From ec1707bc989f8fabcd965c60cbd85c8c080c4909 Mon Sep 17 00:00:00 2001 From: "Locharla, Sandeep" Date: Mon, 3 Nov 2025 11:22:12 +0530 Subject: [PATCH 1/5] CSTACKEX-1: Feign changes and fixes for getting storage pool creation to work --- plugins/storage/volume/ontap/pom.xml | 33 +++-- .../driver/OntapPrimaryDatastoreDriver.java | 7 +- .../storage/feign/FeignClientFactory.java | 46 +++++++ .../storage/feign/FeignConfiguration.java | 99 +++++++++----- .../feign/client/AggregateFeignClient.java | 25 ++-- .../feign/client/ClusterFeignClient.java | 16 +-- .../storage/feign/client/JobFeignClient.java | 22 +-- .../storage/feign/client/NASFeignClient.java | 90 ++++++------ .../storage/feign/client/SANFeignClient.java | 128 +++++++++--------- .../storage/feign/client/SvmFeignClient.java | 21 ++- .../feign/client/VolumeFeignClient.java | 37 ++--- .../storage/feign/model/OntapStorage.java | 23 ++-- .../OntapPrimaryDatastoreProvider.java | 4 +- .../provider/StorageProviderFactory.java | 21 ++- .../storage/service/StorageStrategy.java | 92 ++++++------- .../storage/service/UnifiedNASStrategy.java | 42 ++++-- .../storage/service/UnifiedSANStrategy.java | 20 ++- .../cloudstack/storage/utils/Utility.java | 18 +-- 18 files changed, 414 insertions(+), 330 deletions(-) create mode 100644 plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java diff --git a/plugins/storage/volume/ontap/pom.xml b/plugins/storage/volume/ontap/pom.xml index d538d555cb10..3e706b8065c8 100644 --- a/plugins/storage/volume/ontap/pom.xml +++ b/plugins/storage/volume/ontap/pom.xml @@ -58,24 +58,35 @@ ${json.version} - org.springframework.cloud - spring-cloud-commons + io.github.openfeign + feign-core + ${openfeign.version} - org.springframework.cloud - spring-cloud-starter-openfeign - - - org.springframework.security - spring-security-crypto - - + io.github.openfeign + feign-httpclient + ${openfeign.version} + + + + + io.github.openfeign - feign-httpclient + feign-jackson ${openfeign.version} + + com.fasterxml.jackson.core + jackson-databind + 2.15.2 + + + org.apache.httpcomponents + httpclient + 4.5.14 + org.apache.cloudstack cloud-engine-storage-volume diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java index 6d850a97168f..7895cf1a9dfe 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java @@ -51,6 +51,7 @@ import org.apache.cloudstack.storage.utils.Utility; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import com.cloud.utils.component.ComponentContext; import javax.inject.Inject; import java.util.HashMap; @@ -60,9 +61,13 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver { private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class); - @Inject private Utility utils; + private Utility utils; @Inject private StoragePoolDetailsDao storagePoolDetailsDao; @Inject private PrimaryDataStoreDao storagePoolDao; + + public OntapPrimaryDatastoreDriver() { + utils = ComponentContext.inject(Utility.class); + } @Override public Map getCapabilities() { s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called"); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java new file mode 100644 index 000000000000..f89198e49353 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java @@ -0,0 +1,46 @@ +/* + * 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.Feign; + +public class FeignClientFactory { + + private final FeignConfiguration feignConfiguration; + + public FeignClientFactory() { + this.feignConfiguration = new FeignConfiguration(); + } + + public FeignClientFactory(FeignConfiguration feignConfiguration) { + this.feignConfiguration = feignConfiguration; + } + + public T createClient(Class clientClass) { + return Feign.builder() + .client(feignConfiguration.createClient()) + .encoder(feignConfiguration.createEncoder()) + .decoder(feignConfiguration.createDecoder()) +// .logger(feignConfiguration.createLogger()) + .retryer(feignConfiguration.createRetryer()) + .requestInterceptor(feignConfiguration.createRequestInterceptor()) + .target(clientClass, "https://placeholder.com"); + } +} diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java index 576c2dd1c1b4..42227a7295dd 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java @@ -19,41 +19,45 @@ package org.apache.cloudstack.storage.feign; - import feign.RequestInterceptor; -import feign.RequestTemplate; import feign.Retryer; -import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import feign.Client; +import feign.httpclient.ApacheHttpClient; +import feign.codec.Decoder; +import feign.codec.Encoder; +//import feign.slf4j.Slf4jLogger; +import feign.Response; +import feign.codec.DecodeException; +import feign.codec.EncodeException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustAllStrategy; import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.ssl.SSLContexts; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import feign.Client; -import feign.httpclient.ApacheHttpClient; + import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.concurrent.TimeUnit; -@Configuration public class FeignConfiguration { - private static Logger logger = LogManager.getLogger(FeignConfiguration.class); + private static final Logger logger = LogManager.getLogger(FeignConfiguration.class); - private int retryMaxAttempt = 3; - - private int retryMaxInterval = 5; - - private String ontapFeignMaxConnection = "80"; - - private String ontapFeignMaxConnectionPerRoute = "20"; - - @Bean - public Client client(ApacheHttpClientFactory httpClientFactory) { + private final int retryMaxAttempt = 3; + private final int retryMaxInterval = 5; + private final String ontapFeignMaxConnection = "80"; + private final String ontapFeignMaxConnectionPerRoute = "20"; + private final ObjectMapper objectMapper = new ObjectMapper(); + public Client createClient() { int maxConn; int maxConnPerRoute; try { @@ -68,10 +72,11 @@ public Client client(ApacheHttpClientFactory httpClientFactory) { logger.error("ontapFeignClient: encounter exception while parse the max connection per route from env. setting default value"); maxConnPerRoute = 2; } + // Disable Keep Alive for Http Connection logger.debug("ontapFeignClient: Setting the feign client config values as max connection: {}, max connections per route: {}", maxConn, maxConnPerRoute); ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> 0; - CloseableHttpClient httpClient = httpClientFactory.createBuilder() + CloseableHttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(maxConn) .setMaxConnPerRoute(maxConnPerRoute) .setKeepAliveStrategy(keepAliveStrategy) @@ -91,22 +96,54 @@ private SSLConnectionSocketFactory getSSLSocketFactory() { } } + public RequestInterceptor createRequestInterceptor() { + return template -> { + logger.info("Feign Request URL: {}", template.url()); + logger.info("HTTP Method: {}", template.method()); + logger.info("Headers: {}", template.headers()); + if (template.body() != null) { + logger.info("Body: {}", new String(template.body())); + } + }; + } + + public Retryer createRetryer() { + return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt); + } - @Bean - public RequestInterceptor requestInterceptor() { - return new RequestInterceptor() { + public Encoder createEncoder() { + return new Encoder() { @Override - public void apply(RequestTemplate template) { - logger.info("Feign Request URL: {}", template.url()); - logger.info("HTTP Method: {}", template.method()); - logger.info("Headers: {}", template.headers()); - logger.info("Body: {}", template.requestBody().asString()); + public void encode(Object object, Type bodyType, feign.RequestTemplate template) throws EncodeException { + try { + String json = objectMapper.writeValueAsString(object); + template.body(Arrays.toString(json.getBytes(StandardCharsets.UTF_8))); + template.header("Content-Type", "application/json"); + } catch (JsonProcessingException e) { + throw new EncodeException("Error encoding object to JSON", e); + } } }; } - @Bean - public Retryer feignRetryer() { - return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt); + public Decoder createDecoder() { + return new Decoder() { + @Override + public Object decode(Response response, Type type) throws IOException, DecodeException { + if (response.body() == null) { + return null; + } + try { + String json = new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8); + return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type)); + } catch (IOException e) { + throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e); + } + } + }; } + +// public Slf4jLogger createLogger() { +// return new Slf4jLogger(); +// } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java index ed57bf419405..12ef7316df10 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java @@ -20,26 +20,19 @@ package org.apache.cloudstack.storage.feign.client; import org.apache.cloudstack.storage.feign.model.Aggregate; -import org.apache.cloudstack.storage.feign.FeignConfiguration; import org.apache.cloudstack.storage.feign.model.response.OntapResponse; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; -@Lazy -@FeignClient(name="AggregateClient", url="https://{clusterIP}/api/storage/aggregates", configuration = FeignConfiguration.class) public interface AggregateFeignClient { - //this method to get all aggregates and also filtered aggregates based on query params as a part of URL - @RequestMapping(method=RequestMethod.GET) - OntapResponse getAggregateResponse(URI baseURL, @RequestHeader("Authorization") String header); - - @RequestMapping(method=RequestMethod.GET, value="/{uuid}") - Aggregate getAggregateByUUID(URI baseURL,@RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); + @RequestLine("GET /") + @Headers("Authorization: {authHeader}") + OntapResponse getAggregateResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @RequestLine("GET /{uuid}") + @Headers("Authorization: {authHeader}") + Aggregate getAggregateByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java index 7758a846f361..dcd12e60d281 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java @@ -19,19 +19,15 @@ package org.apache.cloudstack.storage.feign.client; -import org.apache.cloudstack.storage.feign.FeignConfiguration; import org.apache.cloudstack.storage.feign.model.Cluster; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; -@FeignClient(name="ClusterClient", url="https://{clusterIP}/api/cluster", configuration = FeignConfiguration.class) public interface ClusterFeignClient { - @RequestMapping(method= RequestMethod.GET) - Cluster getCluster(URI baseURL, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value); - + @RequestLine("GET /") + @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) + Cluster getCluster(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java index 4becf7bb29c4..ea3c53728280 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java @@ -18,25 +18,15 @@ */ package org.apache.cloudstack.storage.feign.client; -import org.apache.cloudstack.storage.feign.FeignConfiguration; import org.apache.cloudstack.storage.feign.model.Job; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; -/** - * @author Administrator - * - */ -@Lazy -@FeignClient(name = "JobClient", url = "https://{clusterIP}/api/cluster/jobs" , configuration = FeignConfiguration.class) public interface JobFeignClient { - @RequestMapping(method = RequestMethod.GET, value="/{uuid}") - Job getJobByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); - + @RequestLine("GET /{uuid}") + @Headers("Authorization: {authHeader}") + Job getJobByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java index 6e7b37d9378f..96053d6a6459 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java @@ -19,61 +19,67 @@ package org.apache.cloudstack.storage.feign.client; -import org.apache.cloudstack.storage.feign.FeignConfiguration; 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 org.springframework.cloud.openfeign.FeignClient; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMethod; +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; -/** - * @author Administrator - * - */ -@Lazy -@FeignClient(name = "NASClient", url = "" , configuration = FeignConfiguration.class) public interface NASFeignClient { - //File Operations - - @RequestMapping(method = RequestMethod.GET, value="/{volume.uuid}/files/{path}") - OntapResponse getFileResponse(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath); - @RequestMapping(method = RequestMethod.DELETE, value="/{volume.uuid}/files/{path}") - void deleteFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath); - @RequestMapping(method = RequestMethod.PATCH, value="/{volume.uuid}/files/{path}") - void updateFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo fileInfo); - @RequestMapping(method = RequestMethod.POST, value="/{volume.uuid}/files/{path}") - void createFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID, - @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo file); + // File Operations + @RequestLine("GET /{volumeUuid}/files/{path}") + @Headers("Authorization: {authHeader}") + OntapResponse getFileResponse(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath); + @RequestLine("DELETE /{volumeUuid}/files/{path}") + @Headers("Authorization: {authHeader}") + void deleteFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath); + @RequestLine("PATCH /{volumeUuid}/files/{path}") + @Headers("Authorization: {authHeader}") + void updateFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath, + @Param("fileInfo") FileInfo fileInfo); - //Export Policy Operations + @RequestLine("POST /{volumeUuid}/files/{path}") + @Headers("Authorization: {authHeader}") + void createFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath, + @Param("file") FileInfo file); - @RequestMapping(method = RequestMethod.POST) - ExportPolicy createExportPolicy(URI uri, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value, - @RequestBody ExportPolicy exportPolicy); + // Export Policy Operations + @RequestLine("POST /") + @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) + ExportPolicy createExportPolicy(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("returnRecords") boolean returnRecords, + @Param("exportPolicy") ExportPolicy exportPolicy); - //this method to get all export policies and also filtered export policy based on query params as a part of URL - @RequestMapping(method = RequestMethod.GET) - OntapResponse getExportPolicyResponse(URI baseURL, @RequestHeader("Authorization") String header); + @RequestLine("GET /") + @Headers("Authorization: {authHeader}") + OntapResponse getExportPolicyResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); - @RequestMapping(method = RequestMethod.GET, value="/{id}") - OntapResponse getExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id); + @RequestLine("GET /{id}") + @Headers("Authorization: {authHeader}") + OntapResponse getExportPolicyById(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Param("id") String id); - @RequestMapping(method = RequestMethod.DELETE, value="/{id}") - void deleteExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id); + @RequestLine("DELETE /{id}") + @Headers("Authorization: {authHeader}") + void deleteExportPolicyById(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Param("id") String id); - @RequestMapping(method = RequestMethod.PATCH, value="/{id}") - OntapResponse updateExportPolicy(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id, - @RequestBody ExportPolicy request); + @RequestLine("PATCH /{id}") + @Headers("Authorization: {authHeader}") + OntapResponse updateExportPolicy(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Param("id") String id, + @Param("request") ExportPolicy request); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java index 325823f8515c..e750521a068b 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java @@ -20,72 +20,76 @@ import org.apache.cloudstack.storage.feign.model.Igroup; import org.apache.cloudstack.storage.feign.model.Lun; -import org.apache.cloudstack.storage.feign.FeignConfiguration; import org.apache.cloudstack.storage.feign.model.LunMap; import org.apache.cloudstack.storage.feign.model.response.OntapResponse; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestHeader; - +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; -@Lazy -@FeignClient(name = "SANClient", url = "", configuration = FeignConfiguration.class ) public interface SANFeignClient { - //Lun Operation APIs - @RequestMapping(method = RequestMethod.POST) - OntapResponse createLun(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestHeader("return_records") boolean value, - @RequestBody Lun lun); - - //this method to get all luns and also filtered luns based on query params as a part of URL - @RequestMapping(method = RequestMethod.GET) - OntapResponse getLunResponse(URI baseURL, @RequestHeader("Authorization") String authHeader); - - @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") - Lun getLunByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid); - - @RequestMapping(method = RequestMethod.PATCH, value = "/{uuid}") - void updateLun(URI uri, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid, - @RequestBody Lun lun); - - @RequestMapping(method = RequestMethod.DELETE, value = "/{uuid}") - void deleteLun(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid); - - - //iGroup Operation APIs - - @RequestMapping(method = RequestMethod.POST) - OntapResponse createIgroup(URI uri, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value, - @RequestBody Igroup igroupRequest); - - //this method to get all igroups and also filtered igroups based on query params as a part of URL - @RequestMapping(method = RequestMethod.GET) - OntapResponse getIgroupResponse(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); - @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") - Igroup getIgroupByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid); - @RequestMapping(method = RequestMethod.DELETE, value = "/{uuid}") - void deleteIgroup(URI baseUri, @RequestHeader("Authorization") String authHeader, @PathVariable(name = "uuid", required = true) String uuid); - - @RequestMapping(method = RequestMethod.POST, value = "/{uuid}/igroups") - OntapResponse addNestedIgroups(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid, - @RequestBody Igroup igroupNestedRequest, @RequestHeader(value="return_records", defaultValue = "true") boolean value); - - - //Lun Maps Operation APIs - - @RequestMapping(method = RequestMethod.POST) - OntapResponse createLunMap(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody LunMap lunMap); - - @RequestMapping(method = RequestMethod.GET) - OntapResponse getLunMapResponse(URI baseURL, @RequestHeader("Authorization") String authHeader); - - @RequestMapping(method = RequestMethod.GET, value = "/{lun.uuid}/{igroup.uuid}") - void deleteLunMap(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="lun.uuid", required=true) String uuid, - @PathVariable(name="igroup.uuid", required=true) String igroupUUID); - + // LUN Operation APIs + @RequestLine("POST /") + @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) + OntapResponse createLun(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Param("returnRecords") boolean returnRecords, + @Param("lun") Lun lun); + + @RequestLine("GET /") + @Headers("Authorization: {authHeader}") + OntapResponse getLunResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + + @RequestLine("GET /{uuid}") + @Headers("Authorization: {authHeader}") + Lun getLunByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + + @RequestLine("PATCH /{uuid}") + @Headers("Authorization: {authHeader}") + void updateLun(@Param("uri") URI uri, @Param("authHeader") String authHeader, @Param("uuid") String uuid, @Param("lun") Lun lun); + + @RequestLine("DELETE /{uuid}") + @Headers("Authorization: {authHeader}") + void deleteLun(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + + // iGroup Operation APIs + @RequestLine("POST /") + @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) + OntapResponse createIgroup(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("returnRecords") boolean returnRecords, + @Param("igroupRequest") Igroup igroupRequest); + + @RequestLine("GET /") + @Headers("Authorization: {authHeader}") + OntapResponse getIgroupResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + + @RequestLine("GET /{uuid}") + @Headers("Authorization: {authHeader}") + Igroup getIgroupByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + + @RequestLine("DELETE /{uuid}") + @Headers("Authorization: {authHeader}") + void deleteIgroup(@Param("baseUri") URI baseUri, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + + @RequestLine("POST /{uuid}/igroups") + @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) + OntapResponse addNestedIgroups(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Param("uuid") String uuid, + @Param("igroupNestedRequest") Igroup igroupNestedRequest, + @Param("returnRecords") boolean returnRecords); + + // LUN Maps Operation APIs + @RequestLine("POST /") + @Headers("Authorization: {authHeader}") + OntapResponse createLunMap(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("lunMap") LunMap lunMap); + + @RequestLine("GET /") + @Headers("Authorization: {authHeader}") + OntapResponse getLunMapResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + + @RequestLine("DELETE /{lunUuid}/{igroupUuid}") + @Headers("Authorization: {authHeader}") + void deleteLunMap(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Param("lunUuid") String lunUuid, + @Param("igroupUuid") String igroupUuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java index c0091c8733e9..881fe8f27664 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java @@ -19,23 +19,20 @@ package org.apache.cloudstack.storage.feign.client; -import org.apache.cloudstack.storage.feign.FeignConfiguration; import org.apache.cloudstack.storage.feign.model.Svm; import org.apache.cloudstack.storage.feign.model.response.OntapResponse; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; -@FeignClient(name = "SvmClient", url = "https://{clusterIP}/api/svm/svms", configuration = FeignConfiguration.class) public interface SvmFeignClient { - //this method to get all svms and also filtered svms based on query params as a part of URL - @RequestMapping(method = RequestMethod.GET) - OntapResponse getSvmResponse(URI baseURL, @RequestHeader("Authorization") String header); - - @RequestMapping(method = RequestMethod.GET, value = "/{uuid}") - Svm getSvmByUUID(URI baseURL, @RequestHeader("Authorization") String header); + @RequestLine("GET /") + @Headers("Authorization: {authHeader}") + OntapResponse getSvmResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @RequestLine("GET /{uuid}") + @Headers("Authorization: {authHeader}") + Svm getSvmByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java index af92754da42e..41e05caf9bb1 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java @@ -18,35 +18,28 @@ */ package org.apache.cloudstack.storage.feign.client; - -import org.apache.cloudstack.storage.feign.FeignConfiguration; import org.apache.cloudstack.storage.feign.model.Volume; import org.apache.cloudstack.storage.feign.model.response.JobResponse; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.context.annotation.Lazy; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMethod; - +import feign.Headers; +import feign.Param; +import feign.RequestLine; import java.net.URI; - -@Lazy -@FeignClient(name = "VolumeClient", url = "https://{clusterIP}/api/storage/volumes", configuration = FeignConfiguration.class) public interface VolumeFeignClient { - @RequestMapping(method = RequestMethod.DELETE, value="/{uuid}") - void deleteVolume(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); - - @RequestMapping(method = RequestMethod.POST) - JobResponse createVolumeWithJob(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest); + @RequestLine("DELETE /{uuid}") + @Headers("Authorization: {authHeader}") + void deleteVolume(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); - @RequestMapping(method = RequestMethod.GET, value="/{uuid}") - Volume getVolumeByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); + @RequestLine("POST /") + @Headers("Authorization: {authHeader}") + JobResponse createVolumeWithJob(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("volumeRequest") Volume volumeRequest); - @RequestMapping(method = RequestMethod.PATCH) - JobResponse updateVolumeRebalancing(URI baseURL, @RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest); + @RequestLine("GET /{uuid}") + @Headers("Authorization: {authHeader}") + Volume getVolumeByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @RequestLine("PATCH /{uuid}") + @Headers({"accept: {acceptHeader}", "Authorization: {authHeader}"}) + JobResponse updateVolumeRebalancing(@Param("baseURL") URI baseURL, @Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, @Param("volumeRequest") Volume volumeRequest); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java index 017dfff23d27..839af96756a1 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java @@ -19,16 +19,15 @@ package org.apache.cloudstack.storage.feign.model; - import org.apache.cloudstack.storage.service.model.ProtocolType; public class OntapStorage { - private String username; - private String password; - private String managementLIF; - private String svmName; + private String username; + private String password; + private String managementLIF; + private String svmName; private ProtocolType protocolType; - private Boolean isDisaggregated; + private Boolean isDisaggregated; public OntapStorage(String username, String password, String managementLIF, String svmName, ProtocolType protocolType, Boolean isDisaggregated) { this.username = username; @@ -44,7 +43,7 @@ public String getUsername() { } public void setUsername(String username) { - username = username; + this.username = username; } public String getPassword() { @@ -52,7 +51,7 @@ public String getPassword() { } public void setPassword(String password) { - password = password; + this.password = password; } public String getManagementLIF() { @@ -60,7 +59,7 @@ public String getManagementLIF() { } public void setManagementLIF(String managementLIF) { - managementLIF = managementLIF; + this.managementLIF = managementLIF; } public String getSvmName() { @@ -68,7 +67,7 @@ public String getSvmName() { } public void setSvmName(String svmName) { - svmName = svmName; + this.svmName = svmName; } public ProtocolType getProtocol() { @@ -76,7 +75,7 @@ public ProtocolType getProtocol() { } public void setProtocol(ProtocolType protocolType) { - protocolType = protocolType; + this.protocolType = protocolType; } public Boolean getIsDisaggregated() { @@ -84,6 +83,6 @@ public Boolean getIsDisaggregated() { } public void setIsDisaggregated(Boolean isDisaggregated) { - isDisaggregated = isDisaggregated; + this.isDisaggregated = isDisaggregated; } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java index 0240201b1057..fa2f14692c77 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapPrimaryDatastoreProvider.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.storage.driver.OntapPrimaryDatastoreDriver; import org.apache.cloudstack.storage.lifecycle.OntapPrimaryDatastoreLifecycle; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; import java.util.HashSet; @@ -38,7 +38,7 @@ @Component public class OntapPrimaryDatastoreProvider implements PrimaryDataStoreProvider { - private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreProvider.class); + private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreProvider.class); private OntapPrimaryDatastoreDriver primaryDatastoreDriver; private OntapPrimaryDatastoreLifecycle primaryDatastoreLifecycle; diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java index f5b1b25aa2cc..5fd4c061bdcc 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java @@ -27,38 +27,37 @@ 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; -@Component public class StorageProviderFactory { - private final StorageStrategy storageStrategy; private static final Logger s_logger = LogManager.getLogger(StorageProviderFactory.class); - private StorageProviderFactory(OntapStorage ontapStorage) { + private StorageProviderFactory() { + // Private constructor to prevent instantiation + } + + public static StorageStrategy getStrategy(OntapStorage ontapStorage) { ProtocolType protocol = ontapStorage.getProtocol(); s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol); + + StorageStrategy storageStrategy; switch (protocol) { case NFS: if(!ontapStorage.getIsDisaggregated()) { - this.storageStrategy = new UnifiedNASStrategy(ontapStorage); + storageStrategy = new UnifiedNASStrategy(ontapStorage); } else { throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); } break; case ISCSI: if (!ontapStorage.getIsDisaggregated()) { - this.storageStrategy = new UnifiedSANStrategy(ontapStorage); + storageStrategy = new UnifiedSANStrategy(ontapStorage); } else { throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); } break; default: - this.storageStrategy = null; throw new CloudRuntimeException("Unsupported protocol: " + protocol); } - } - - public static StorageStrategy getStrategy(OntapStorage ontapStorage) { - return new StorageProviderFactory(ontapStorage).storageStrategy; + return storageStrategy; } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index e3b15e81f210..32e36ce5d19f 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -21,6 +21,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import feign.FeignException; +import org.apache.cloudstack.storage.feign.FeignClientFactory; import org.apache.cloudstack.storage.feign.client.JobFeignClient; import org.apache.cloudstack.storage.feign.client.SvmFeignClient; import org.apache.cloudstack.storage.feign.client.VolumeFeignClient; @@ -33,13 +34,13 @@ 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.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.Map; import java.net.URI; import java.util.List; import java.util.Objects; @@ -55,14 +56,11 @@ public abstract class StorageStrategy { @Inject private Utility utils; - @Inject - private VolumeFeignClient volumeFeignClient; - - @Inject - private SvmFeignClient svmFeignClient; - - @Inject - private JobFeignClient jobFeignClient; + // Replace @Inject Feign clients with FeignClientFactory + private final FeignClientFactory feignClientFactory; + private final VolumeFeignClient volumeFeignClient; + private final SvmFeignClient svmFeignClient; + private final JobFeignClient jobFeignClient; protected final OntapStorage storage; @@ -71,11 +69,15 @@ public abstract class StorageStrategy { */ private List aggregates; - private static final Logger s_logger = (Logger) LogManager.getLogger(StorageStrategy.class); + private static final Logger s_logger = LogManager.getLogger(StorageStrategy.class); public StorageStrategy(OntapStorage ontapStorage) { storage = ontapStorage; - + // Initialize FeignClientFactory and create clients + this.feignClientFactory = new FeignClientFactory(); + this.volumeFeignClient = feignClientFactory.createClient(VolumeFeignClient.class); + this.svmFeignClient = feignClientFactory.createClient(SvmFeignClient.class); + this.jobFeignClient = feignClientFactory.createClient(JobFeignClient.class); } // Connect method to validate ONTAP cluster, credentials, protocol, and SVM @@ -100,10 +102,10 @@ public boolean connect() { s_logger.error("SVM " + svmName + " is not in running state."); throw new CloudRuntimeException("SVM " + svmName + " is not in running state."); } - if (Objects.equals(storage.getProtocol(), Constants.NFS) && !svm.getNfsEnabled()) { + if (storage.getProtocol() == ProtocolType.NFS && !svm.getNfsEnabled()) { s_logger.error("NFS protocol is not enabled on SVM " + svmName); throw new CloudRuntimeException("NFS protocol is not enabled on SVM " + svmName); - } else if (Objects.equals(storage.getProtocol(), Constants.ISCSI) && !svm.getIscsiEnabled()) { + } else if (storage.getProtocol() == ProtocolType.ISCSI && !svm.getIscsiEnabled()) { s_logger.error("iSCSI protocol is not enabled on SVM " + svmName); throw new CloudRuntimeException("iSCSI protocol is not enabled on SVM " + svmName); } @@ -127,8 +129,9 @@ public boolean connect() { * Eligible only for Unified ONTAP storage * throw exception in case of disaggregated ONTAP storage * - * @param volumeName - * @param size + * @param volumeName the name of the volume to create + * @param size the size of the volume in bytes + * @return the created Volume object */ public Volume createStorageVolume(String volumeName, Long size) { s_logger.info("Creating volume: " + volumeName + " of size: " + size + " bytes"); @@ -153,7 +156,7 @@ public Volume createStorageVolume(String volumeName, Long size) { // Make the POST API call to create the volume try { // Create URI for POST CreateVolume API - URI url = utils.generateURI(Constants.CREATE_VOLUME); + URI url = utils.generateURI(storage.getManagementLIF(), Constants.CREATE_VOLUME); // Call the VolumeFeignClient to create the volume JobResponse jobResponse = volumeFeignClient.createVolumeWithJob(url, authHeader, volumeRequest); if (jobResponse == null || jobResponse.getJob() == null) { @@ -162,7 +165,7 @@ public Volume createStorageVolume(String volumeName, Long size) { String jobUUID = jobResponse.getJob().getUuid(); //Create URI for GET Job API - url = utils.generateURI(Constants.GET_JOB_BY_UUID); + url = utils.generateURI(storage.getManagementLIF(), Constants.GET_JOB_BY_UUID); int jobRetryCount = 0; Job createVolumeJob = null; while(createVolumeJob == null || !createVolumeJob.getState().equals(Constants.JOB_SUCCESS)) { @@ -199,7 +202,8 @@ public Volume createStorageVolume(String volumeName, Long size) { * Eligible only for Unified ONTAP storage * throw exception in case of disaggregated ONTAP storage * - * @param values + * @param volume the volume to update + * @return the updated Volume object */ public Volume updateStorageVolume(Volume volume) { @@ -212,7 +216,7 @@ public Volume updateStorageVolume(Volume volume) * Eligible only for Unified ONTAP storage * throw exception in case of disaggregated ONTAP storage * - * @param values + * @param volume the volume to delete */ public void deleteStorageVolume(Volume volume) { @@ -220,11 +224,12 @@ public void deleteStorageVolume(Volume volume) } /** - * Updates ONTAP Flex-Volume + * Gets ONTAP Flex-Volume * Eligible only for Unified ONTAP storage * throw exception in case of disaggregated ONTAP storage * - * @param values + * @param volume the volume to retrieve + * @return the retrieved Volume object */ public Volume getStorageVolume(Volume volume) { @@ -238,7 +243,8 @@ public Volume getStorageVolume(Volume volume) * createLun for iSCSI, FC protocols * createFile for NFS3.0 and NFS4.1 protocols * createNameSpace for Nvme/TCP and Nvme/FC protocol - * @param values + * @param cloudstackVolume the CloudStack volume to create + * @return the created CloudStackVolume object */ abstract public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume); @@ -248,7 +254,8 @@ public Volume getStorageVolume(Volume volume) * updateLun for iSCSI, FC protocols * updateFile for NFS3.0 and NFS4.1 protocols * updateNameSpace for Nvme/TCP and Nvme/FC protocol - * @param values + * @param cloudstackVolume the CloudStack volume to update + * @return the updated CloudStackVolume object */ abstract CloudStackVolume updateCloudStackVolume(CloudStackVolume cloudstackVolume); @@ -258,7 +265,7 @@ public Volume getStorageVolume(Volume volume) * deleteLun for iSCSI, FC protocols * deleteFile for NFS3.0 and NFS4.1 protocols * deleteNameSpace for Nvme/TCP and Nvme/FC protocol - * @param values + * @param cloudstackVolume the CloudStack volume to delete */ abstract void deleteCloudStackVolume(CloudStackVolume cloudstackVolume); @@ -268,7 +275,8 @@ public Volume getStorageVolume(Volume volume) * getLun for iSCSI, FC protocols * getFile for NFS3.0 and NFS4.1 protocols * getNameSpace for Nvme/TCP and Nvme/FC protocol - * @param values + * @param cloudstackVolume the CloudStack volume to retrieve + * @return the retrieved CloudStackVolume object */ abstract CloudStackVolume getCloudStackVolume(CloudStackVolume cloudstackVolume); @@ -277,7 +285,8 @@ public Volume getStorageVolume(Volume volume) * createiGroup for iSCSI and FC protocols * createExportPolicy for NFS 3.0 and NFS 4.1 protocols * createSubsystem for Nvme/TCP and Nvme/FC protocols - * @param values + * @param accessGroup the access group to create + * @return the created AccessGroup object */ abstract AccessGroup createAccessGroup(AccessGroup accessGroup); @@ -286,7 +295,7 @@ public Volume getStorageVolume(Volume volume) * deleteiGroup for iSCSI and FC protocols * deleteExportPolicy for NFS 3.0 and NFS 4.1 protocols * deleteSubsystem for Nvme/TCP and Nvme/FC protocols - * @param values + * @param accessGroup the access group to delete */ abstract void deleteAccessGroup(AccessGroup accessGroup); @@ -295,33 +304,8 @@ public Volume getStorageVolume(Volume volume) * updateiGroup example add/remove-Iqn for iSCSI and FC protocols * updateExportPolicy example add/remove-Rule for NFS 3.0 and NFS 4.1 protocols * //TODO for Nvme/TCP and Nvme/FC protocols - * @param values + * @param accessGroup the access group to update + * @return the updated AccessGroup object */ abstract AccessGroup updateAccessGroup(AccessGroup accessGroup); - - /** - * Method encapsulates the behavior based on the opted protocol in subclasses - * getiGroup for iSCSI and FC protocols - * getExportPolicy for NFS 3.0 and NFS 4.1 protocols - * getNameSpace for Nvme/TCP and Nvme/FC protocols - * @param values - */ - abstract AccessGroup getAccessGroup(AccessGroup accessGroup); - - /** - * Method encapsulates the behavior based on the opted protocol in subclasses - * lunMap for iSCSI and FC protocols - * //TODO for Nvme/TCP and Nvme/FC protocols - * @param values - */ - abstract void enableLogicalAccess(Map values); - - /** - * Method encapsulates the behavior based on the opted protocol in subclasses - * lunUnmap for iSCSI and FC protocols - * //TODO for Nvme/TCP and Nvme/FC protocols - * @param values - */ - abstract void disableLogicalAccess(Map values); - } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java index 865a5925168f..851dd87c015a 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -19,36 +19,55 @@ package org.apache.cloudstack.storage.service; +import com.cloud.utils.component.ComponentContext; +import org.apache.cloudstack.storage.feign.FeignClientFactory; +import org.apache.cloudstack.storage.feign.client.NASFeignClient; import org.apache.cloudstack.storage.feign.model.OntapStorage; import org.apache.cloudstack.storage.service.model.AccessGroup; import org.apache.cloudstack.storage.service.model.CloudStackVolume; +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.Map; -public class UnifiedNASStrategy extends NASStrategy{ +public class UnifiedNASStrategy extends NASStrategy { + + private static final Logger s_logger = LogManager.getLogger(UnifiedNASStrategy.class); + private Utility utils; + + // Add missing Feign client setup for NAS operations + private final FeignClientFactory feignClientFactory; + private final NASFeignClient nasFeignClient; + public UnifiedNASStrategy(OntapStorage ontapStorage) { super(ontapStorage); + this.utils = ComponentContext.inject(Utility.class); + // Initialize FeignClientFactory and create NAS client + this.feignClientFactory = new FeignClientFactory(); + this.nasFeignClient = feignClientFactory.createClient(NASFeignClient.class); } @Override public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume) { - //TODO + //TODO: Implement NAS volume creation using nasFeignClient return null; } @Override - public CloudStackVolume updateCloudStackVolume(CloudStackVolume cloudstackVolume) { + CloudStackVolume updateCloudStackVolume(CloudStackVolume cloudstackVolume) { //TODO return null; } @Override - public void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) { - + void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) { + //TODO } @Override - public CloudStackVolume getCloudStackVolume(CloudStackVolume cloudstackVolume) { + CloudStackVolume getCloudStackVolume(CloudStackVolume cloudstackVolume) { //TODO return null; } @@ -61,7 +80,7 @@ public AccessGroup createAccessGroup(AccessGroup accessGroup) { @Override public void deleteAccessGroup(AccessGroup accessGroup) { - + //TODO } @Override @@ -70,20 +89,17 @@ public AccessGroup updateAccessGroup(AccessGroup accessGroup) { return null; } - @Override + // Remove @Override - these methods don't exist in parent classes public AccessGroup getAccessGroup(AccessGroup accessGroup) { //TODO return null; } - @Override void enableLogicalAccess(Map values) { - + //TODO } - @Override void disableLogicalAccess(Map values) { - + //TODO } - } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java index e0f1c16e788f..4ce8e9c4b46d 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java @@ -19,7 +19,9 @@ package org.apache.cloudstack.storage.service; +import com.cloud.utils.component.ComponentContext; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.storage.feign.FeignClientFactory; import org.apache.cloudstack.storage.feign.client.SANFeignClient; import org.apache.cloudstack.storage.feign.model.Lun; import org.apache.cloudstack.storage.feign.model.OntapStorage; @@ -38,10 +40,18 @@ public class UnifiedSANStrategy extends SANStrategy { private static final Logger s_logger = LogManager.getLogger(UnifiedSANStrategy.class); - @Inject private Utility utils; - @Inject private SANFeignClient sanFeignClient; + private Utility utils; + + // Replace @Inject Feign client with FeignClientFactory + private final FeignClientFactory feignClientFactory; + private final SANFeignClient sanFeignClient; + public UnifiedSANStrategy(OntapStorage ontapStorage) { super(ontapStorage); + this.utils = ComponentContext.inject(Utility.class); + // Initialize FeignClientFactory and create SAN client + this.feignClientFactory = new FeignClientFactory(); + this.sanFeignClient = feignClientFactory.createClient(SANFeignClient.class); } @Override @@ -55,7 +65,7 @@ public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume // Get AuthHeader String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); // Create URI for lun creation - URI url = utils.generateURI(Constants.CREATE_LUN); + URI url = utils.generateURI(storage.getManagementLIF(), Constants.CREATE_LUN); //TODO: It is possible that Lun creation will take time and we may need to handle through async job. OntapResponse createdLun = sanFeignClient.createLun(url, authHeader, true, cloudstackVolume.getLun()); if (createdLun == null || createdLun.getRecords() == null || createdLun.getRecords().size() == 0) { @@ -109,20 +119,16 @@ public AccessGroup updateAccessGroup(AccessGroup accessGroup) { return null; } - @Override public AccessGroup getAccessGroup(AccessGroup accessGroup) { //TODO return null; } - @Override void enableLogicalAccess(Map values) { } - @Override void disableLogicalAccess(Map values) { } - } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java index c1fc47aef90b..73c55b105831 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java @@ -21,6 +21,7 @@ import com.cloud.utils.StringUtils; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.component.ComponentContext; 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; @@ -35,20 +36,21 @@ 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 { private static final Logger s_logger = LogManager.getLogger(Utility.class); - @Inject private OntapStorage ontapStorage; - @Inject private PrimaryDataStoreDao storagePoolDao; - @Inject private StoragePoolDetailsDao storagePoolDetailsDao; + private PrimaryDataStoreDao storagePoolDao; + private StoragePoolDetailsDao storagePoolDetailsDao; + + public Utility() { + this.storagePoolDao = ComponentContext.inject(PrimaryDataStoreDao.class); + this.storagePoolDetailsDao = ComponentContext.inject(StoragePoolDetailsDao.class); + } private static final String BASIC = "Basic"; private static final String AUTH_HEADER_COLON = ":"; @@ -65,8 +67,8 @@ public String generateAuthHeader (String username, String password) { return BASIC + StringUtils.SPACE + new String(encodedBytes); } - public URI generateURI (String path) { - String uriString = Constants.HTTPS + ontapStorage.getManagementLIF() + path; + public URI generateURI (String managementLIF, String path) { + String uriString = Constants.HTTPS + managementLIF + path; return URI.create(uriString); } From b855306a74bb6b05a9cb698d4b5017928c9ace4d Mon Sep 17 00:00:00 2001 From: "Locharla, Sandeep" Date: Tue, 4 Nov 2025 10:08:06 +0530 Subject: [PATCH 2/5] CSTACKEX-01: Create Primary Storage pool changes with working code --- .../driver/OntapPrimaryDatastoreDriver.java | 28 ++++++-- .../storage/feign/FeignClientFactory.java | 4 +- .../storage/feign/FeignConfiguration.java | 26 +++++-- .../feign/client/AggregateFeignClient.java | 13 ++-- .../feign/client/ClusterFeignClient.java | 5 +- .../storage/feign/client/JobFeignClient.java | 7 +- .../storage/feign/client/NASFeignClient.java | 35 +++++---- .../storage/feign/client/SANFeignClient.java | 44 ++++++------ .../storage/feign/client/SvmFeignClient.java | 15 ++-- .../feign/client/VolumeFeignClient.java | 26 +++---- .../cloudstack/storage/feign/model/Svm.java | 25 +++++-- .../OntapPrimaryDatastoreLifecycle.java | 72 +++++++++++++++---- .../provider/StorageProviderFactory.java | 26 +++---- .../storage/service/StorageStrategy.java | 54 ++++++++------ .../storage/service/UnifiedNASStrategy.java | 12 ++-- .../storage/service/UnifiedSANStrategy.java | 15 ++-- .../cloudstack/storage/utils/Constants.java | 5 +- .../cloudstack/storage/utils/Utility.java | 46 +++--------- 18 files changed, 258 insertions(+), 200 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java index 7895cf1a9dfe..3966527b47f6 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java @@ -44,6 +44,8 @@ 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; @@ -51,7 +53,6 @@ import org.apache.cloudstack.storage.utils.Utility; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.cloud.utils.component.ComponentContext; import javax.inject.Inject; import java.util.HashMap; @@ -64,9 +65,8 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver { private Utility utils; @Inject private StoragePoolDetailsDao storagePoolDetailsDao; @Inject private PrimaryDataStoreDao storagePoolDao; - public OntapPrimaryDatastoreDriver() { - utils = ComponentContext.inject(Utility.class); + utils = new Utility(); } @Override public Map getCapabilities() { @@ -131,7 +131,7 @@ private String createCloudStackVolumeForTypeVolume(DataStore dataStore, DataObje throw new CloudRuntimeException("createCloudStackVolume : Storage Pool not found for id: " + dataStore.getId()); } Map details = storagePoolDetailsDao.listDetailsKeyPairs(dataStore.getId()); - StorageStrategy storageStrategy = utils.getStrategyByStoragePoolDetails(details); + StorageStrategy storageStrategy = 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); @@ -273,4 +273,24 @@ public boolean isStorageSupportHA(Storage.StoragePoolType type) { public void detachVolumeFromAllStorageNodes(Volume volume) { } + + public StorageStrategy getStrategyByStoragePoolDetails(Map 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"); + } + } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java index f89198e49353..4afcb3787590 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java @@ -33,7 +33,7 @@ public FeignClientFactory(FeignConfiguration feignConfiguration) { this.feignConfiguration = feignConfiguration; } - public T createClient(Class clientClass) { + public T createClient(Class clientClass, String baseURL) { return Feign.builder() .client(feignConfiguration.createClient()) .encoder(feignConfiguration.createEncoder()) @@ -41,6 +41,6 @@ public T createClient(Class clientClass) { // .logger(feignConfiguration.createLogger()) .retryer(feignConfiguration.createRetryer()) .requestInterceptor(feignConfiguration.createRequestInterceptor()) - .target(clientClass, "https://placeholder.com"); + .target(clientClass, baseURL); } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java index 42227a7295dd..f1b821b14135 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java @@ -30,6 +30,7 @@ import feign.codec.DecodeException; import feign.codec.EncodeException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.core.JsonProcessingException; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.ssl.NoopHostnameVerifier; @@ -45,7 +46,6 @@ import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.concurrent.TimeUnit; public class FeignConfiguration { @@ -55,7 +55,13 @@ public class FeignConfiguration { private final int retryMaxInterval = 5; private final String ontapFeignMaxConnection = "80"; private final String ontapFeignMaxConnectionPerRoute = "20"; - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper; + + public FeignConfiguration() { + this.objectMapper = new ObjectMapper(); + // Configure ObjectMapper to ignore unknown properties like _links + this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } public Client createClient() { int maxConn; @@ -115,9 +121,13 @@ public Encoder createEncoder() { return new Encoder() { @Override public void encode(Object object, Type bodyType, feign.RequestTemplate template) throws EncodeException { + if (object == null) { + template.body((byte[]) null, StandardCharsets.UTF_8); + return; + } try { - String json = objectMapper.writeValueAsString(object); - template.body(Arrays.toString(json.getBytes(StandardCharsets.UTF_8))); + byte[] jsonBytes = objectMapper.writeValueAsBytes(object); + template.body(jsonBytes, StandardCharsets.UTF_8); template.header("Content-Type", "application/json"); } catch (JsonProcessingException e) { throw new EncodeException("Error encoding object to JSON", e); @@ -133,10 +143,13 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc if (response.body() == null) { return null; } - try { - String json = new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8); + String json = null; + try (var bodyStream = response.body().asInputStream()) { + json = new String(bodyStream.readAllBytes(), StandardCharsets.UTF_8); + 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); throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e); } } @@ -147,3 +160,4 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc // return new Slf4jLogger(); // } } + diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java index 12ef7316df10..f756c3d32f18 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java @@ -24,15 +24,14 @@ import feign.Headers; import feign.Param; import feign.RequestLine; -import java.net.URI; public interface AggregateFeignClient { - @RequestLine("GET /") - @Headers("Authorization: {authHeader}") - OntapResponse getAggregateResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @RequestLine("GET /api/storage/aggregates") + @Headers({"Authorization: {authHeader}"}) + OntapResponse getAggregateResponse(@Param("authHeader") String authHeader); - @RequestLine("GET /{uuid}") - @Headers("Authorization: {authHeader}") - Aggregate getAggregateByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @RequestLine("GET /api/storage/aggregates/{uuid}") + @Headers({"Authorization: {authHeader}"}) + Aggregate getAggregateByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java index dcd12e60d281..582fb58e6f3b 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java @@ -23,11 +23,10 @@ import feign.Headers; import feign.Param; import feign.RequestLine; -import java.net.URI; public interface ClusterFeignClient { - @RequestLine("GET /") + @RequestLine("GET /api/cluster") @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) - Cluster getCluster(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords); + Cluster getCluster(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java index ea3c53728280..535e112d9eb3 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java @@ -22,11 +22,10 @@ import feign.Headers; import feign.Param; import feign.RequestLine; -import java.net.URI; public interface JobFeignClient { - @RequestLine("GET /{uuid}") - @Headers("Authorization: {authHeader}") - Job getJobByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @RequestLine("GET /api/cluster/jobs/{uuid}") + @Headers({"Authorization: {authHeader}"}) + Job getJobByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java index 96053d6a6459..f625e6360b65 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java @@ -25,33 +25,32 @@ import feign.Headers; import feign.Param; import feign.RequestLine; -import java.net.URI; public interface NASFeignClient { // File Operations @RequestLine("GET /{volumeUuid}/files/{path}") - @Headers("Authorization: {authHeader}") - OntapResponse getFileResponse(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + OntapResponse getFileResponse(@Param("authHeader") String authHeader, @Param("volumeUuid") String volumeUUID, @Param("path") String filePath); @RequestLine("DELETE /{volumeUuid}/files/{path}") - @Headers("Authorization: {authHeader}") - void deleteFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + void deleteFile(@Param("authHeader") String authHeader, @Param("volumeUuid") String volumeUUID, @Param("path") String filePath); @RequestLine("PATCH /{volumeUuid}/files/{path}") - @Headers("Authorization: {authHeader}") - void updateFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + void updateFile(@Param("authHeader") String authHeader, @Param("volumeUuid") String volumeUUID, @Param("path") String filePath, @Param("fileInfo") FileInfo fileInfo); @RequestLine("POST /{volumeUuid}/files/{path}") - @Headers("Authorization: {authHeader}") - void createFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + void createFile(@Param("authHeader") String authHeader, @Param("volumeUuid") String volumeUUID, @Param("path") String filePath, @Param("file") FileInfo file); @@ -59,27 +58,27 @@ void createFile(@Param("uri") URI uri, @Param("authHeader") String authHeader, // Export Policy Operations @RequestLine("POST /") @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) - ExportPolicy createExportPolicy(@Param("uri") URI uri, @Param("authHeader") String authHeader, + ExportPolicy createExportPolicy(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, @Param("exportPolicy") ExportPolicy exportPolicy); @RequestLine("GET /") - @Headers("Authorization: {authHeader}") - OntapResponse getExportPolicyResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @Headers({"Authorization: {authHeader}"}) + OntapResponse getExportPolicyResponse(@Param("authHeader") String authHeader); @RequestLine("GET /{id}") - @Headers("Authorization: {authHeader}") - OntapResponse getExportPolicyById(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + OntapResponse getExportPolicyById(@Param("authHeader") String authHeader, @Param("id") String id); @RequestLine("DELETE /{id}") - @Headers("Authorization: {authHeader}") - void deleteExportPolicyById(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + void deleteExportPolicyById(@Param("authHeader") String authHeader, @Param("id") String id); @RequestLine("PATCH /{id}") - @Headers("Authorization: {authHeader}") - OntapResponse updateExportPolicy(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + OntapResponse updateExportPolicy(@Param("authHeader") String authHeader, @Param("id") String id, @Param("request") ExportPolicy request); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java index e750521a068b..23e5164b35c9 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java @@ -32,64 +32,64 @@ public interface SANFeignClient { // LUN Operation APIs @RequestLine("POST /") @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) - OntapResponse createLun(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + OntapResponse createLun(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, @Param("lun") Lun lun); @RequestLine("GET /") - @Headers("Authorization: {authHeader}") - OntapResponse getLunResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @Headers({"Authorization: {authHeader}"}) + OntapResponse getLunResponse(@Param("authHeader") String authHeader); @RequestLine("GET /{uuid}") - @Headers("Authorization: {authHeader}") - Lun getLunByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @Headers({"Authorization: {authHeader}"}) + Lun getLunByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); @RequestLine("PATCH /{uuid}") - @Headers("Authorization: {authHeader}") - void updateLun(@Param("uri") URI uri, @Param("authHeader") String authHeader, @Param("uuid") String uuid, @Param("lun") Lun lun); + @Headers({"Authorization: {authHeader}"}) + void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, @Param("lun") Lun lun); @RequestLine("DELETE /{uuid}") - @Headers("Authorization: {authHeader}") - void deleteLun(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @Headers({"Authorization: {authHeader}"}) + void deleteLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid); // iGroup Operation APIs @RequestLine("POST /") @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) - OntapResponse createIgroup(@Param("uri") URI uri, @Param("authHeader") String authHeader, + OntapResponse createIgroup(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, @Param("igroupRequest") Igroup igroupRequest); @RequestLine("GET /") - @Headers("Authorization: {authHeader}") - OntapResponse getIgroupResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @Headers({"Authorization: {authHeader}"}) + OntapResponse getIgroupResponse(@Param("authHeader") String authHeader, @Param("uuid") String uuid); @RequestLine("GET /{uuid}") - @Headers("Authorization: {authHeader}") - Igroup getIgroupByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @Headers({"Authorization: {authHeader}"}) + Igroup getIgroupByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); @RequestLine("DELETE /{uuid}") - @Headers("Authorization: {authHeader}") + @Headers({"Authorization: {authHeader}"}) void deleteIgroup(@Param("baseUri") URI baseUri, @Param("authHeader") String authHeader, @Param("uuid") String uuid); @RequestLine("POST /{uuid}/igroups") @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) - OntapResponse addNestedIgroups(@Param("uri") URI uri, @Param("authHeader") String authHeader, + OntapResponse addNestedIgroups(@Param("authHeader") String authHeader, @Param("uuid") String uuid, @Param("igroupNestedRequest") Igroup igroupNestedRequest, @Param("returnRecords") boolean returnRecords); // LUN Maps Operation APIs @RequestLine("POST /") - @Headers("Authorization: {authHeader}") - OntapResponse createLunMap(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("lunMap") LunMap lunMap); + @Headers({"Authorization: {authHeader}"}) + OntapResponse createLunMap(@Param("authHeader") String authHeader, @Param("lunMap") LunMap lunMap); @RequestLine("GET /") - @Headers("Authorization: {authHeader}") - OntapResponse getLunMapResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @Headers({"Authorization: {authHeader}"}) + OntapResponse getLunMapResponse(@Param("authHeader") String authHeader); @RequestLine("DELETE /{lunUuid}/{igroupUuid}") - @Headers("Authorization: {authHeader}") - void deleteLunMap(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, + @Headers({"Authorization: {authHeader}"}) + void deleteLunMap(@Param("authHeader") String authHeader, @Param("lunUuid") String lunUuid, @Param("igroupUuid") String igroupUuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java index 881fe8f27664..8a887a9ee7b8 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java @@ -19,20 +19,21 @@ package org.apache.cloudstack.storage.feign.client; +import feign.QueryMap; import org.apache.cloudstack.storage.feign.model.Svm; 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; public interface SvmFeignClient { - @RequestLine("GET /") - @Headers("Authorization: {authHeader}") - OntapResponse getSvmResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader); + @RequestLine("GET /api/svm/svms") + @Headers({"Authorization: {authHeader}"}) + OntapResponse getSvmResponse(@QueryMap Map queryMap, @Param("authHeader") String authHeader); - @RequestLine("GET /{uuid}") - @Headers("Authorization: {authHeader}") - Svm getSvmByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @RequestLine("GET /api/svm/svms/{uuid}") + @Headers({"Authorization: {authHeader}"}) + Svm getSvmByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java index 41e05caf9bb1..9a2c76639221 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java @@ -23,23 +23,23 @@ import feign.Headers; import feign.Param; import feign.RequestLine; -import java.net.URI; public interface VolumeFeignClient { - @RequestLine("DELETE /{uuid}") - @Headers("Authorization: {authHeader}") - void deleteVolume(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @RequestLine("DELETE /api/storage/volumes/{uuid}") + @Headers({"Authorization: {authHeader}"}) + void deleteVolume(@Param("authHeader") String authHeader, @Param("uuid") String uuid); - @RequestLine("POST /") - @Headers("Authorization: {authHeader}") - JobResponse createVolumeWithJob(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("volumeRequest") Volume volumeRequest); + @RequestLine("POST /api/storage/volumes") + @Headers({"Authorization: {authHeader}"}) + JobResponse createVolumeWithJob(@Param("authHeader") String authHeader, Volume volumeRequest); - @RequestLine("GET /{uuid}") - @Headers("Authorization: {authHeader}") - Volume getVolumeByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid); + @RequestLine("GET /api/storage/volumes/{uuid}") + @Headers({"Authorization: {authHeader}"}) + Volume getVolumeByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); - @RequestLine("PATCH /{uuid}") - @Headers({"accept: {acceptHeader}", "Authorization: {authHeader}"}) - JobResponse updateVolumeRebalancing(@Param("baseURL") URI baseURL, @Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, @Param("volumeRequest") Volume volumeRequest); + @RequestLine("PATCH /api/storage/volumes/{uuid}") + @Headers({"Accept: {acceptHeader}", "Authorization: {authHeader}"}) + JobResponse updateVolumeRebalancing(@Param("acceptHeader") String acceptHeader, @Param("uuid") String uuid, Volume volumeRequest); } + diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java index e89c1f8426a6..c053c8959cbb 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -48,9 +49,12 @@ public class Svm { @JsonProperty("aggregates_delegated") private Boolean aggregatesDelegated = null; - @JsonProperty("state.value") + @JsonProperty("state") private String state = null; + @JsonIgnore + private Links links = null; + public String getUuid() { return uuid; } @@ -75,7 +79,6 @@ public void setIscsiEnabled(Boolean iscsiEnabled) { this.iscsiEnabled = iscsiEnabled; } - public Boolean getFcpEnabled() { return fcpEnabled; } @@ -84,7 +87,6 @@ public void setFcpEnabled(Boolean fcpEnabled) { this.fcpEnabled = fcpEnabled; } - public Boolean getNfsEnabled() { return nfsEnabled; } @@ -93,7 +95,6 @@ public void setNfsEnabled(Boolean nfsEnabled) { this.nfsEnabled = nfsEnabled; } - public List getAggregates() { return aggregates; } @@ -102,7 +103,6 @@ public void setAggregates(List aggregates) { this.aggregates = aggregates; } - public Boolean getAggregatesDelegated() { return aggregatesDelegated; } @@ -111,7 +111,6 @@ public void setAggregatesDelegated(Boolean aggregatesDelegated) { this.aggregatesDelegated = aggregatesDelegated; } - public String getState() { return state; } @@ -120,6 +119,14 @@ public void setState(String state) { this.state = state; } + public Links getLinks() { + return links; + } + + public void setLinks(Links links) { + this.links = links; + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; @@ -131,4 +138,8 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hashCode(getUuid()); } -} + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Links { } + +} \ No newline at end of file diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 1538cebfd373..83137cfda27d 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -58,7 +58,7 @@ public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycl @Inject private StorageManager _storageMgr; @Inject private ResourceManager _resourceMgr; @Inject private PrimaryDataStoreHelper _dataStoreHelper; - private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class); + private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class); /** * Creates primary storage on NetApp storage @@ -70,23 +70,30 @@ public DataStore initialize(Map dsInfos) { if (dsInfos == null) { throw new CloudRuntimeException("Datastore info map is null, cannot create primary storage"); } - String url = dsInfos.get("url").toString(); // TODO: Decide on whether should the customer enter just the Management LIF IP or https://ManagementLIF - Long zoneId = dsInfos.get("zoneId").toString().trim().isEmpty() ? null : (Long)dsInfos.get("zoneId"); - Long podId = dsInfos.get("podId").toString().trim().isEmpty() ? null : (Long)dsInfos.get("zoneId"); - Long clusterId = dsInfos.get("clusterId").toString().trim().isEmpty() ? null : (Long)dsInfos.get("clusterId"); - String storagePoolName = dsInfos.get("name").toString().trim(); - String providerName = dsInfos.get("providerName").toString().trim(); - String tags = dsInfos.get("tags").toString().trim(); + String url = (String)dsInfos.get("url"); + Long zoneId = (Long)dsInfos.get("zoneId"); + Long podId = (Long)dsInfos.get("podId"); + Long clusterId = (Long)dsInfos.get("clusterId"); + String storagePoolName = (String)dsInfos.get("name"); + String providerName = (String)dsInfos.get("providerName"); + Long capacityBytes = (Long)dsInfos.get("capacityBytes"); + String tags = (String)dsInfos.get("tags"); Boolean isTagARule = (Boolean) dsInfos.get("isTagARule"); - String scheme = dsInfos.get("scheme").toString(); +// String scheme = dsInfos.get("scheme").toString(); +// s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + +// ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme); s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + - ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme); + ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId); // Additional details requested for ONTAP primary storage pool creation @SuppressWarnings("unchecked") Map details = (Map)dsInfos.get("details"); // Validations + if (capacityBytes == null || capacityBytes <= 0) { + throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0."); + } + if (podId == null ^ clusterId == null) { throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage"); } @@ -117,21 +124,51 @@ public DataStore initialize(Map dsInfos) { parameters.setHypervisorType(clusterVO.getHypervisorType()); } + // Get ONTAP details from the URL + Map storageDetails = Map.of( + Constants.USERNAME, "", + Constants.PASSWORD, "", + Constants.SVM_NAME, "", + Constants.PROTOCOL, "", + Constants.MANAGEMENT_LIF, "", + Constants.SIZE, "", + Constants.IS_DISAGGREGATED, "" + ); + + String[] urlDetails = url.split(";"); + for(int m =0; m < urlDetails.length; m++) { + String[] kvs = urlDetails[m].split("="); + if(kvs.length == 2) { + details.put(kvs[0], kvs[1]); + } + } + + //managementLIF=;username=;password=;svmName=;protocol=;size=;isDisaggregated=; + + for (String key : storageDetails.keySet()) { + if (!details.containsKey(key) || details.get(key).isEmpty()) { + throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail: " + key); + } + } + + // TODO: While testing need to check what does this actually do and if the fields corresponding to each protocol should also be set // TODO: scheme could be 'custom' in our case and we might have to ask 'protocol' separately to the user - ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL).toLowerCase()); + String path = ""; + ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL)); switch (protocol) { case NFS: parameters.setType(Storage.StoragePoolType.NetworkFilesystem); + path = "/"+ storagePoolName; break; case ISCSI: parameters.setType(Storage.StoragePoolType.Iscsi); break; default: - throw new CloudRuntimeException("Unsupported protocol: " + scheme + ", cannot create primary storage"); + throw new CloudRuntimeException("Unsupported protocol: " + protocol + ", cannot create primary storage"); } - details.put(Constants.MANAGEMENT_LIF, url); +// details.put(Constants.MANAGEMENT_LIF, url); // Validate the ONTAP details if(details.get(Constants.IS_DISAGGREGATED) == null || details.get(Constants.IS_DISAGGREGATED).isEmpty()) { @@ -140,16 +177,19 @@ public DataStore initialize(Map dsInfos) { OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD), details.get(Constants.MANAGEMENT_LIF), details.get(Constants.SVM_NAME), protocol, - Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED))); + Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED).toLowerCase())); StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage); boolean isValid = storageStrategy.connect(); if (isValid) { // String volumeName = storagePoolName + "_vol"; //TODO: Figure out a better naming convention - storageStrategy.createStorageVolume(storagePoolName, Long.parseLong((details.get("size")))); // TODO: size should be in bytes, so see if conversion is needed + storageStrategy.createStorageVolume(storagePoolName, Long.parseLong((details.get(Constants.SIZE)))); // TODO: size should be in bytes, so see if conversion is needed } else { throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage"); } + parameters.setHost(details.get(Constants.MANAGEMENT_LIF)); + parameters.setPort(443); + parameters.setPath(path); parameters.setTags(tags); parameters.setIsTagARule(isTagARule); parameters.setDetails(details); @@ -160,6 +200,8 @@ public DataStore initialize(Map dsInfos) { parameters.setName(storagePoolName); parameters.setProviderName(providerName); parameters.setManaged(true); + parameters.setCapacityBytes(capacityBytes); + parameters.setUsedBytes(0); return _dataStoreHelper.createPrimaryDataStore(parameters); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java index 5fd4c061bdcc..e9448ec16ded 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java @@ -28,36 +28,30 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; + public class StorageProviderFactory { private static final Logger s_logger = LogManager.getLogger(StorageProviderFactory.class); - private StorageProviderFactory() { - // Private constructor to prevent instantiation - } - public static StorageStrategy getStrategy(OntapStorage ontapStorage) { ProtocolType protocol = ontapStorage.getProtocol(); s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol); - - StorageStrategy storageStrategy; switch (protocol) { case NFS: - if(!ontapStorage.getIsDisaggregated()) { - storageStrategy = new UnifiedNASStrategy(ontapStorage); - } else { - throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + if (!ontapStorage.getIsDisaggregated()) { + UnifiedNASStrategy unifiedNASStrategy = new UnifiedNASStrategy(ontapStorage); + unifiedNASStrategy.setOntapStorage(ontapStorage); + return unifiedNASStrategy; } - break; + throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); case ISCSI: if (!ontapStorage.getIsDisaggregated()) { - storageStrategy = new UnifiedSANStrategy(ontapStorage); - } else { - throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + UnifiedSANStrategy unifiedSANStrategy = new UnifiedSANStrategy(ontapStorage); + unifiedSANStrategy.setOntapStorage(ontapStorage); + return unifiedSANStrategy; } - break; + throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); default: throw new CloudRuntimeException("Unsupported protocol: " + protocol); } - return storageStrategy; } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index 32e36ce5d19f..1d80a46513c3 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -34,15 +34,13 @@ 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.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.net.URI; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -53,8 +51,7 @@ * Supported platform: Unified and Disaggregated */ public abstract class StorageStrategy { - @Inject - private Utility utils; + private final Utility utils; // Replace @Inject Feign clients with FeignClientFactory private final FeignClientFactory feignClientFactory; @@ -62,7 +59,7 @@ public abstract class StorageStrategy { private final SvmFeignClient svmFeignClient; private final JobFeignClient jobFeignClient; - protected final OntapStorage storage; + protected OntapStorage storage; /** * Presents aggregate object for the unified storage, not eligible for disaggregated @@ -73,39 +70,52 @@ public abstract class StorageStrategy { public StorageStrategy(OntapStorage ontapStorage) { storage = ontapStorage; + String baseURL = Constants.HTTPS + storage.getManagementLIF(); + s_logger.info("Initializing StorageStrategy with base URL: " + baseURL); + this.utils = new Utility(); // Initialize FeignClientFactory and create clients this.feignClientFactory = new FeignClientFactory(); - this.volumeFeignClient = feignClientFactory.createClient(VolumeFeignClient.class); - this.svmFeignClient = feignClientFactory.createClient(SvmFeignClient.class); - this.jobFeignClient = feignClientFactory.createClient(JobFeignClient.class); + this.volumeFeignClient = feignClientFactory.createClient(VolumeFeignClient.class, baseURL); + this.svmFeignClient = feignClientFactory.createClient(SvmFeignClient.class, baseURL); + this.jobFeignClient = feignClientFactory.createClient(JobFeignClient.class, baseURL); } // Connect method to validate ONTAP cluster, credentials, protocol, and SVM public boolean connect() { - s_logger.info("Attempting to connect to ONTAP cluster at " + storage.getManagementLIF()); + s_logger.info("Attempting to connect to ONTAP cluster at " + storage.getManagementLIF() + " and validate SVM " + + storage.getSvmName() + ", username " + storage.getUsername() + ", password " + storage.getPassword() + ", protocol " + storage.getProtocol()); //Get AuthHeader String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); String svmName = storage.getSvmName(); try { // Call the SVM API to check if the SVM exists Svm svm = new Svm(); - URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GET_SVMs + "?name=" + svmName); - OntapResponse svms = svmFeignClient.getSvmResponse(url, authHeader); - if (svms != null && svms.getRecords() != null && !svms.getRecords().isEmpty()) { - svm = svms.getRecords().get(0); - } else { - throw new CloudRuntimeException("No SVM found on the ONTAP cluster by the name" + svmName + "."); + try { + s_logger.info("Fetching the SVM details..."); + if (svmFeignClient == null) { + throw new CloudRuntimeException("SVM Feign client is not initialized."); + } + Map queryParams = Map.of("name", svmName, "fields", "aggregates,state"); + OntapResponse svms = svmFeignClient.getSvmResponse(queryParams, authHeader); + if (svms != null && svms.getRecords() != null && !svms.getRecords().isEmpty()) { + svm = svms.getRecords().get(0); + } else { + throw new CloudRuntimeException("No SVM found on the ONTAP cluster by the name" + svmName + "."); + } + } catch (FeignException.FeignClientException e) { + throw new CloudRuntimeException("Failed to fetch SVM details: " + e.getMessage()); } // Validations + s_logger.info("Validating SVM state and protocol settings..."); if (!Objects.equals(svm.getState(), Constants.RUNNING)) { s_logger.error("SVM " + svmName + " is not in running state."); throw new CloudRuntimeException("SVM " + svmName + " is not in running state."); } - if (storage.getProtocol() == ProtocolType.NFS && !svm.getNfsEnabled()) { + if (Objects.equals(storage.getProtocol(), Constants.NFS) && !svm.getNfsEnabled()) { s_logger.error("NFS protocol is not enabled on SVM " + svmName); throw new CloudRuntimeException("NFS protocol is not enabled on SVM " + svmName); - } else if (storage.getProtocol() == ProtocolType.ISCSI && !svm.getIscsiEnabled()) { + } else if (Objects.equals(storage.getProtocol(), Constants.ISCSI) && !svm.getIscsiEnabled()) { s_logger.error("iSCSI protocol is not enabled on SVM " + svmName); throw new CloudRuntimeException("iSCSI protocol is not enabled on SVM " + svmName); } @@ -117,7 +127,7 @@ public boolean connect() { this.aggregates = aggrs; s_logger.info("Successfully connected to ONTAP cluster and validated ONTAP details provided"); } catch (Exception e) { - throw new CloudRuntimeException("Failed to connect to ONTAP cluster: " + e.getMessage()); + throw new CloudRuntimeException("Failed to connect to ONTAP cluster: " + e); } return true; } @@ -156,16 +166,14 @@ public Volume createStorageVolume(String volumeName, Long size) { // Make the POST API call to create the volume try { // Create URI for POST CreateVolume API - URI url = utils.generateURI(storage.getManagementLIF(), Constants.CREATE_VOLUME); // Call the VolumeFeignClient to create the volume - JobResponse jobResponse = volumeFeignClient.createVolumeWithJob(url, authHeader, volumeRequest); + JobResponse jobResponse = volumeFeignClient.createVolumeWithJob(authHeader, volumeRequest); if (jobResponse == null || jobResponse.getJob() == null) { throw new CloudRuntimeException("Failed to initiate volume creation for " + volumeName); } String jobUUID = jobResponse.getJob().getUuid(); //Create URI for GET Job API - url = utils.generateURI(storage.getManagementLIF(), Constants.GET_JOB_BY_UUID); int jobRetryCount = 0; Job createVolumeJob = null; while(createVolumeJob == null || !createVolumeJob.getState().equals(Constants.JOB_SUCCESS)) { @@ -175,7 +183,7 @@ public Volume createStorageVolume(String volumeName, Long size) { } try { - createVolumeJob = jobFeignClient.getJobByUUID(url, authHeader, jobUUID); + createVolumeJob = jobFeignClient.getJobByUUID(authHeader, jobUUID); if (createVolumeJob == null) { s_logger.warn("Job with UUID " + jobUUID + " not found. Retrying..."); } else if (createVolumeJob.getState().equals(Constants.JOB_FAILURE)) { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java index 851dd87c015a..a4f9db383c6c 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -19,17 +19,16 @@ package org.apache.cloudstack.storage.service; -import com.cloud.utils.component.ComponentContext; import org.apache.cloudstack.storage.feign.FeignClientFactory; import org.apache.cloudstack.storage.feign.client.NASFeignClient; import org.apache.cloudstack.storage.feign.model.OntapStorage; 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.util.Map; public class UnifiedNASStrategy extends NASStrategy { @@ -43,10 +42,15 @@ public class UnifiedNASStrategy extends NASStrategy { public UnifiedNASStrategy(OntapStorage ontapStorage) { super(ontapStorage); - this.utils = ComponentContext.inject(Utility.class); + String baseURL = Constants.HTTPS + ontapStorage.getManagementLIF(); + this.utils = new Utility(); // Initialize FeignClientFactory and create NAS client this.feignClientFactory = new FeignClientFactory(); - this.nasFeignClient = feignClientFactory.createClient(NASFeignClient.class); + this.nasFeignClient = feignClientFactory.createClient(NASFeignClient.class, baseURL); + } + + public void setOntapStorage(OntapStorage ontapStorage) { + this.storage = ontapStorage; } @Override diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java index 4ce8e9c4b46d..fd9cbc1ef87b 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java @@ -19,7 +19,6 @@ package org.apache.cloudstack.storage.service; -import com.cloud.utils.component.ComponentContext; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.storage.feign.FeignClientFactory; import org.apache.cloudstack.storage.feign.client.SANFeignClient; @@ -33,8 +32,6 @@ 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 { @@ -48,10 +45,15 @@ public class UnifiedSANStrategy extends SANStrategy { public UnifiedSANStrategy(OntapStorage ontapStorage) { super(ontapStorage); - this.utils = ComponentContext.inject(Utility.class); + String baseURL = Constants.HTTPS + ontapStorage.getManagementLIF(); + this.utils = new Utility(); // Initialize FeignClientFactory and create SAN client this.feignClientFactory = new FeignClientFactory(); - this.sanFeignClient = feignClientFactory.createClient(SANFeignClient.class); + this.sanFeignClient = feignClientFactory.createClient(SANFeignClient.class, baseURL); + } + + public void setOntapStorage(OntapStorage ontapStorage) { + this.storage = ontapStorage; } @Override @@ -65,9 +67,8 @@ public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume // Get AuthHeader String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); // Create URI for lun creation - URI url = utils.generateURI(storage.getManagementLIF(), Constants.CREATE_LUN); //TODO: It is possible that Lun creation will take time and we may need to handle through async job. - OntapResponse createdLun = sanFeignClient.createLun(url, authHeader, true, cloudstackVolume.getLun()); + OntapResponse createdLun = sanFeignClient.createLun(authHeader, true, cloudstackVolume.getLun()); 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()); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java index 61674db7c8b0..3669e9bcf38f 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java @@ -23,6 +23,7 @@ public class Constants { public static final String NFS = "nfs"; public static final String ISCSI = "iscsi"; + public static final String SIZE = "size"; public static final String PROTOCOL = "protocol"; public static final String SVM_NAME = "svmName"; public static final String USERNAME = "username"; @@ -47,9 +48,5 @@ public class Constants { 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"; } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java index 73c55b105831..c90f14af7d39 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java @@ -21,36 +21,22 @@ import com.cloud.utils.StringUtils; import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.component.ComponentContext; 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.util.Base64Utils; -import java.net.URI; import java.util.Map; public class Utility { private static final Logger s_logger = LogManager.getLogger(Utility.class); - private PrimaryDataStoreDao storagePoolDao; - private StoragePoolDetailsDao storagePoolDetailsDao; - - public Utility() { - this.storagePoolDao = ComponentContext.inject(PrimaryDataStoreDao.class); - this.storagePoolDetailsDao = ComponentContext.inject(StoragePoolDetailsDao.class); - } private static final String BASIC = "Basic"; private static final String AUTH_HEADER_COLON = ":"; @@ -67,10 +53,14 @@ public String generateAuthHeader (String username, String password) { return BASIC + StringUtils.SPACE + new String(encodedBytes); } - public URI generateURI (String managementLIF, String path) { - String uriString = Constants.HTTPS + managementLIF + path; - return URI.create(uriString); - } +// public URI generateURI (String managementLIF, String path) { +// if (path == null || path.isEmpty() || path.equals(StringUtils.SPACE) || path.equals("/")) { +// String uriString = Constants.HTTPS + managementLIF; +// return URI.create(uriString); +// } +// String uriString = Constants.HTTPS + managementLIF + path; +// return URI.create(uriString); +// } public CloudStackVolume createCloudStackVolumeRequestByProtocol(StoragePoolVO storagePool, Map details, DataObject dataObject) { CloudStackVolume cloudStackVolumeRequest = null; @@ -109,24 +99,4 @@ public CloudStackVolume createCloudStackVolumeRequestByProtocol(StoragePoolVO st throw new CloudRuntimeException("createCloudStackVolumeRequestByProtocol: Unsupported protocol " + protocol); } } - - public StorageStrategy getStrategyByStoragePoolDetails(Map 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"); - } - } } From fd67a41ad289ab07b60b76e1195aef9bf77605dd Mon Sep 17 00:00:00 2001 From: "Locharla, Sandeep" Date: Wed, 5 Nov 2025 08:09:38 +0530 Subject: [PATCH 3/5] CSTACKEX-01: Addressed all review comments and updated some code --- .../driver/OntapPrimaryDatastoreDriver.java | 9 +- .../storage/feign/FeignClientFactory.java | 1 - .../storage/feign/FeignConfiguration.java | 56 +++-------- .../storage/feign/client/NASFeignClient.java | 10 +- .../storage/feign/client/SANFeignClient.java | 12 +-- .../storage/feign/model/Aggregate.java | 2 + .../storage/feign/model/Cluster.java | 2 + .../storage/feign/model/ExportPolicy.java | 2 + .../storage/feign/model/ExportRule.java | 2 + .../storage/feign/model/FileInfo.java | 2 + .../storage/feign/model/Igroup.java | 2 + .../storage/feign/model/Initiator.java | 2 + .../cloudstack/storage/feign/model/Job.java | 2 + .../cloudstack/storage/feign/model/Lun.java | 2 + .../storage/feign/model/LunMap.java | 2 + .../storage/feign/model/LunSpace.java | 2 + .../cloudstack/storage/feign/model/Nas.java | 2 + .../storage/feign/model/OntapStorage.java | 36 ++----- .../storage/feign/model/Policy.java | 2 + .../cloudstack/storage/feign/model/Qos.java | 2 + .../cloudstack/storage/feign/model/Svm.java | 2 + .../storage/feign/model/Version.java | 2 + .../storage/feign/model/Volume.java | 2 + .../storage/feign/model/VolumeQosPolicy.java | 2 + .../storage/feign/model/VolumeSpace.java | 2 + .../feign/model/VolumeSpaceLogicalSpace.java | 2 + .../OntapPrimaryDatastoreLifecycle.java | 93 ++++++++++--------- .../provider/StorageProviderFactory.java | 2 +- .../storage/service/StorageStrategy.java | 60 +++++++----- .../storage/service/UnifiedNASStrategy.java | 8 +- .../storage/service/UnifiedSANStrategy.java | 16 ++-- .../storage/service/model/ProtocolType.java | 2 +- .../cloudstack/storage/utils/Constants.java | 11 +++ .../cloudstack/storage/utils/Utility.java | 13 +-- 34 files changed, 190 insertions(+), 179 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java index 3966527b47f6..693c5c4101ac 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java @@ -62,12 +62,9 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver { private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class); - private Utility utils; @Inject private StoragePoolDetailsDao storagePoolDetailsDao; @Inject private PrimaryDataStoreDao storagePoolDao; - public OntapPrimaryDatastoreDriver() { - utils = new Utility(); - } + public OntapPrimaryDatastoreDriver() {} @Override public Map getCapabilities() { s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called"); @@ -133,7 +130,7 @@ private String createCloudStackVolumeForTypeVolume(DataStore dataStore, DataObje Map 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 = utils.createCloudStackVolumeRequestByProtocol(storagePool, details, dataObject); + 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(); @@ -274,7 +271,7 @@ public void detachVolumeFromAllStorageNodes(Volume volume) { } - public StorageStrategy getStrategyByStoragePoolDetails(Map details) { + private StorageStrategy getStrategyByStoragePoolDetails(Map 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"); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java index 4afcb3787590..3bbf3aaaafc4 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java @@ -38,7 +38,6 @@ public T createClient(Class clientClass, String baseURL) { .client(feignConfiguration.createClient()) .encoder(feignConfiguration.createEncoder()) .decoder(feignConfiguration.createDecoder()) -// .logger(feignConfiguration.createLogger()) .retryer(feignConfiguration.createRetryer()) .requestInterceptor(feignConfiguration.createRequestInterceptor()) .target(clientClass, baseURL); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java index f1b821b14135..555f50f18220 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java @@ -1,22 +1,3 @@ -/* - * 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; @@ -25,13 +6,12 @@ import feign.httpclient.ApacheHttpClient; import feign.codec.Decoder; import feign.codec.Encoder; -//import feign.slf4j.Slf4jLogger; import feign.Response; import feign.codec.DecodeException; import feign.codec.EncodeException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.DeserializationFeature; 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; @@ -44,6 +24,7 @@ import javax.net.ssl.SSLContext; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; @@ -55,12 +36,13 @@ public class FeignConfiguration { private final int retryMaxInterval = 5; private final String ontapFeignMaxConnection = "80"; private final String ontapFeignMaxConnectionPerRoute = "20"; - private final ObjectMapper objectMapper; + private final JsonMapper jsonMapper; public FeignConfiguration() { - this.objectMapper = new ObjectMapper(); - // Configure ObjectMapper to ignore unknown properties like _links - this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.jsonMapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .findAndAddModules() + .build(); } public Client createClient() { @@ -69,18 +51,16 @@ public Client createClient() { try { maxConn = Integer.parseInt(this.ontapFeignMaxConnection); } catch (Exception e) { - logger.error("ontapFeignClient: encounter exception while parse the max connection from env. setting default value"); + logger.error("ontapFeignClient: parse max connection failed, using default"); maxConn = 20; } try { maxConnPerRoute = Integer.parseInt(this.ontapFeignMaxConnectionPerRoute); } catch (Exception e) { - logger.error("ontapFeignClient: encounter exception while parse the max connection per route from env. setting default value"); + logger.error("ontapFeignClient: parse max connection per route failed, using default"); maxConnPerRoute = 2; } - - // Disable Keep Alive for Http Connection - logger.debug("ontapFeignClient: Setting the feign client config values as max connection: {}, max connections per route: {}", maxConn, maxConnPerRoute); + logger.debug("ontapFeignClient: maxConn={}, maxConnPerRoute={}", maxConn, maxConnPerRoute); ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> 0; CloseableHttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(maxConn) @@ -94,7 +74,6 @@ public Client createClient() { private SSLConnectionSocketFactory getSSLSocketFactory() { try { - // The TrustAllStrategy is a strategy used in SSL context configuration that accepts any certificate SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustAllStrategy()).build(); return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); } catch (Exception ex) { @@ -108,7 +87,7 @@ public RequestInterceptor createRequestInterceptor() { logger.info("HTTP Method: {}", template.method()); logger.info("Headers: {}", template.headers()); if (template.body() != null) { - logger.info("Body: {}", new String(template.body())); + logger.info("Body: {}", new String(template.body(), StandardCharsets.UTF_8)); } }; } @@ -126,7 +105,7 @@ public void encode(Object object, Type bodyType, feign.RequestTemplate template) return; } try { - byte[] jsonBytes = objectMapper.writeValueAsBytes(object); + byte[] jsonBytes = jsonMapper.writeValueAsBytes(object); template.body(jsonBytes, StandardCharsets.UTF_8); template.header("Content-Type", "application/json"); } catch (JsonProcessingException e) { @@ -144,10 +123,10 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc return null; } String json = null; - try (var bodyStream = response.body().asInputStream()) { + try (InputStream bodyStream = response.body().asInputStream()) { json = new String(bodyStream.readAllBytes(), StandardCharsets.UTF_8); logger.debug("Decoding JSON response: {}", json); - return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type)); + return jsonMapper.readValue(json, jsonMapper.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); @@ -155,9 +134,4 @@ public Object decode(Response response, Type type) throws IOException, DecodeExc } }; } - -// public Slf4jLogger createLogger() { -// return new Slf4jLogger(); -// } } - diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java index f625e6360b65..1005824ee902 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java @@ -45,22 +45,20 @@ void deleteFile(@Param("authHeader") String authHeader, @Headers({"Authorization: {authHeader}"}) void updateFile(@Param("authHeader") String authHeader, @Param("volumeUuid") String volumeUUID, - @Param("path") String filePath, - @Param("fileInfo") FileInfo fileInfo); + @Param("path") String filePath, FileInfo fileInfo); @RequestLine("POST /{volumeUuid}/files/{path}") @Headers({"Authorization: {authHeader}"}) void createFile(@Param("authHeader") String authHeader, @Param("volumeUuid") String volumeUUID, - @Param("path") String filePath, - @Param("file") FileInfo file); + @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, - @Param("exportPolicy") ExportPolicy exportPolicy); + ExportPolicy exportPolicy); @RequestLine("GET /") @Headers({"Authorization: {authHeader}"}) @@ -80,5 +78,5 @@ void deleteExportPolicyById(@Param("authHeader") String authHeader, @Headers({"Authorization: {authHeader}"}) OntapResponse updateExportPolicy(@Param("authHeader") String authHeader, @Param("id") String id, - @Param("request") ExportPolicy request); + ExportPolicy request); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java index 23e5164b35c9..0a2ce4d0e77a 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java @@ -34,7 +34,7 @@ public interface SANFeignClient { @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) OntapResponse createLun(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, - @Param("lun") Lun lun); + Lun lun); @RequestLine("GET /") @Headers({"Authorization: {authHeader}"}) @@ -46,7 +46,7 @@ OntapResponse createLun(@Param("authHeader") String authHeader, @RequestLine("PATCH /{uuid}") @Headers({"Authorization: {authHeader}"}) - void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, @Param("lun") Lun lun); + void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Lun lun); @RequestLine("DELETE /{uuid}") @Headers({"Authorization: {authHeader}"}) @@ -57,10 +57,10 @@ OntapResponse createLun(@Param("authHeader") String authHeader, @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) OntapResponse createIgroup(@Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords, - @Param("igroupRequest") Igroup igroupRequest); + Igroup igroupRequest); @RequestLine("GET /") - @Headers({"Authorization: {authHeader}"}) + @Headers({"Authorization: {authHeader}"}) // TODO: Check this again, uuid should be part of the path? OntapResponse getIgroupResponse(@Param("authHeader") String authHeader, @Param("uuid") String uuid); @RequestLine("GET /{uuid}") @@ -75,13 +75,13 @@ OntapResponse createIgroup(@Param("authHeader") String authHeader, @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) OntapResponse addNestedIgroups(@Param("authHeader") String authHeader, @Param("uuid") String uuid, - @Param("igroupNestedRequest") Igroup igroupNestedRequest, + Igroup igroupNestedRequest, @Param("returnRecords") boolean returnRecords); // LUN Maps Operation APIs @RequestLine("POST /") @Headers({"Authorization: {authHeader}"}) - OntapResponse createLunMap(@Param("authHeader") String authHeader, @Param("lunMap") LunMap lunMap); + OntapResponse createLunMap(@Param("authHeader") String authHeader, LunMap lunMap); @RequestLine("GET /") @Headers({"Authorization: {authHeader}"}) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java index 85b72a0af27e..c91f0f87eb27 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Aggregate.java @@ -19,11 +19,13 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Aggregate { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java index 9dcf8aa738c1..061372756175 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Cluster.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -28,6 +29,7 @@ * Complete cluster information */ @SuppressWarnings("checkstyle:RegexpSingleline") +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Cluster { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java index ab0ed0b9e356..8c7c0323e662 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportPolicy.java @@ -19,12 +19,14 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.math.BigInteger; import java.util.List; import java.util.Objects; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class ExportPolicy { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java index 6d4798667075..8f3c9597dca7 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/ExportRule.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; @@ -26,6 +27,7 @@ /** * ExportRule */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class ExportRule { @JsonProperty("anonymous_user") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java index 973e957ada63..181620268932 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/FileInfo.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.feign.model; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; @@ -30,6 +31,7 @@ /** * Information about a single file. */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class FileInfo { @JsonProperty("bytes_used") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java index 3ddde89f9184..877d60de830c 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Igroup.java @@ -19,12 +19,14 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Objects; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Igroup { @JsonProperty("delete_on_unmap") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java index dc290bdc2fc9..b0a5bd24272a 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Initiator.java @@ -19,9 +19,11 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Initiator { @JsonProperty("name") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java index a1a0c2698f14..04b5611a8dab 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java @@ -18,6 +18,7 @@ */ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; @@ -26,6 +27,7 @@ * @author Administrator * */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(Include.NON_NULL) public class Job { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java index d8f66106dd5c..48ebc9c739cb 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Lun.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.feign.model; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonValue; @@ -30,6 +31,7 @@ /** * A LUN is the logical representation of storage in a storage area network (SAN).<br/> In ONTAP, a LUN is located within a volume. Optionally, it can be located within a qtree in a volume.<br/> A LUN can be created to a specified size using thin or thick provisioning. A LUN can then be renamed, resized, cloned, and moved to a different volume. LUNs support the assignment of a quality of service (QoS) policy for performance management or a QoS policy can be assigned to the volume containing the LUN. See the LUN object model to learn more about each of the properties supported by the LUN REST API.<br/> A LUN must be mapped to an initiator group to grant access to the initiator group's initiators (client hosts). Initiators can then access the LUN and perform I/O over a Fibre Channel (FC) fabric using the Fibre Channel Protocol or a TCP/IP network using iSCSI. */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Lun { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java index 4804a71e406d..085e38f1e9c6 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunMap.java @@ -20,10 +20,12 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.annotations.SerializedName; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class LunMap { @JsonProperty("igroup") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java index d0956cd5366f..03e776cd378c 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/LunSpace.java @@ -20,12 +20,14 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** * The storage space related properties of the LUN. */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class LunSpace { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java index 27590d3fde25..42d348d80c80 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Nas.java @@ -18,9 +18,11 @@ */ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Nas { @JsonProperty("path") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java index 839af96756a1..eb56b4a5d5e5 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java @@ -22,12 +22,12 @@ import org.apache.cloudstack.storage.service.model.ProtocolType; public class OntapStorage { - private String username; - private String password; - private String managementLIF; - private String svmName; - private ProtocolType protocolType; - private Boolean isDisaggregated; + private final String username; + private final String password; + private final String managementLIF; + private final String svmName; + private final ProtocolType protocolType; + private final Boolean isDisaggregated; public OntapStorage(String username, String password, String managementLIF, String svmName, ProtocolType protocolType, Boolean isDisaggregated) { this.username = username; @@ -42,47 +42,23 @@ public String getUsername() { return username; } - public void setUsername(String username) { - this.username = username; - } - public String getPassword() { return password; } - public void setPassword(String password) { - this.password = password; - } - public String getManagementLIF() { return managementLIF; } - public void setManagementLIF(String managementLIF) { - this.managementLIF = managementLIF; - } - public String getSvmName() { return svmName; } - public void setSvmName(String svmName) { - this.svmName = svmName; - } - public ProtocolType getProtocol() { return protocolType; } - public void setProtocol(ProtocolType protocolType) { - this.protocolType = protocolType; - } - public Boolean getIsDisaggregated() { return isDisaggregated; } - - public void setIsDisaggregated(Boolean isDisaggregated) { - this.isDisaggregated = isDisaggregated; - } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java index 82ba149bd040..24fdee6a1424 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Policy.java @@ -19,10 +19,12 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.Objects; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Policy { private int minThroughputIops; diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java index ccc230c6ea53..3f7f8180de8e 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Qos.java @@ -18,9 +18,11 @@ */ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Qos { @JsonProperty("policy") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java index c053c8959cbb..f1a226739365 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Svm.java @@ -20,12 +20,14 @@ package org.apache.cloudstack.storage.feign.model; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Objects; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Svm { @JsonProperty("uuid") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java index 056e20eb3400..80b4d0229abe 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Version.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; @@ -28,6 +29,7 @@ /** * This returns the cluster version information. When the cluster has more than one node, the cluster version is equivalent to the lowest of generation, major, and minor versions on all nodes. */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Version { @JsonProperty("full") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java index 3d384c56db2e..22ac7563a6de 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java @@ -19,12 +19,14 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import java.util.Objects; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class Volume { @JsonProperty("uuid") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java index ae4cfa519c14..7a9a4307ab1a 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeQosPolicy.java @@ -19,9 +19,11 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class VolumeQosPolicy { @JsonProperty("max_throughput_iops") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java index 067997fcd440..84ae7d93199e 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpace.java @@ -19,9 +19,11 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class VolumeSpace { @JsonProperty("size") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java index 354a314a5017..fa14252e4db3 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/VolumeSpaceLogicalSpace.java @@ -19,9 +19,11 @@ package org.apache.cloudstack.storage.feign.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class VolumeSpaceLogicalSpace { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 83137cfda27d..1fc8131fb8b1 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -51,6 +51,7 @@ import javax.inject.Inject; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle { @@ -70,29 +71,28 @@ public DataStore initialize(Map dsInfos) { if (dsInfos == null) { throw new CloudRuntimeException("Datastore info map is null, cannot create primary storage"); } - String url = (String)dsInfos.get("url"); - Long zoneId = (Long)dsInfos.get("zoneId"); - Long podId = (Long)dsInfos.get("podId"); - Long clusterId = (Long)dsInfos.get("clusterId"); - String storagePoolName = (String)dsInfos.get("name"); - String providerName = (String)dsInfos.get("providerName"); - Long capacityBytes = (Long)dsInfos.get("capacityBytes"); - String tags = (String)dsInfos.get("tags"); + String url = (String) dsInfos.get("url"); + Long zoneId = (Long) dsInfos.get("zoneId"); + Long podId = (Long) dsInfos.get("podId"); + Long clusterId = (Long) dsInfos.get("clusterId"); + String storagePoolName = (String) dsInfos.get("name"); + String providerName = (String) dsInfos.get("providerName"); + Long capacityBytes = (Long) dsInfos.get("capacityBytes"); + String tags = (String) dsInfos.get("tags"); Boolean isTagARule = (Boolean) dsInfos.get("isTagARule"); -// String scheme = dsInfos.get("scheme").toString(); + String scheme = dsInfos.get("scheme").toString(); -// s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + -// ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme); s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + - ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId); + ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", scheme: " + scheme); // Additional details requested for ONTAP primary storage pool creation @SuppressWarnings("unchecked") - Map details = (Map)dsInfos.get("details"); + Map details = (Map) dsInfos.get("details"); // Validations - if (capacityBytes == null || capacityBytes <= 0) { + if (capacityBytes == null || capacityBytes <= 1677721600L) { // 1.56 GB minimum for ONTAP Volume throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0."); } + details.put(Constants.SIZE, capacityBytes.toString()); if (podId == null ^ clusterId == null) { throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage"); @@ -125,56 +125,65 @@ public DataStore initialize(Map dsInfos) { } // Get ONTAP details from the URL - Map storageDetails = Map.of( - Constants.USERNAME, "", - Constants.PASSWORD, "", - Constants.SVM_NAME, "", - Constants.PROTOCOL, "", - Constants.MANAGEMENT_LIF, "", - Constants.SIZE, "", - Constants.IS_DISAGGREGATED, "" + Set requiredKeys = Set.of( + Constants.USERNAME, + Constants.PASSWORD, + Constants.SVM_NAME, + Constants.PROTOCOL, + Constants.MANAGEMENT_LIF, + Constants.IS_DISAGGREGATED ); - String[] urlDetails = url.split(";"); - for(int m =0; m < urlDetails.length; m++) { - String[] kvs = urlDetails[m].split("="); - if(kvs.length == 2) { - details.put(kvs[0], kvs[1]); + // Parse key=value pairs from URL into details (skip empty segments) + for (String segment : url.split(Constants.SEMICOLON)) { + if (segment.isEmpty()) { + continue; + } + String[] kv = segment.split(Constants.EQUALS, 2); + if (kv.length == 2) { + details.put(kv[0].trim(), kv[1].trim()); } } - //managementLIF=;username=;password=;svmName=;protocol=;size=;isDisaggregated=; - - for (String key : storageDetails.keySet()) { - if (!details.containsKey(key) || details.get(key).isEmpty()) { - throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail: " + key); + // Validate existing entries (unexpected keys, empty values) + for (Map.Entry e : details.entrySet()) { + String key = e.getKey(); + String val = e.getValue(); + if (!requiredKeys.contains(key)) { + throw new CloudRuntimeException("Unexpected ONTAP key passed in url: " + key); + } + if (val == null || val.isEmpty()) { + throw new CloudRuntimeException("ONTAP primary storage creation failed, empty detail: " + key); } } - + // Detect missing required keys + if (details.size() != requiredKeys.size()) { + Set missing = new java.util.HashSet<>(requiredKeys); + missing.removeAll(details.keySet()); + if (!missing.isEmpty()) { + throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail(s): " + missing); + } + } + // Default for IS_DISAGGREGATED if needed (after validation of presence) + details.putIfAbsent(Constants.IS_DISAGGREGATED, "false"); // TODO: While testing need to check what does this actually do and if the fields corresponding to each protocol should also be set // TODO: scheme could be 'custom' in our case and we might have to ask 'protocol' separately to the user String path = ""; ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL)); switch (protocol) { - case NFS: + case NFS3: parameters.setType(Storage.StoragePoolType.NetworkFilesystem); path = "/"+ storagePoolName; break; case ISCSI: parameters.setType(Storage.StoragePoolType.Iscsi); + //TODO: path for iSCSI break; default: throw new CloudRuntimeException("Unsupported protocol: " + protocol + ", cannot create primary storage"); } -// details.put(Constants.MANAGEMENT_LIF, url); - - // Validate the ONTAP details - if(details.get(Constants.IS_DISAGGREGATED) == null || details.get(Constants.IS_DISAGGREGATED).isEmpty()) { - details.put(Constants.IS_DISAGGREGATED, "false"); - } - OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD), details.get(Constants.MANAGEMENT_LIF), details.get(Constants.SVM_NAME), protocol, Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED).toLowerCase())); @@ -188,7 +197,7 @@ public DataStore initialize(Map dsInfos) { } parameters.setHost(details.get(Constants.MANAGEMENT_LIF)); - parameters.setPort(443); + parameters.setPort(Constants.ONTAP_PORT); parameters.setPath(path); parameters.setTags(tags); parameters.setIsTagARule(isTagARule); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java index e9448ec16ded..6bb6ad1fef73 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/StorageProviderFactory.java @@ -36,7 +36,7 @@ public static StorageStrategy getStrategy(OntapStorage ontapStorage) { ProtocolType protocol = ontapStorage.getProtocol(); s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol); switch (protocol) { - case NFS: + case NFS3: if (!ontapStorage.getIsDisaggregated()) { UnifiedNASStrategy unifiedNASStrategy = new UnifiedNASStrategy(ontapStorage); unifiedNASStrategy.setOntapStorage(ontapStorage); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index 1d80a46513c3..c48681dd40b8 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -21,6 +21,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import feign.FeignException; +import feign.Util; import org.apache.cloudstack.storage.feign.FeignClientFactory; import org.apache.cloudstack.storage.feign.client.JobFeignClient; import org.apache.cloudstack.storage.feign.client.SvmFeignClient; @@ -51,8 +52,6 @@ * Supported platform: Unified and Disaggregated */ public abstract class StorageStrategy { - private final Utility utils; - // Replace @Inject Feign clients with FeignClientFactory private final FeignClientFactory feignClientFactory; private final VolumeFeignClient volumeFeignClient; @@ -72,7 +71,6 @@ public StorageStrategy(OntapStorage ontapStorage) { storage = ontapStorage; String baseURL = Constants.HTTPS + storage.getManagementLIF(); s_logger.info("Initializing StorageStrategy with base URL: " + baseURL); - this.utils = new Utility(); // Initialize FeignClientFactory and create clients this.feignClientFactory = new FeignClientFactory(); this.volumeFeignClient = feignClientFactory.createClient(VolumeFeignClient.class, baseURL); @@ -83,27 +81,21 @@ public StorageStrategy(OntapStorage ontapStorage) { // Connect method to validate ONTAP cluster, credentials, protocol, and SVM public boolean connect() { s_logger.info("Attempting to connect to ONTAP cluster at " + storage.getManagementLIF() + " and validate SVM " + - storage.getSvmName() + ", username " + storage.getUsername() + ", password " + storage.getPassword() + ", protocol " + storage.getProtocol()); + storage.getSvmName() + ", protocol " + storage.getProtocol()); //Get AuthHeader - String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + String authHeader = Utility.generateAuthHeader(storage.getUsername(), storage.getPassword()); String svmName = storage.getSvmName(); try { // Call the SVM API to check if the SVM exists Svm svm = new Svm(); - try { - s_logger.info("Fetching the SVM details..."); - if (svmFeignClient == null) { - throw new CloudRuntimeException("SVM Feign client is not initialized."); - } - Map queryParams = Map.of("name", svmName, "fields", "aggregates,state"); - OntapResponse svms = svmFeignClient.getSvmResponse(queryParams, authHeader); - if (svms != null && svms.getRecords() != null && !svms.getRecords().isEmpty()) { - svm = svms.getRecords().get(0); - } else { - throw new CloudRuntimeException("No SVM found on the ONTAP cluster by the name" + svmName + "."); - } - } catch (FeignException.FeignClientException e) { - throw new CloudRuntimeException("Failed to fetch SVM details: " + e.getMessage()); + s_logger.info("Fetching the SVM details..."); + Map queryParams = Map.of(Constants.NAME, svmName, Constants.FIELDS, Constants.AGGREGATES + + Constants.COMMA + Constants.STATE); + OntapResponse svms = svmFeignClient.getSvmResponse(queryParams, authHeader); + if (svms != null && svms.getRecords() != null && !svms.getRecords().isEmpty()) { + svm = svms.getRecords().get(0); + } else { + throw new CloudRuntimeException("No SVM found on the ONTAP cluster by the name" + svmName + "."); } // Validations @@ -127,7 +119,7 @@ public boolean connect() { this.aggregates = aggrs; s_logger.info("Successfully connected to ONTAP cluster and validated ONTAP details provided"); } catch (Exception e) { - throw new CloudRuntimeException("Failed to connect to ONTAP cluster: " + e); + throw new CloudRuntimeException("Failed to connect to ONTAP cluster: " + e.getMessage(), e); } return true; } @@ -152,7 +144,7 @@ public Volume createStorageVolume(String volumeName, Long size) { throw new CloudRuntimeException("No aggregates available to create volume on SVM " + svmName); } // Get the AuthHeader - String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + String authHeader = Utility.generateAuthHeader(storage.getUsername(), storage.getPassword()); // Generate the Create Volume Request Volume volumeRequest = new Volume(); @@ -316,4 +308,30 @@ public Volume getStorageVolume(Volume volume) * @return the updated AccessGroup object */ abstract AccessGroup updateAccessGroup(AccessGroup accessGroup); + + /** + * Method encapsulates the behavior based on the opted protocol in subclasses + * getiGroup for iSCSI and FC protocols + * getExportPolicy for NFS 3.0 and NFS 4.1 protocols + * getNameSpace for Nvme/TCP and Nvme/FC protocols + * @param accessGroup the access group to retrieve + * @return the retrieved AccessGroup object + */ + abstract AccessGroup getAccessGroup(AccessGroup accessGroup); + + /** + * Method encapsulates the behavior based on the opted protocol in subclasses + * lunMap for iSCSI and FC protocols + * //TODO for Nvme/TCP and Nvme/FC protocols + * @param values + */ + abstract void enableLogicalAccess(Map values); + + /** + * Method encapsulates the behavior based on the opted protocol in subclasses + * lunUnmap for iSCSI and FC protocols + * //TODO for Nvme/TCP and Nvme/FC protocols + * @param values + */ + abstract void disableLogicalAccess(Map values); } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java index a4f9db383c6c..cb3079691c94 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedNASStrategy.java @@ -25,7 +25,6 @@ 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; @@ -34,8 +33,6 @@ public class UnifiedNASStrategy extends NASStrategy { private static final Logger s_logger = LogManager.getLogger(UnifiedNASStrategy.class); - private Utility utils; - // Add missing Feign client setup for NAS operations private final FeignClientFactory feignClientFactory; private final NASFeignClient nasFeignClient; @@ -43,7 +40,6 @@ public class UnifiedNASStrategy extends NASStrategy { public UnifiedNASStrategy(OntapStorage ontapStorage) { super(ontapStorage); String baseURL = Constants.HTTPS + ontapStorage.getManagementLIF(); - this.utils = new Utility(); // Initialize FeignClientFactory and create NAS client this.feignClientFactory = new FeignClientFactory(); this.nasFeignClient = feignClientFactory.createClient(NASFeignClient.class, baseURL); @@ -93,16 +89,18 @@ public AccessGroup updateAccessGroup(AccessGroup accessGroup) { return null; } - // Remove @Override - these methods don't exist in parent classes + @Override public AccessGroup getAccessGroup(AccessGroup accessGroup) { //TODO return null; } + @Override void enableLogicalAccess(Map values) { //TODO } + @Override void disableLogicalAccess(Map values) { //TODO } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java index fd9cbc1ef87b..0b47e1ff70a0 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java @@ -37,8 +37,6 @@ public class UnifiedSANStrategy extends SANStrategy { private static final Logger s_logger = LogManager.getLogger(UnifiedSANStrategy.class); - private Utility utils; - // Replace @Inject Feign client with FeignClientFactory private final FeignClientFactory feignClientFactory; private final SANFeignClient sanFeignClient; @@ -46,7 +44,6 @@ public class UnifiedSANStrategy extends SANStrategy { public UnifiedSANStrategy(OntapStorage ontapStorage) { super(ontapStorage); String baseURL = Constants.HTTPS + ontapStorage.getManagementLIF(); - this.utils = new Utility(); // Initialize FeignClientFactory and create SAN client this.feignClientFactory = new FeignClientFactory(); this.sanFeignClient = feignClientFactory.createClient(SANFeignClient.class, baseURL); @@ -65,7 +62,7 @@ public CloudStackVolume createCloudStackVolume(CloudStackVolume cloudstackVolume } try { // Get AuthHeader - String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword()); + String authHeader = Utility.generateAuthHeader(storage.getUsername(), storage.getPassword()); // Create URI for lun creation //TODO: It is possible that Lun creation will take time and we may need to handle through async job. OntapResponse createdLun = sanFeignClient.createLun(authHeader, true, cloudstackVolume.getLun()); @@ -94,7 +91,7 @@ CloudStackVolume updateCloudStackVolume(CloudStackVolume cloudstackVolume) { @Override void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) { - + //TODO } @Override @@ -111,7 +108,7 @@ public AccessGroup createAccessGroup(AccessGroup accessGroup) { @Override public void deleteAccessGroup(AccessGroup accessGroup) { - + //TODO } @Override @@ -120,16 +117,19 @@ public AccessGroup updateAccessGroup(AccessGroup accessGroup) { return null; } + @Override public AccessGroup getAccessGroup(AccessGroup accessGroup) { //TODO return null; } + @Override void enableLogicalAccess(Map values) { - + //TODO } + @Override void disableLogicalAccess(Map values) { - + //TODO } } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/ProtocolType.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/ProtocolType.java index 47b55ec29bb7..00dca62480dc 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/ProtocolType.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/model/ProtocolType.java @@ -20,6 +20,6 @@ package org.apache.cloudstack.storage.service.model; public enum ProtocolType { - NFS, + NFS3, ISCSI } diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java index 3669e9bcf38f..b58e8484cd48 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Constants.java @@ -32,16 +32,27 @@ public class Constants { public static final String IS_DISAGGREGATED = "isDisaggregated"; public static final String RUNNING = "running"; + public static final int ONTAP_PORT = 443; + public static final String JOB_RUNNING = "running"; public static final String JOB_QUEUE = "queued"; public static final String JOB_PAUSED = "paused"; public static final String JOB_FAILURE = "failure"; public static final String JOB_SUCCESS = "success"; + // Query params + public static final String NAME = "name"; + public static final String FIELDS = "fields"; + public static final String AGGREGATES = "aggregates"; + public static final String STATE = "state"; + 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 EQUALS = "="; + public static final String SEMICOLON = ";"; + public static final String COMMA = ","; public static final String VOLUME_PATH_PREFIX = "/vol/"; diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java index c90f14af7d39..af48724f984c 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/utils/Utility.java @@ -48,21 +48,12 @@ public class Utility { * @param password -->> normal decoded password of the storage backend * @return */ - public String generateAuthHeader (String username, String password) { + public static 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 managementLIF, String path) { -// if (path == null || path.isEmpty() || path.equals(StringUtils.SPACE) || path.equals("/")) { -// String uriString = Constants.HTTPS + managementLIF; -// return URI.create(uriString); -// } -// String uriString = Constants.HTTPS + managementLIF + path; -// return URI.create(uriString); -// } - - public CloudStackVolume createCloudStackVolumeRequestByProtocol(StoragePoolVO storagePool, Map details, DataObject dataObject) { + public static CloudStackVolume createCloudStackVolumeRequestByProtocol(StoragePoolVO storagePool, Map details, DataObject dataObject) { CloudStackVolume cloudStackVolumeRequest = null; String protocol = details.get(Constants.PROTOCOL); From 43c3684793a75167c6655f8279f6e3fbf26d900e Mon Sep 17 00:00:00 2001 From: "Locharla, Sandeep" Date: Wed, 5 Nov 2025 08:56:55 +0530 Subject: [PATCH 4/5] CSTACKEX-01: Made some changes to fix some errors seen during testing --- .../feign/model/response/OntapResponse.java | 2 + .../OntapPrimaryDatastoreLifecycle.java | 96 ++++++++++++------- .../storage/service/StorageStrategy.java | 1 - 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java index 1a77991f435b..8377f8906b99 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/response/OntapResponse.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.storage.feign.model.response; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; @@ -26,6 +27,7 @@ /** * OntapResponse */ +@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public class OntapResponse { @JsonProperty("num_records") diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 1fc8131fb8b1..7791a454ea93 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -61,10 +61,13 @@ public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycl @Inject private PrimaryDataStoreHelper _dataStoreHelper; private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class); + // ONTAP minimum volume size is 1.56 GB (1677721600 bytes) + private static final long ONTAP_MIN_VOLUME_SIZE = 1677721600L; + /** * Creates primary storage on NetApp storage - * @param dsInfos - * @return + * @param dsInfos datastore information map + * @return DataStore instance */ @Override public DataStore initialize(Map dsInfos) { @@ -80,20 +83,25 @@ public DataStore initialize(Map dsInfos) { Long capacityBytes = (Long) dsInfos.get("capacityBytes"); String tags = (String) dsInfos.get("tags"); Boolean isTagARule = (Boolean) dsInfos.get("isTagARule"); - String scheme = dsInfos.get("scheme").toString(); s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName + - ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", scheme: " + scheme); + ", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId); + s_logger.debug("Received capacityBytes from UI: " + capacityBytes); // Additional details requested for ONTAP primary storage pool creation @SuppressWarnings("unchecked") Map details = (Map) dsInfos.get("details"); - // Validations - if (capacityBytes == null || capacityBytes <= 1677721600L) { // 1.56 GB minimum for ONTAP Volume - throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0."); + + // Validate and set capacity + if (capacityBytes == null || capacityBytes <= 0) { + s_logger.warn("capacityBytes not provided or invalid (" + capacityBytes + "), using ONTAP minimum size: " + ONTAP_MIN_VOLUME_SIZE); + capacityBytes = ONTAP_MIN_VOLUME_SIZE; + } else if (capacityBytes < ONTAP_MIN_VOLUME_SIZE) { + s_logger.warn("capacityBytes (" + capacityBytes + ") is below ONTAP minimum (" + ONTAP_MIN_VOLUME_SIZE + "), adjusting to minimum"); + capacityBytes = ONTAP_MIN_VOLUME_SIZE; } - details.put(Constants.SIZE, capacityBytes.toString()); + // Validate scope if (podId == null ^ clusterId == null) { throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage"); } @@ -124,7 +132,7 @@ public DataStore initialize(Map dsInfos) { parameters.setHypervisorType(clusterVO.getHypervisorType()); } - // Get ONTAP details from the URL + // Required ONTAP detail keys Set requiredKeys = Set.of( Constants.USERNAME, Constants.PASSWORD, @@ -135,72 +143,87 @@ public DataStore initialize(Map dsInfos) { ); // Parse key=value pairs from URL into details (skip empty segments) - for (String segment : url.split(Constants.SEMICOLON)) { - if (segment.isEmpty()) { - continue; - } - String[] kv = segment.split(Constants.EQUALS, 2); - if (kv.length == 2) { - details.put(kv[0].trim(), kv[1].trim()); + if (url != null && !url.isEmpty()) { + for (String segment : url.split(";")) { + if (segment.isEmpty()) { + continue; + } + String[] kv = segment.split("=", 2); + if (kv.length == 2) { + details.put(kv[0].trim(), kv[1].trim()); + } } } - // Validate existing entries (unexpected keys, empty values) + // Validate existing entries (reject unexpected keys, empty values) for (Map.Entry e : details.entrySet()) { String key = e.getKey(); String val = e.getValue(); if (!requiredKeys.contains(key)) { - throw new CloudRuntimeException("Unexpected ONTAP key passed in url: " + key); + throw new CloudRuntimeException("Unexpected ONTAP detail key in URL: " + key); } if (val == null || val.isEmpty()) { throw new CloudRuntimeException("ONTAP primary storage creation failed, empty detail: " + key); } } + // Detect missing required keys - if (details.size() != requiredKeys.size()) { + Set providedKeys = new java.util.HashSet<>(details.keySet()); + if (!providedKeys.containsAll(requiredKeys)) { Set missing = new java.util.HashSet<>(requiredKeys); - missing.removeAll(details.keySet()); - if (!missing.isEmpty()) { - throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail(s): " + missing); - } + missing.removeAll(providedKeys); + throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail(s): " + missing); } - // Default for IS_DISAGGREGATED if needed (after validation of presence) + + details.put(Constants.SIZE, capacityBytes.toString()); + + // Default for IS_DISAGGREGATED if needed details.putIfAbsent(Constants.IS_DISAGGREGATED, "false"); - // TODO: While testing need to check what does this actually do and if the fields corresponding to each protocol should also be set - // TODO: scheme could be 'custom' in our case and we might have to ask 'protocol' separately to the user - String path = ""; + // Determine storage pool type and path based on protocol + String path; ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL)); switch (protocol) { case NFS3: parameters.setType(Storage.StoragePoolType.NetworkFilesystem); - path = "/"+ storagePoolName; + path = details.get(Constants.MANAGEMENT_LIF) + ":/" + storagePoolName; + s_logger.info("Setting NFS path for storage pool: " + path); break; case ISCSI: parameters.setType(Storage.StoragePoolType.Iscsi); - //TODO: path for iSCSI + path = "iqn.1992-08.com.netapp:" + details.get(Constants.SVM_NAME) + "." + storagePoolName; + s_logger.info("Setting iSCSI path for storage pool: " + path); break; default: throw new CloudRuntimeException("Unsupported protocol: " + protocol + ", cannot create primary storage"); } - OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD), - details.get(Constants.MANAGEMENT_LIF), details.get(Constants.SVM_NAME), protocol, + // Connect to ONTAP and create volume + OntapStorage ontapStorage = new OntapStorage( + details.get(Constants.USERNAME), + details.get(Constants.PASSWORD), + details.get(Constants.MANAGEMENT_LIF), + details.get(Constants.SVM_NAME), + protocol, Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED).toLowerCase())); + StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage); boolean isValid = storageStrategy.connect(); if (isValid) { -// String volumeName = storagePoolName + "_vol"; //TODO: Figure out a better naming convention - storageStrategy.createStorageVolume(storagePoolName, Long.parseLong((details.get(Constants.SIZE)))); // TODO: size should be in bytes, so see if conversion is needed + long volumeSize = Long.parseLong(details.get(Constants.SIZE)); + s_logger.info("Creating ONTAP volume '" + storagePoolName + "' with size: " + volumeSize + " bytes (" + + (volumeSize / (1024 * 1024 * 1024)) + " GB)"); + storageStrategy.createStorageVolume(storagePoolName, volumeSize); } else { throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage"); } + // Set parameters for primary data store parameters.setHost(details.get(Constants.MANAGEMENT_LIF)); parameters.setPort(Constants.ONTAP_PORT); parameters.setPath(path); - parameters.setTags(tags); - parameters.setIsTagARule(isTagARule); + parameters.setTags(tags != null ? tags : ""); + parameters.setIsTagARule(isTagARule != null ? isTagARule : Boolean.FALSE); parameters.setDetails(details); parameters.setUuid(UUID.randomUUID().toString()); parameters.setZoneId(zoneId); @@ -208,7 +231,7 @@ public DataStore initialize(Map dsInfos) { parameters.setClusterId(clusterId); parameters.setName(storagePoolName); parameters.setProviderName(providerName); - parameters.setManaged(true); + parameters.setManaged(true); // ONTAP storage is always managed parameters.setCapacityBytes(capacityBytes); parameters.setUsedBytes(0); @@ -302,3 +325,4 @@ public void changeStoragePoolScopeToCluster(DataStore store, ClusterScope cluste } } + diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java index c48681dd40b8..0f9706335784 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java @@ -21,7 +21,6 @@ import com.cloud.utils.exception.CloudRuntimeException; import feign.FeignException; -import feign.Util; import org.apache.cloudstack.storage.feign.FeignClientFactory; import org.apache.cloudstack.storage.feign.client.JobFeignClient; import org.apache.cloudstack.storage.feign.client.SvmFeignClient; From 80b24ca28d16cf5c3bd2de026f5d277bd83ccb21 Mon Sep 17 00:00:00 2001 From: "Locharla, Sandeep" Date: Wed, 5 Nov 2025 10:55:30 +0530 Subject: [PATCH 5/5] CSTACKEX-01: Addressed additional comments --- plugins/storage/volume/ontap/pom.xml | 11 ++++------- .../storage/driver/OntapPrimaryDatastoreDriver.java | 1 - .../cloudstack/storage/feign/FeignConfiguration.java | 2 +- .../storage/feign/client/NASFeignClient.java | 1 + .../storage/feign/client/SANFeignClient.java | 8 +------- .../storage/feign/client/SvmFeignClient.java | 1 + .../lifecycle/OntapPrimaryDatastoreLifecycle.java | 4 ++-- 7 files changed, 10 insertions(+), 18 deletions(-) diff --git a/plugins/storage/volume/ontap/pom.xml b/plugins/storage/volume/ontap/pom.xml index 3e706b8065c8..10ca7935f408 100644 --- a/plugins/storage/volume/ontap/pom.xml +++ b/plugins/storage/volume/ontap/pom.xml @@ -31,6 +31,8 @@ 2021.0.7 11.0 20230227 + 2.15.2 + 4.5.14 1.6.2 3.8.1 2.22.2 @@ -67,11 +69,6 @@ feign-httpclient ${openfeign.version} - - - - - io.github.openfeign feign-jackson @@ -80,12 +77,12 @@ com.fasterxml.jackson.core jackson-databind - 2.15.2 + ${jackson-databind.version} org.apache.httpcomponents httpclient - 4.5.14 + ${httpclient.version} org.apache.cloudstack diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java index 693c5c4101ac..e2eb6220230a 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java @@ -64,7 +64,6 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver { @Inject private StoragePoolDetailsDao storagePoolDetailsDao; @Inject private PrimaryDataStoreDao storagePoolDao; - public OntapPrimaryDatastoreDriver() {} @Override public Map getCapabilities() { s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called"); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java index 555f50f18220..ce2783add228 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java @@ -101,7 +101,7 @@ public Encoder createEncoder() { @Override public void encode(Object object, Type bodyType, feign.RequestTemplate template) throws EncodeException { if (object == null) { - template.body((byte[]) null, StandardCharsets.UTF_8); + template.body(null, StandardCharsets.UTF_8); return; } try { diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java index 1005824ee902..b7aac9954cfe 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java @@ -26,6 +26,7 @@ import feign.Param; import feign.RequestLine; +//TODO: Proper URLs should be added in the RequestLine annotations below public interface NASFeignClient { // File Operations diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java index 0a2ce4d0e77a..dd2463d7f3bb 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java @@ -27,6 +27,7 @@ import feign.RequestLine; import java.net.URI; +//TODO: Proper URLs should be added in the RequestLine annotations below public interface SANFeignClient { // LUN Operation APIs @@ -71,13 +72,6 @@ OntapResponse createIgroup(@Param("authHeader") String authHeader, @Headers({"Authorization: {authHeader}"}) void deleteIgroup(@Param("baseUri") URI baseUri, @Param("authHeader") String authHeader, @Param("uuid") String uuid); - @RequestLine("POST /{uuid}/igroups") - @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"}) - OntapResponse addNestedIgroups(@Param("authHeader") String authHeader, - @Param("uuid") String uuid, - Igroup igroupNestedRequest, - @Param("returnRecords") boolean returnRecords); - // LUN Maps Operation APIs @RequestLine("POST /") @Headers({"Authorization: {authHeader}"}) diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java index 8a887a9ee7b8..29ea3b5f694f 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java @@ -29,6 +29,7 @@ public interface SvmFeignClient { + // SVM Operation APIs @RequestLine("GET /api/svm/svms") @Headers({"Authorization: {authHeader}"}) OntapResponse getSvmResponse(@QueryMap Map queryMap, @Param("authHeader") String authHeader); diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java index 7791a454ea93..01b013f606dd 100644 --- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/lifecycle/OntapPrimaryDatastoreLifecycle.java @@ -144,11 +144,11 @@ public DataStore initialize(Map dsInfos) { // Parse key=value pairs from URL into details (skip empty segments) if (url != null && !url.isEmpty()) { - for (String segment : url.split(";")) { + for (String segment : url.split(Constants.SEMICOLON)) { if (segment.isEmpty()) { continue; } - String[] kv = segment.split("=", 2); + String[] kv = segment.split(Constants.EQUALS, 2); if (kv.length == 2) { details.put(kv[0].trim(), kv[1].trim()); }