diff --git a/plugins/storage/volume/ontap/pom.xml b/plugins/storage/volume/ontap/pom.xml index d538d555cb10..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 @@ -58,24 +60,30 @@ ${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 + ${jackson-databind.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + 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..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 @@ -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; @@ -60,7 +62,6 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver { private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class); - @Inject private Utility utils; @Inject private StoragePoolDetailsDao storagePoolDetailsDao; @Inject private PrimaryDataStoreDao storagePoolDao; @Override @@ -126,9 +127,9 @@ 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 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(); @@ -268,4 +269,24 @@ public boolean isStorageSupportHA(Storage.StoragePoolType type) { public void detachVolumeFromAllStorageNodes(Volume volume) { } + + 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"); + } + 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 new file mode 100644 index 000000000000..3bbf3aaaafc4 --- /dev/null +++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java @@ -0,0 +1,45 @@ +/* + * 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, String baseURL) { + return Feign.builder() + .client(feignConfiguration.createClient()) + .encoder(feignConfiguration.createEncoder()) + .decoder(feignConfiguration.createDecoder()) + .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 576c2dd1c1b4..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 @@ -1,77 +1,68 @@ -/* - * 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; -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.Response; +import feign.codec.DecodeException; +import feign.codec.EncodeException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; import org.apache.http.conn.ConnectionKeepAliveStrategy; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 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.io.InputStream; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; -@Configuration public class FeignConfiguration { - private static Logger logger = LogManager.getLogger(FeignConfiguration.class); - - private int retryMaxAttempt = 3; - - private int retryMaxInterval = 5; + private static final Logger logger = LogManager.getLogger(FeignConfiguration.class); - private String ontapFeignMaxConnection = "80"; + private final int retryMaxAttempt = 3; + private final int retryMaxInterval = 5; + private final String ontapFeignMaxConnection = "80"; + private final String ontapFeignMaxConnectionPerRoute = "20"; + private final JsonMapper jsonMapper; - private String ontapFeignMaxConnectionPerRoute = "20"; - - @Bean - public Client client(ApacheHttpClientFactory httpClientFactory) { + public FeignConfiguration() { + this.jsonMapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .findAndAddModules() + .build(); + } + public Client createClient() { int maxConn; int maxConnPerRoute; 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 = httpClientFactory.createBuilder() + CloseableHttpClient httpClient = HttpClientBuilder.create() .setMaxConnTotal(maxConn) .setMaxConnPerRoute(maxConnPerRoute) .setKeepAliveStrategy(keepAliveStrategy) @@ -83,7 +74,6 @@ public Client client(ApacheHttpClientFactory httpClientFactory) { 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) { @@ -91,22 +81,57 @@ 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(), StandardCharsets.UTF_8)); + } + }; + } - @Bean - public RequestInterceptor requestInterceptor() { - return new RequestInterceptor() { + public Retryer createRetryer() { + return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt); + } + + 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 { + if (object == null) { + template.body(null, StandardCharsets.UTF_8); + return; + } + try { + byte[] jsonBytes = jsonMapper.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); + } } }; } - @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; + } + String json = null; + try (InputStream bodyStream = response.body().asInputStream()) { + json = new String(bodyStream.readAllBytes(), StandardCharsets.UTF_8); + logger.debug("Decoding JSON response: {}", json); + return jsonMapper.readValue(json, jsonMapper.getTypeFactory().constructType(type)); + } 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); + } + } + }; } } 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..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 @@ -20,26 +20,18 @@ 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 /api/storage/aggregates") + @Headers({"Authorization: {authHeader}"}) + OntapResponse getAggregateResponse(@Param("authHeader") String authHeader); + @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 7758a846f361..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 @@ -19,19 +19,14 @@ 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 /api/cluster") + @Headers({"Authorization: {authHeader}", "return_records: {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 4becf7bb29c4..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 @@ -18,25 +18,14 @@ */ 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 java.net.URI; +import feign.Headers; +import feign.Param; +import feign.RequestLine; -/** - * @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 /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 6e7b37d9378f..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 @@ -19,61 +19,65 @@ 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 java.net.URI; +import feign.Headers; +import feign.Param; +import feign.RequestLine; -/** - * @author Administrator - * - */ -@Lazy -@FeignClient(name = "NASClient", url = "" , configuration = FeignConfiguration.class) +//TODO: Proper URLs should be added in the RequestLine annotations below 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("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath); + @RequestLine("DELETE /{volumeUuid}/files/{path}") + @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("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath, FileInfo fileInfo); - //Export Policy Operations + @RequestLine("POST /{volumeUuid}/files/{path}") + @Headers({"Authorization: {authHeader}"}) + void createFile(@Param("authHeader") String authHeader, + @Param("volumeUuid") String volumeUUID, + @Param("path") String filePath, 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("authHeader") String authHeader, + @Param("returnRecords") boolean returnRecords, + 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("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("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("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("authHeader") String authHeader, + @Param("id") String id, + 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..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 @@ -20,72 +20,70 @@ 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 ) +//TODO: Proper URLs should be added in the RequestLine annotations below 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("authHeader") String authHeader, + @Param("returnRecords") boolean returnRecords, + Lun lun); + + @RequestLine("GET /") + @Headers({"Authorization: {authHeader}"}) + OntapResponse getLunResponse(@Param("authHeader") String authHeader); + + @RequestLine("GET /{uuid}") + @Headers({"Authorization: {authHeader}"}) + Lun getLunByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); + + @RequestLine("PATCH /{uuid}") + @Headers({"Authorization: {authHeader}"}) + void updateLun(@Param("authHeader") String authHeader, @Param("uuid") String uuid, Lun lun); + + @RequestLine("DELETE /{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("authHeader") String authHeader, + @Param("returnRecords") boolean returnRecords, + Igroup igroupRequest); + + @RequestLine("GET /") + @Headers({"Authorization: {authHeader}"}) // TODO: Check this again, uuid should be part of the path? + OntapResponse getIgroupResponse(@Param("authHeader") String authHeader, @Param("uuid") String uuid); + + @RequestLine("GET /{uuid}") + @Headers({"Authorization: {authHeader}"}) + Igroup getIgroupByUUID(@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); + + // LUN Maps Operation APIs + @RequestLine("POST /") + @Headers({"Authorization: {authHeader}"}) + OntapResponse createLunMap(@Param("authHeader") String authHeader, LunMap lunMap); + + @RequestLine("GET /") + @Headers({"Authorization: {authHeader}"}) + OntapResponse getLunMapResponse(@Param("authHeader") String authHeader); + + @RequestLine("DELETE /{lunUuid}/{igroupUuid}") + @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 c0091c8733e9..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 @@ -19,23 +19,22 @@ package org.apache.cloudstack.storage.feign.client; -import org.apache.cloudstack.storage.feign.FeignConfiguration; +import feign.QueryMap; 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 java.net.URI; +import feign.Headers; +import feign.Param; +import feign.RequestLine; +import java.util.Map; -@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); + // SVM Operation APIs + @RequestLine("GET /api/svm/svms") + @Headers({"Authorization: {authHeader}"}) + OntapResponse getSvmResponse(@QueryMap Map queryMap, @Param("authHeader") String authHeader); + @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 af92754da42e..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 @@ -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 java.net.URI; - +import feign.Headers; +import feign.Param; +import feign.RequestLine; -@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); + @RequestLine("DELETE /api/storage/volumes/{uuid}") + @Headers({"Authorization: {authHeader}"}) + void deleteVolume(@Param("authHeader") String authHeader, @Param("uuid") String uuid); - @RequestMapping(method = RequestMethod.POST) - JobResponse createVolumeWithJob(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest); + @RequestLine("POST /api/storage/volumes") + @Headers({"Authorization: {authHeader}"}) + JobResponse createVolumeWithJob(@Param("authHeader") String authHeader, Volume volumeRequest); - @RequestMapping(method = RequestMethod.GET, value="/{uuid}") - Volume getVolumeByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid); - - @RequestMapping(method = RequestMethod.PATCH) - JobResponse updateVolumeRebalancing(URI baseURL, @RequestHeader("accept") String acceptHeader, @PathVariable("uuid") String uuid, @RequestBody Volume volumeRequest); + @RequestLine("GET /api/storage/volumes/{uuid}") + @Headers({"Authorization: {authHeader}"}) + Volume getVolumeByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid); + @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/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 017dfff23d27..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 @@ -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 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; @@ -43,47 +42,23 @@ public String getUsername() { return username; } - public void setUsername(String username) { - username = username; - } - public String getPassword() { return password; } - public void setPassword(String password) { - password = password; - } - public String getManagementLIF() { return managementLIF; } - public void setManagementLIF(String managementLIF) { - managementLIF = managementLIF; - } - public String getSvmName() { return svmName; } - public void setSvmName(String svmName) { - svmName = svmName; - } - public ProtocolType getProtocol() { return protocolType; } - public void setProtocol(ProtocolType protocolType) { - protocolType = protocolType; - } - public Boolean getIsDisaggregated() { return isDisaggregated; } - - public void setIsDisaggregated(Boolean isDisaggregated) { - 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 e89c1f8426a6..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 @@ -19,12 +19,15 @@ 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") @@ -48,9 +51,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 +81,6 @@ public void setIscsiEnabled(Boolean iscsiEnabled) { this.iscsiEnabled = iscsiEnabled; } - public Boolean getFcpEnabled() { return fcpEnabled; } @@ -84,7 +89,6 @@ public void setFcpEnabled(Boolean fcpEnabled) { this.fcpEnabled = fcpEnabled; } - public Boolean getNfsEnabled() { return nfsEnabled; } @@ -93,7 +97,6 @@ public void setNfsEnabled(Boolean nfsEnabled) { this.nfsEnabled = nfsEnabled; } - public List getAggregates() { return aggregates; } @@ -102,7 +105,6 @@ public void setAggregates(List aggregates) { this.aggregates = aggregates; } - public Boolean getAggregatesDelegated() { return aggregatesDelegated; } @@ -111,7 +113,6 @@ public void setAggregatesDelegated(Boolean aggregatesDelegated) { this.aggregatesDelegated = aggregatesDelegated; } - public String getState() { return state; } @@ -120,6 +121,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 +140,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/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/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 1538cebfd373..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 @@ -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 { @@ -58,35 +59,49 @@ 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); + + // 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) { 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(); 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); + 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 + Map details = (Map) dsInfos.get("details"); + + // 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; + } + + // Validate scope if (podId == null ^ clusterId == null) { throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage"); } @@ -117,41 +132,98 @@ public DataStore initialize(Map dsInfos) { parameters.setHypervisorType(clusterVO.getHypervisorType()); } - // 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()); + // Required ONTAP detail keys + Set requiredKeys = Set.of( + Constants.USERNAME, + Constants.PASSWORD, + Constants.SVM_NAME, + Constants.PROTOCOL, + Constants.MANAGEMENT_LIF, + Constants.IS_DISAGGREGATED + ); + + // Parse key=value pairs from URL into details (skip empty segments) + if (url != null && !url.isEmpty()) { + 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()); + } + } + } + + // 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 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 + Set providedKeys = new java.util.HashSet<>(details.keySet()); + if (!providedKeys.containsAll(requiredKeys)) { + Set missing = new java.util.HashSet<>(requiredKeys); + missing.removeAll(providedKeys); + throw new CloudRuntimeException("ONTAP primary storage creation failed, missing detail(s): " + missing); + } + + details.put(Constants.SIZE, capacityBytes.toString()); + + // Default for IS_DISAGGREGATED if needed + details.putIfAbsent(Constants.IS_DISAGGREGATED, "false"); + + // Determine storage pool type and path based on protocol + String path; + ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL)); switch (protocol) { - case NFS: + case NFS3: parameters.setType(Storage.StoragePoolType.NetworkFilesystem); + 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); + 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: " + scheme + ", cannot create primary storage"); + 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"); - } + // 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())); - 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))); 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 + 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"); } - parameters.setTags(tags); - parameters.setIsTagARule(isTagARule); + // Set parameters for primary data store + parameters.setHost(details.get(Constants.MANAGEMENT_LIF)); + parameters.setPort(Constants.ONTAP_PORT); + parameters.setPath(path); + parameters.setTags(tags != null ? tags : ""); + parameters.setIsTagARule(isTagARule != null ? isTagARule : Boolean.FALSE); parameters.setDetails(details); parameters.setUuid(UUID.randomUUID().toString()); parameters.setZoneId(zoneId); @@ -159,7 +231,9 @@ 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); return _dataStoreHelper.createPrimaryDataStore(parameters); } @@ -251,3 +325,4 @@ public void changeStoragePoolScopeToCluster(DataStore store, ClusterScope cluste } } + 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..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 @@ -27,38 +27,31 @@ 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) { + public static StorageStrategy getStrategy(OntapStorage ontapStorage) { ProtocolType protocol = ontapStorage.getProtocol(); s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol); switch (protocol) { - case NFS: - if(!ontapStorage.getIsDisaggregated()) { - this.storageStrategy = new UnifiedNASStrategy(ontapStorage); - } else { - throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported."); + case NFS3: + 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()) { - this.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: - this.storageStrategy = null; throw new CloudRuntimeException("Unsupported protocol: " + protocol); } } - - public static StorageStrategy getStrategy(OntapStorage ontapStorage) { - return new StorageProviderFactory(ontapStorage).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..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,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; @@ -38,10 +39,8 @@ 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.Map; import java.util.Objects; /** @@ -52,43 +51,46 @@ * Supported platform: Unified and Disaggregated */ 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; + protected OntapStorage storage; /** * Presents aggregate object for the unified storage, not eligible for disaggregated */ 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; - + String baseURL = Constants.HTTPS + storage.getManagementLIF(); + s_logger.info("Initializing StorageStrategy with base URL: " + baseURL); + // Initialize FeignClientFactory and create clients + this.feignClientFactory = new FeignClientFactory(); + 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() + ", 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(); - URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GET_SVMs + "?name=" + svmName); - OntapResponse svms = svmFeignClient.getSvmResponse(url, authHeader); + 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 { @@ -96,6 +98,7 @@ public boolean connect() { } // 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."); @@ -115,7 +118,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.getMessage(), e); } return true; } @@ -127,8 +130,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"); @@ -139,7 +143,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(); @@ -153,16 +157,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(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(Constants.GET_JOB_BY_UUID); int jobRetryCount = 0; Job createVolumeJob = null; while(createVolumeJob == null || !createVolumeJob.getState().equals(Constants.JOB_SUCCESS)) { @@ -172,7 +174,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)) { @@ -199,7 +201,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 +215,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 +223,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 +242,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 +253,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 +264,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 +274,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 +284,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 +294,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,7 +303,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); @@ -304,7 +313,8 @@ public Volume getStorageVolume(Volume volume) * 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 + * @param accessGroup the access group to retrieve + * @return the retrieved AccessGroup object */ abstract AccessGroup getAccessGroup(AccessGroup accessGroup); @@ -323,5 +333,4 @@ public Volume getStorageVolume(Volume volume) * @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..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 @@ -19,36 +19,55 @@ package org.apache.cloudstack.storage.service; +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.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.Map; -public class UnifiedNASStrategy extends NASStrategy{ +public class UnifiedNASStrategy extends NASStrategy { + + private static final Logger s_logger = LogManager.getLogger(UnifiedNASStrategy.class); + // Add missing Feign client setup for NAS operations + private final FeignClientFactory feignClientFactory; + private final NASFeignClient nasFeignClient; + public UnifiedNASStrategy(OntapStorage ontapStorage) { super(ontapStorage); + String baseURL = Constants.HTTPS + ontapStorage.getManagementLIF(); + // Initialize FeignClientFactory and create NAS client + this.feignClientFactory = new FeignClientFactory(); + this.nasFeignClient = feignClientFactory.createClient(NASFeignClient.class, baseURL); + } + + public void setOntapStorage(OntapStorage ontapStorage) { + this.storage = ontapStorage; } @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 @@ -78,12 +97,11 @@ public AccessGroup getAccessGroup(AccessGroup accessGroup) { @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..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 @@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.service; 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; @@ -31,17 +32,25 @@ 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 { private static final Logger s_logger = LogManager.getLogger(UnifiedSANStrategy.class); - @Inject private Utility utils; - @Inject private SANFeignClient sanFeignClient; + // Replace @Inject Feign client with FeignClientFactory + private final FeignClientFactory feignClientFactory; + private final SANFeignClient sanFeignClient; + public UnifiedSANStrategy(OntapStorage ontapStorage) { super(ontapStorage); + String baseURL = Constants.HTTPS + ontapStorage.getManagementLIF(); + // Initialize FeignClientFactory and create SAN client + this.feignClientFactory = new FeignClientFactory(); + this.sanFeignClient = feignClientFactory.createClient(SANFeignClient.class, baseURL); + } + + public void setOntapStorage(OntapStorage ontapStorage) { + this.storage = ontapStorage; } @Override @@ -53,11 +62,10 @@ 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 - URI url = utils.generateURI(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()); @@ -83,7 +91,7 @@ CloudStackVolume updateCloudStackVolume(CloudStackVolume cloudstackVolume) { @Override void deleteCloudStackVolume(CloudStackVolume cloudstackVolume) { - + //TODO } @Override @@ -100,7 +108,7 @@ public AccessGroup createAccessGroup(AccessGroup accessGroup) { @Override public void deleteAccessGroup(AccessGroup accessGroup) { - + //TODO } @Override @@ -117,12 +125,11 @@ public AccessGroup getAccessGroup(AccessGroup accessGroup) { @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 61674db7c8b0..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 @@ -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"; @@ -31,25 +32,32 @@ 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/"; 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 c1fc47aef90b..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 @@ -22,33 +22,21 @@ import com.cloud.utils.StringUtils; import com.cloud.utils.exception.CloudRuntimeException; 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.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 static final String BASIC = "Basic"; private static final String AUTH_HEADER_COLON = ":"; @@ -60,17 +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 path) { - String uriString = Constants.HTTPS + ontapStorage.getManagementLIF() + 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); @@ -107,24 +90,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"); - } - } }