diff --git a/plugins/storage/volume/ontap/pom.xml b/plugins/storage/volume/ontap/pom.xml
index d538d555cb10..7460ff2068ec 100644
--- a/plugins/storage/volume/ontap/pom.xml
+++ b/plugins/storage/volume/ontap/pom.xml
@@ -76,6 +76,16 @@
feign-httpclient
${openfeign.version}
+
+
+
+
+
+
+ io.github.openfeign
+ feign-jackson
+ ${openfeign.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 3310064406fd..22a9c8f4bc65 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
@@ -37,15 +37,16 @@
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.storage.command.CommandResult;
-import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
import java.util.HashMap;
import java.util.Map;
public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
- private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreDriver.class);
+ private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class);
@Override
public Map getCapabilities() {
s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java
new file mode 100644
index 000000000000..ab4c17d098f1
--- /dev/null
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignClientFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cloudstack.storage.feign;
+
+import feign.Feign;
+
+public class FeignClientFactory {
+
+ private final FeignConfiguration feignConfiguration;
+
+ public FeignClientFactory() {
+ this.feignConfiguration = new FeignConfiguration();
+ }
+
+ public FeignClientFactory(FeignConfiguration feignConfiguration) {
+ this.feignConfiguration = feignConfiguration;
+ }
+
+ public T createClient(Class clientClass) {
+ return Feign.builder()
+ .client(feignConfiguration.createClient())
+ .encoder(feignConfiguration.createEncoder())
+ .decoder(feignConfiguration.createDecoder())
+// .logger(feignConfiguration.createLogger())
+ .retryer(feignConfiguration.createRetryer())
+ .requestInterceptor(feignConfiguration.createRequestInterceptor())
+ .target(clientClass, "https://10.196.38.171/");
+ }
+}
\ No newline at end of file
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..278ab169a61f 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/FeignConfiguration.java
@@ -19,41 +19,43 @@
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.databind.ObjectMapper;
+import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import feign.Client;
-import feign.httpclient.ApacheHttpClient;
+
import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
-@Configuration
public class FeignConfiguration {
- private static Logger logger = LogManager.getLogger(FeignConfiguration.class);
+ private static final Logger logger = LogManager.getLogger(FeignConfiguration.class);
- private int retryMaxAttempt = 3;
-
- private int retryMaxInterval = 5;
-
- private String ontapFeignMaxConnection = "80";
-
- private String ontapFeignMaxConnectionPerRoute = "20";
-
- @Bean
- public Client client(ApacheHttpClientFactory httpClientFactory) {
+ private final int retryMaxAttempt = 3;
+ private final int retryMaxInterval = 5;
+ private final String ontapFeignMaxConnection = "80";
+ private final String ontapFeignMaxConnectionPerRoute = "20";
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ public Client createClient() {
int maxConn;
int maxConnPerRoute;
try {
@@ -68,10 +70,11 @@ public Client client(ApacheHttpClientFactory httpClientFactory) {
logger.error("ontapFeignClient: encounter exception while parse the max connection per route from env. setting default value");
maxConnPerRoute = 2;
}
+
// Disable Keep Alive for Http Connection
logger.debug("ontapFeignClient: Setting the feign client config values as max connection: {}, max connections per route: {}", maxConn, maxConnPerRoute);
ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> 0;
- CloseableHttpClient httpClient = httpClientFactory.createBuilder()
+ CloseableHttpClient httpClient = HttpClientBuilder.create()
.setMaxConnTotal(maxConn)
.setMaxConnPerRoute(maxConnPerRoute)
.setKeepAliveStrategy(keepAliveStrategy)
@@ -91,22 +94,55 @@ private SSLConnectionSocketFactory getSSLSocketFactory() {
}
}
+ public RequestInterceptor createRequestInterceptor() {
+ return template -> {
+ logger.info("Feign Request URL: {}", template.url());
+ logger.info("HTTP Method: {}", template.method());
+ logger.info("Headers: {}", template.headers());
+ if (template.body() != null) {
+ logger.info("Body: {}", new String(template.body()));
+ }
+ };
+ }
+
+ public Retryer createRetryer() {
+ return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt);
+ }
- @Bean
- public RequestInterceptor requestInterceptor() {
- return new RequestInterceptor() {
+ public Encoder createEncoder() {
+ return new Encoder() {
@Override
- public void apply(RequestTemplate template) {
- logger.info("Feign Request URL: {}", template.url());
- logger.info("HTTP Method: {}", template.method());
- logger.info("Headers: {}", template.headers());
- logger.info("Body: {}", template.requestBody().asString());
+ public void encode(Object object, Type bodyType, feign.RequestTemplate template) throws EncodeException {
+ try {
+ String json = objectMapper.writeValueAsString(object);
+ logger.debug("Encoding object to JSON: {}", json);
+
+ // Use the String version - it handles charset conversion internally
+ template.body(json);
+
+ template.header("Content-Type", "application/json");
+ } catch (JsonProcessingException e) {
+ throw new EncodeException("Error encoding object to JSON", e);
+ }
}
};
}
- @Bean
- public Retryer feignRetryer() {
- return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt);
+ public Decoder createDecoder() {
+ return new Decoder() {
+ @Override
+ public Object decode(Response response, Type type) throws IOException, DecodeException {
+ if (response.body() == null) {
+ return null;
+ }
+ try {
+ String json = new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8);
+ return objectMapper.readValue(json, objectMapper.getTypeFactory().constructType(type));
+ } catch (IOException e) {
+ throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e);
+ }
+ }
+ };
}
-}
+
+}
\ No newline at end of file
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java
index ed57bf419405..12ef7316df10 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java
@@ -20,26 +20,19 @@
package org.apache.cloudstack.storage.feign.client;
import org.apache.cloudstack.storage.feign.model.Aggregate;
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-@Lazy
-@FeignClient(name="AggregateClient", url="https://{clusterIP}/api/storage/aggregates", configuration = FeignConfiguration.class)
public interface AggregateFeignClient {
- //this method to get all aggregates and also filtered aggregates based on query params as a part of URL
- @RequestMapping(method=RequestMethod.GET)
- OntapResponse getAggregateResponse(URI baseURL, @RequestHeader("Authorization") String header);
-
- @RequestMapping(method=RequestMethod.GET, value="/{uuid}")
- Aggregate getAggregateByUUID(URI baseURL,@RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid);
+ @RequestLine("GET /")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getAggregateResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader);
+ @RequestLine("GET /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ Aggregate getAggregateByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java
index 7758a846f361..dcd12e60d281 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/ClusterFeignClient.java
@@ -19,19 +19,15 @@
package org.apache.cloudstack.storage.feign.client;
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.Cluster;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-@FeignClient(name="ClusterClient", url="https://{clusterIP}/api/cluster", configuration = FeignConfiguration.class)
public interface ClusterFeignClient {
- @RequestMapping(method= RequestMethod.GET)
- Cluster getCluster(URI baseURL, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value);
-
+ @RequestLine("GET /")
+ @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
+ Cluster getCluster(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("returnRecords") boolean returnRecords);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java
index 4becf7bb29c4..d925ea931037 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/JobFeignClient.java
@@ -18,25 +18,15 @@
*/
package org.apache.cloudstack.storage.feign.client;
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.Job;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-/**
- * @author Administrator
- *
- */
-@Lazy
-@FeignClient(name = "JobClient", url = "https://{clusterIP}/api/cluster/jobs" , configuration = FeignConfiguration.class)
public interface JobFeignClient {
- @RequestMapping(method = RequestMethod.GET, value="/{uuid}")
- Job getJobByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid);
-
+ @RequestLine("GET /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ Job getJobByUUID(URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/NASFeignClient.java
index 6e7b37d9378f..ef1369bf831a 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,68 @@
package org.apache.cloudstack.storage.feign.client;
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.ExportPolicy;
import org.apache.cloudstack.storage.feign.model.FileInfo;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMethod;
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-/**
- * @author Administrator
- *
- */
-@Lazy
-@FeignClient(name = "NASClient", url = "" , configuration = FeignConfiguration.class)
public interface NASFeignClient {
- //File Operations
-
- @RequestMapping(method = RequestMethod.GET, value="/{volume.uuid}/files/{path}")
- OntapResponse getFileResponse(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID,
- @PathVariable(name = "path", required = true) String filePath);
- @RequestMapping(method = RequestMethod.DELETE, value="/{volume.uuid}/files/{path}")
- void deleteFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID,
- @PathVariable(name = "path", required = true) String filePath);
- @RequestMapping(method = RequestMethod.PATCH, value="/{volume.uuid}/files/{path}")
- void updateFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID,
- @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo fileInfo);
- @RequestMapping(method = RequestMethod.POST, value="/{volume.uuid}/files/{path}")
- void createFile(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "volume.uuid", required = true) String volumeUUID,
- @PathVariable(name = "path", required = true) String filePath, @RequestBody FileInfo file);
+ // File Operations
+ @RequestLine("GET /{volumeUuid}/files/{path}")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getFileResponse(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("volumeUuid") String volumeUUID,
+ @Param("path") String filePath);
+ @RequestLine("DELETE /{volumeUuid}/files/{path}")
+ @Headers("Authorization: {authHeader}")
+ void deleteFile(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("volumeUuid") String volumeUUID,
+ @Param("path") String filePath);
+ @RequestLine("PATCH /{volumeUuid}/files/{path}")
+ @Headers("Authorization: {authHeader}")
+ void updateFile(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("volumeUuid") String volumeUUID,
+ @Param("path") String filePath,
+ @Param("fileInfo") FileInfo fileInfo);
- //Export Policy Operations
+ @RequestLine("POST /{volumeUuid}/files/{path}")
+ @Headers("Authorization: {authHeader}")
+ void createFile(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("volumeUuid") String volumeUUID,
+ @Param("path") String filePath,
+ @Param("file") FileInfo file);
- @RequestMapping(method = RequestMethod.POST)
- ExportPolicy createExportPolicy(URI uri, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value,
- @RequestBody ExportPolicy exportPolicy);
+ // TODO this needs to be moved to different feignclient as baseURL in feign-core needs to be same for all methods in a class
+ // Export Policy Operations
+ @RequestLine("POST /")
+ @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
+ ExportPolicy createExportPolicy(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("returnRecords") boolean returnRecords,
+ @Param("exportPolicy") ExportPolicy exportPolicy);
- //this method to get all export policies and also filtered export policy based on query params as a part of URL
- @RequestMapping(method = RequestMethod.GET)
- OntapResponse getExportPolicyResponse(URI baseURL, @RequestHeader("Authorization") String header);
+ @RequestLine("GET /")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getExportPolicyResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader);
- @RequestMapping(method = RequestMethod.GET, value="/{id}")
- OntapResponse getExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id);
+ @RequestLine("GET /{id}")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getExportPolicyById(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader,
+ @Param("id") String id);
- @RequestMapping(method = RequestMethod.DELETE, value="/{id}")
- void deleteExportPolicyById(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id);
+ @RequestLine("DELETE /{id}")
+ @Headers("Authorization: {authHeader}")
+ void deleteExportPolicyById(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader,
+ @Param("id") String id);
- @RequestMapping(method = RequestMethod.PATCH, value="/{id}")
- OntapResponse updateExportPolicy(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "id", required = true) String id,
- @RequestBody ExportPolicy request);
+ @RequestLine("PATCH /{id}")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse updateExportPolicy(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader,
+ @Param("id") String id,
+ @Param("request") ExportPolicy request);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java
index 325823f8515c..381698eb9f0c 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SANFeignClient.java
@@ -20,72 +20,76 @@
import org.apache.cloudstack.storage.feign.model.Igroup;
import org.apache.cloudstack.storage.feign.model.Lun;
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.LunMap;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestHeader;
-
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-@Lazy
-@FeignClient(name = "SANClient", url = "", configuration = FeignConfiguration.class )
public interface SANFeignClient {
- //Lun Operation APIs
- @RequestMapping(method = RequestMethod.POST)
- OntapResponse createLun(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestHeader("return_records") boolean value,
- @RequestBody Lun lun);
-
- //this method to get all luns and also filtered luns based on query params as a part of URL
- @RequestMapping(method = RequestMethod.GET)
- OntapResponse getLunResponse(URI baseURL, @RequestHeader("Authorization") String authHeader);
-
- @RequestMapping(method = RequestMethod.GET, value = "/{uuid}")
- Lun getLunByUUID(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid);
-
- @RequestMapping(method = RequestMethod.PATCH, value = "/{uuid}")
- void updateLun(URI uri, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid,
- @RequestBody Lun lun);
-
- @RequestMapping(method = RequestMethod.DELETE, value = "/{uuid}")
- void deleteLun(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="uuid", required=true) String uuid);
-
-
- //iGroup Operation APIs
-
- @RequestMapping(method = RequestMethod.POST)
- OntapResponse createIgroup(URI uri, @RequestHeader("Authorization") String header, @RequestHeader("return_records") boolean value,
- @RequestBody Igroup igroupRequest);
-
- //this method to get all igroups and also filtered igroups based on query params as a part of URL
- @RequestMapping(method = RequestMethod.GET)
- OntapResponse getIgroupResponse(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid);
- @RequestMapping(method = RequestMethod.GET, value = "/{uuid}")
- Igroup getIgroupByUUID(URI baseURL, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid);
- @RequestMapping(method = RequestMethod.DELETE, value = "/{uuid}")
- void deleteIgroup(URI baseUri, @RequestHeader("Authorization") String authHeader, @PathVariable(name = "uuid", required = true) String uuid);
-
- @RequestMapping(method = RequestMethod.POST, value = "/{uuid}/igroups")
- OntapResponse addNestedIgroups(URI uri, @RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid,
- @RequestBody Igroup igroupNestedRequest, @RequestHeader(value="return_records", defaultValue = "true") boolean value);
-
-
- //Lun Maps Operation APIs
-
- @RequestMapping(method = RequestMethod.POST)
- OntapResponse createLunMap(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody LunMap lunMap);
-
- @RequestMapping(method = RequestMethod.GET)
- OntapResponse getLunMapResponse(URI baseURL, @RequestHeader("Authorization") String authHeader);
-
- @RequestMapping(method = RequestMethod.GET, value = "/{lun.uuid}/{igroup.uuid}")
- void deleteLunMap(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable(name="lun.uuid", required=true) String uuid,
- @PathVariable(name="igroup.uuid", required=true) String igroupUUID);
-
+ // LUN Operation APIs
+ @RequestLine("POST /")
+ @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
+ OntapResponse createLun(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader,
+ @Param("returnRecords") boolean returnRecords,
+ @Param("lun") Lun lun);
+
+ @RequestLine("GET /")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getLunResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader);
+
+ @RequestLine("GET /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ Lun getLunByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+ @RequestLine("PATCH /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ void updateLun(@Param("uri") URI uri, @Param("authHeader") String authHeader, @Param("uuid") String uuid, @Param("lun") Lun lun);
+
+ @RequestLine("DELETE /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ void deleteLun(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+ // iGroup Operation APIs
+ @RequestLine("POST /")
+ @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
+ OntapResponse createIgroup(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("returnRecords") boolean returnRecords,
+ @Param("igroupRequest") Igroup igroupRequest);
+
+ @RequestLine("GET /")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getIgroupResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+ @RequestLine("GET /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ Igroup getIgroupByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+ @RequestLine("DELETE /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ void deleteIgroup(@Param("baseUri") URI baseUri, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+ @RequestLine("POST /{uuid}/igroups")
+ @Headers({"Authorization: {authHeader}", "return_records: {returnRecords}"})
+ OntapResponse addNestedIgroups(@Param("uri") URI uri, @Param("authHeader") String authHeader,
+ @Param("uuid") String uuid,
+ @Param("igroupNestedRequest") Igroup igroupNestedRequest,
+ @Param("returnRecords") boolean returnRecords);
+
+ // LUN Maps Operation APIs
+ @RequestLine("POST /")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse createLunMap(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("lunMap") LunMap lunMap);
+
+ @RequestLine("GET /")
+ @Headers("Authorization: {authHeader}")
+ OntapResponse getLunMapResponse(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader);
+
+ @RequestLine("DELETE /{lunUuid}/{igroupUuid}")
+ @Headers("Authorization: {authHeader}")
+ void deleteLunMap(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader,
+ @Param("lunUuid") String lunUuid,
+ @Param("igroupUuid") String igroupUuid);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/SvmFeignClient.java
old mode 100644
new mode 100755
index 753595713c25..9c5f395b4d6f
--- 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,26 +19,23 @@
package org.apache.cloudstack.storage.feign.client;
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.Svm;
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-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);
+ @RequestLine("GET")
+ @Headers({
+ "Authorization: {authHeader}",
+ "Accept: application/json"
+ })
+ OntapResponse getSvmResponse(URI uri, @Param("authHeader") String authHeader);
+ @RequestLine("GET /{uuid}")
+ @Headers("Authorization: {authHeader}")
+ Svm getSvmByUUID(@Param("baseURL") URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/VolumeFeignClient.java
index af92754da42e..75f91c190aee 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,42 @@
*/
package org.apache.cloudstack.storage.feign.client;
-
-import org.apache.cloudstack.storage.feign.FeignConfiguration;
import org.apache.cloudstack.storage.feign.model.Volume;
import org.apache.cloudstack.storage.feign.model.response.JobResponse;
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMethod;
-
+import feign.Headers;
+import feign.Param;
+import feign.RequestLine;
import java.net.URI;
-
-@Lazy
-@FeignClient(name = "VolumeClient", url = "https://{clusterIP}/api/storage/volumes", configuration = FeignConfiguration.class)
public interface VolumeFeignClient {
- @RequestMapping(method = RequestMethod.DELETE, value="/{uuid}")
- void deleteVolume(URI baseURL, @RequestHeader("Authorization") String authHeader, @PathVariable("uuid") String uuid);
-
- @RequestMapping(method = RequestMethod.POST)
- JobResponse createVolumeWithJob(URI baseURL, @RequestHeader("Authorization") String authHeader, @RequestBody Volume volumeRequest);
-
- @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("DELETE")
+// @Headers({
+// "Authorization: {authHeader}",
+// "Accept: application/json"
+// })
+// void deleteVolume(URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+ @RequestLine("POST /api/storage/volumes")
+ @Headers({
+ "Authorization: {authHeader}",
+ "Content-Type: application/json",
+ "Accept: application/json"
+ })
+ JobResponse createVolumeWithJob(@Param("authHeader") String authHeader, Volume volumeRequest);
+
+ @RequestLine("GET")
+ @Headers({
+ "Authorization: {authHeader}",
+ "Accept: application/json"
+ })
+ Volume getVolumeByUUID(URI baseURL, @Param("authHeader") String authHeader, @Param("uuid") String uuid);
+
+// @RequestLine("PATCH")
+// @Headers({
+// "Authorization: {authHeader}",
+// "Content-Type: application/json",
+// "Accept: application/json"
+// })
+// JobResponse updateVolumeRebalancing(URI baseURL, @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/Job.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Job.java
index a1a0c2698f14..dd26aa2d7663 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
@@ -19,14 +19,16 @@
package org.apache.cloudstack.storage.feign.model;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
* @author Administrator
*
*/
@JsonInclude(Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
public class Job {
@JsonProperty("uuid")
@@ -85,14 +87,14 @@ public String toString() {
}
public static class Links {
- @JsonProperty("message")
+ @JsonProperty("self")
private Self self;
public Self getSelf() { return self; }
public void setSelf(Self self) { this.self = self; }
}
public static class Self {
- @JsonProperty("message")
+ @JsonProperty("href")
private String href;
public String getHref() { return href; }
public void setHref(String href) { this.href = href; }
@@ -100,11 +102,11 @@ public static class Self {
public static class JobError {
@JsonProperty("message")
- String errorMesssage;
+ String errorMessage;
@JsonProperty("code")
String code;
- public String getErrorMesssage () { return errorMesssage; }
- public void setErrorMesssage (String errorMesssage) { this.errorMesssage = errorMesssage; }
+ public String getErrorMesssage () { return errorMessage; }
+ public void setErrorMesssage (String errorMesssage) { this.errorMessage = errorMesssage; }
public String getCode() {
return code;
}
@@ -113,7 +115,7 @@ public void setCode(String code) {
}
@Override
public String toString() {
- return "JobError [errorMesssage=" + errorMesssage + ", code=" + code + "]";
+ return "JobError [errorMesssage=" + errorMessage + ", code=" + code + "]";
}
}
}
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 af986e5fdc39..2667ffca8480 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/OntapStorage.java
@@ -22,67 +22,45 @@
import org.apache.cloudstack.storage.utils.Constants.ProtocolType;
public class OntapStorage {
- public static String _username;
- public static String _password;
- public static String _managementLIF;
- public static String _svmName;
- public static ProtocolType _protocolType;
- public static 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) {
- _username = username;
- _password = password;
- _managementLIF = managementLIF;
- _svmName = svmName;
- _protocolType = protocolType;
- _isDisaggregated = isDisaggregated;
+ this.username = username;
+ this.password = password;
+ this.managementLIF = managementLIF;
+ this.svmName = svmName;
+ this.protocolType = protocolType;
+ this.isDisaggregated = isDisaggregated;
}
public String getUsername() {
- return _username;
+ return username;
}
- public void setUsername(String username) {
- _username = username;
- }
public String getPassword() {
- return _password;
- }
-
- public void setPassword(String password) {
- _password = password;
+ return password;
}
public String getManagementLIF() {
- return _managementLIF;
- }
-
- public void setManagementLIF(String managementLIF) {
- _managementLIF = managementLIF;
+ return managementLIF;
}
public String getSvmName() {
- return _svmName;
+ return svmName;
}
- public void setSvmName(String svmName) {
- _svmName = svmName;
- }
-
- public ProtocolType getProtocol() {
- return _protocolType;
- }
-
- public void setProtocol(ProtocolType protocolType) {
- _protocolType = protocolType;
+ public ProtocolType getProtocolType() {
+ return protocolType;
}
public Boolean getIsDisaggregated() {
- return _isDisaggregated;
+ 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/Volume.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/model/Volume.java
index 3d384c56db2e..bcb26be1c7a5 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
@@ -20,12 +20,14 @@
package org.apache.cloudstack.storage.feign.model;
import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.Objects;
@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
public class Volume {
@JsonProperty("uuid")
private String uuid;
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 722ed1e8c707..6271b6672b72 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
@@ -28,6 +28,7 @@
import com.cloud.resource.ResourceManager;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
+import com.cloud.utils.component.ComponentContext;
import com.cloud.storage.StoragePool;
import com.cloud.utils.exception.CloudRuntimeException;
import com.google.common.base.Preconditions;
@@ -45,47 +46,58 @@
import org.apache.cloudstack.storage.utils.Constants;
import org.apache.cloudstack.storage.utils.Constants.ProtocolType;
import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
-import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
import javax.inject.Inject;
import java.util.List;
import java.util.Map;
+import java.util.HashMap;
+import java.util.StringTokenizer;
import java.util.UUID;
public class OntapPrimaryDatastoreLifecycle extends BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
- @Inject private ClusterDao _clusterDao;
- @Inject private StorageManager _storageMgr;
- @Inject private ResourceManager _resourceMgr;
- @Inject private PrimaryDataStoreHelper _dataStoreHelper;
- private static final Logger s_logger = (Logger)LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class);
+ @Inject
+ private ClusterDao _clusterDao;
+ @Inject
+ private StorageManager _storageMgr;
+ @Inject
+ private ResourceManager _resourceMgr;
+ @Inject
+ private PrimaryDataStoreHelper _dataStoreHelper;
+ private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreLifecycle.class);
/**
* Creates primary storage on NetApp storage
+ *
* @param dsInfos
* @return
*/
@Override
public DataStore initialize(Map dsInfos) {
+ s_logger.debug("initialize method called with datastore info {}", 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");
+ 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 tags = dsInfos.get("tags").toString().trim(); // this should be null checked as optional
Boolean isTagARule = (Boolean) dsInfos.get("isTagARule");
String scheme = dsInfos.get("scheme").toString();
+ String volSizeInBytes = dsInfos.get("capacityBytes").toString();
+ String capacityIops = dsInfos.get("capacityIops").toString();// this is fetched from url in internal cs impl and is first part of url
s_logger.info("Creating ONTAP primary storage pool with name: " + storagePoolName + ", provider: " + providerName +
", zoneId: " + zoneId + ", podId: " + podId + ", clusterId: " + clusterId + ", protocol: " + scheme);
// Additional details requested for ONTAP primary storage pool creation
@SuppressWarnings("unchecked")
- Map details = (Map)dsInfos.get("details");
+ Map details = (Map) dsInfos.get("details");
// Validations
if (podId == null ^ clusterId == null) {
throw new CloudRuntimeException("Cluster Id or Pod Id is null, cannot create primary storage");
@@ -119,33 +131,43 @@ public DataStore initialize(Map dsInfos) {
// 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());
- switch (protocol) {
- case NFS:
+ //ProtocolType protocol = ProtocolType.valueOf(details.get(Constants.PROTOCOL).toLowerCase());
+ switch (scheme) {
+ case "nfs":
parameters.setType(Storage.StoragePoolType.NetworkFilesystem);
+ String storagePath = url + ":/" + storagePoolName; // TODO need to see where is this used
+ parameters.setPath(storagePath);
break;
- case ISCSI:
+ case "iscsi":
parameters.setType(Storage.StoragePoolType.Iscsi);
break;
default:
throw new CloudRuntimeException("Unsupported protocol: " + scheme + ", cannot create primary storage");
}
- details.put(Constants.MANAGEMENT_LIF, url);
+ Map extractedDetails = extractDetails(url);
- // Validate the ONTAP details
- if(details.get(Constants.IS_DISAGGREGATED) == null || details.get(Constants.IS_DISAGGREGATED).isEmpty()) {
- details.put(Constants.IS_DISAGGREGATED, "false");
- }
+ String mLIF = extractedDetails.get("ip");
+ details.put(Constants.MANAGEMENT_LIF, mLIF); // TODO need to fetch ip from URL
- 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);
+ // Validate the ONTAP details
+// if(details.get(Constants.IS_DISAGGREGATED) == null || details.get(Constants.IS_DISAGGREGATED).isEmpty()) {
+// details.put(Constants.IS_DISAGGREGATED, "false");
+// }
+
+ // TODO hardcoded for now
+ details.put(Constants.IS_DISAGGREGATED, "false");
+
+ OntapStorage ontapStorage = new OntapStorage(extractedDetails.get(Constants.USERNAME), extractedDetails.get(Constants.PASSWORD),
+ "10.196.38.171", extractedDetails.get(Constants.SVM_NAME), ProtocolType.NFS,
+ Boolean.parseBoolean(extractedDetails.get(Constants.IS_DISAGGREGATED))); // TODO hardcoded for testing
+ s_logger.debug("ontap storage object is {}", ontapStorage);
+ StorageProviderFactory providerFactory = ComponentContext.inject(StorageProviderFactory.class);
+ StorageStrategy storageStrategy = providerFactory.getStrategy(ontapStorage);
boolean isValid = storageStrategy.connect();
if (isValid) {
// String volumeName = storagePoolName + "_vol"; //TODO: Figure out a better naming convention
- storageStrategy.createVolume(storagePoolName, Long.parseLong((details.get("size")))); // TODO: size should be in bytes, so see if conversion is needed
+ storageStrategy.createVolume(storagePoolName, Long.parseLong((volSizeInBytes))); // TODO: size should be in bytes, so see if conversion is needed
} else {
throw new CloudRuntimeException("ONTAP details validation failed, cannot create primary storage");
}
@@ -160,6 +182,10 @@ public DataStore initialize(Map dsInfos) {
parameters.setName(storagePoolName);
parameters.setProviderName(providerName);
parameters.setManaged(true);
+ parameters.setHost("10.196.38.171");
+ parameters.setCapacityBytes(Long.parseLong((volSizeInBytes)));
+ parameters.setCapacityIops(Long.parseLong(capacityIops));
+ parameters.setUsedBytes(0);
return _dataStoreHelper.createPrimaryDataStore(parameters);
}
@@ -167,7 +193,7 @@ public DataStore initialize(Map dsInfos) {
@Override
public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
logger.debug("In attachCluster for ONTAP primary storage");
- PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo)dataStore;
+ PrimaryDataStoreInfo primarystore = (PrimaryDataStoreInfo) dataStore;
List hostsToConnect = _resourceMgr.getEligibleUpAndEnabledHostsInClusterForStorageConnection(primarystore);
logger.debug(String.format("Attaching the pool to each of the hosts %s in the cluster: %s", hostsToConnect, primarystore.getClusterId()));
@@ -250,4 +276,54 @@ public void changeStoragePoolScopeToZone(DataStore store, ClusterScope clusterSc
public void changeStoragePoolScopeToCluster(DataStore store, ClusterScope clusterScope, Hypervisor.HypervisorType hypervisorType) {
}
-}
+
+ private Map extractDetails(String url) {
+ Map details = new HashMap<>();
+
+ // Step 1: Remove protocol
+ StringTokenizer protocolTokenizer = new StringTokenizer(url, "://");
+ String remainder = null;
+ while (protocolTokenizer.hasMoreTokens()) {
+ remainder = protocolTokenizer.nextToken(); // Last token is the remainder
+ }
+ if (remainder == null) {
+ throw new IllegalArgumentException("URL format is incorrect: " + url);
+ }
+
+ // Step 2: Split remainder into path and parameters
+ String pathAndParams = remainder;
+ String pathPart;
+ String paramsPart = "";
+ int semicolonIndex = pathAndParams.indexOf(';');
+ if (semicolonIndex != -1) {
+ pathPart = pathAndParams.substring(0, semicolonIndex);
+ paramsPart = pathAndParams.substring(semicolonIndex + 1);
+ } else {
+ pathPart = pathAndParams;
+ }
+
+ // Step 3: Extract IP from pathPart
+ StringTokenizer ipTokenizer = new StringTokenizer(pathPart, "/");
+ String ip = null;
+ if (ipTokenizer.hasMoreTokens()) {
+ ip = ipTokenizer.nextToken();
+ details.put("managementLIF", ip);
+ }
+
+ // Step 4: Extract parameters
+ if (!paramsPart.isEmpty()) {
+ StringTokenizer paramTokenizer = new StringTokenizer(paramsPart, ";");
+ while (paramTokenizer.hasMoreTokens()) {
+ String param = paramTokenizer.nextToken();
+ int eqIndex = param.indexOf('=');
+ if (eqIndex != -1) {
+ String key = param.substring(0, eqIndex);
+ String value = param.substring(eqIndex + 1);
+ details.put(key, value);
+ }
+ }
+ }
+
+ return details;
+ }
+}
\ No newline at end of file
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapHostListener.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapHostListener.java
new file mode 100644
index 000000000000..96f99f92f86a
--- /dev/null
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/provider/OntapHostListener.java
@@ -0,0 +1,36 @@
+package org.apache.cloudstack.storage.provider;
+
+import com.cloud.exception.StorageConflictException;
+import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
+
+public class OntapHostListener implements HypervisorHostListener {
+ @Override
+ public boolean hostAdded(long hostId) {
+ return false;
+ }
+
+ @Override
+ public boolean hostConnect(long hostId, long poolId) throws StorageConflictException {
+ return false;
+ }
+
+ @Override
+ public boolean hostDisconnected(long hostId, long poolId) {
+ return false;
+ }
+
+ @Override
+ public boolean hostAboutToBeRemoved(long hostId) {
+ return false;
+ }
+
+ @Override
+ public boolean hostRemoved(long hostId, long clusterId) {
+ return false;
+ }
+
+ @Override
+ public boolean hostEnabled(long hostId) {
+ return false;
+ }
+}
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..b685488851f2 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
@@ -27,20 +27,18 @@
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider;
import org.apache.cloudstack.storage.driver.OntapPrimaryDatastoreDriver;
import org.apache.cloudstack.storage.lifecycle.OntapPrimaryDatastoreLifecycle;
+import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.core.Logger;
-import org.springframework.stereotype.Component;
-
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-@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;
+ private HypervisorHostListener listener;
public OntapPrimaryDatastoreProvider() {
s_logger.info("OntapPrimaryDatastoreProvider initialized");
@@ -57,7 +55,7 @@ public DataStoreDriver getDataStoreDriver() {
@Override
public HypervisorHostListener getHostListener() {
- return null;
+ return listener;
}
@Override
@@ -71,6 +69,7 @@ public boolean configure(Map params) {
s_logger.trace("OntapPrimaryDatastoreProvider: configure: Called");
primaryDatastoreDriver = ComponentContext.inject(OntapPrimaryDatastoreDriver.class);
primaryDatastoreLifecycle = ComponentContext.inject(OntapPrimaryDatastoreLifecycle.class);
+ listener = ComponentContext.inject(OntapHostListener.class);
return true;
}
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 1bc6f51798ba..0644b2f49a24 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
@@ -24,42 +24,44 @@
import org.apache.cloudstack.storage.service.StorageStrategy;
import org.apache.cloudstack.storage.service.UnifiedNASStrategy;
import org.apache.cloudstack.storage.service.UnifiedSANStrategy;
-import org.apache.cloudstack.storage.utils.Constants;
import org.apache.cloudstack.storage.utils.Constants.ProtocolType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.springframework.stereotype.Component;
+import com.cloud.utils.component.ComponentContext;
-@Component
public class StorageProviderFactory {
- private final StorageStrategy storageStrategy;
- private static final Logger s_logger = (Logger) LogManager.getLogger(StorageProviderFactory.class);
- private StorageProviderFactory(OntapStorage ontapStorage) {
- ProtocolType protocol = ontapStorage.getProtocol();
- s_logger.info("Initializing StorageProviderFactory with protocol: " + protocol);
+ private final UnifiedNASStrategy nasStrategy;
+ private final UnifiedSANStrategy sanStrategy;
+ private static final Logger s_logger = LogManager.getLogger(StorageProviderFactory.class);
+
+ public StorageProviderFactory() {
+ // Initialize strategies using ComponentContext.inject
+ this.nasStrategy = ComponentContext.inject(UnifiedNASStrategy.class);
+ this.sanStrategy = ComponentContext.inject(UnifiedSANStrategy.class);
+ }
+
+
+ public StorageStrategy getStrategy(OntapStorage ontapStorage) {
+ ProtocolType protocol = ontapStorage.getProtocolType();
+ s_logger.info("Initializing StorageProviderFactory with protocol: {}", protocol);
switch (protocol) {
case NFS:
if(!ontapStorage.getIsDisaggregated()) {
- this.storageStrategy = new UnifiedNASStrategy(ontapStorage);
+ nasStrategy.setStorage(ontapStorage);
+ return nasStrategy;
} else {
throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported.");
}
- break;
case ISCSI:
if (!ontapStorage.getIsDisaggregated()) {
- this.storageStrategy = new UnifiedSANStrategy(ontapStorage);
+ sanStrategy.setStorage(ontapStorage);
+ return sanStrategy;
} else {
throw new CloudRuntimeException("Unsupported configuration: Disaggregated ONTAP is not supported.");
}
- break;
default:
- this.storageStrategy = null;
- throw new CloudRuntimeException("Unsupported protocol: " + protocol);
+ throw new CloudRuntimeException("Unsupported configuration: " + 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/NASStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java
old mode 100644
new mode 100755
index 4e03daae4b4a..147afe03c220
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/NASStrategy.java
@@ -20,15 +20,22 @@
package org.apache.cloudstack.storage.service;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
+import org.apache.cloudstack.storage.feign.model.ExportPolicy;
public abstract class NASStrategy extends StorageStrategy {
+ // Default constructor for Spring
+ public NASStrategy() {
+ super();
+ }
public NASStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
}
- public abstract String createExportPolicy(String svmName, String policyName);
+ public abstract ExportPolicy getExportPolicy(String svmName, String policyName);
+ public abstract ExportPolicy createExportPolicy(String svmName, String policyName);
+ public abstract void deleteExportPolicy(String svmName, String policyName);
public abstract String addExportRule(String policyName, String clientMatch, String[] protocols, String[] roRule, String[] rwRule);
public abstract String assignExportPolicyToVolume(String volumeUuid, String policyName);
- public abstract String enableNFS(String svmUuid);
+
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java
index 4e6846ef7610..538bb29ecf70 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/SANStrategy.java
@@ -22,6 +22,10 @@
import org.apache.cloudstack.storage.feign.model.OntapStorage;
public abstract class SANStrategy extends StorageStrategy {
+ // Default constructor for Spring
+ public SANStrategy() {
+ super();
+ }
public SANStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
}
diff --git a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/StorageStrategy.java
index c608f039b381..89b35092e320 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
@@ -1,26 +1,8 @@
-/*
- * 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.service;
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;
@@ -36,45 +18,68 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import javax.inject.Inject;
-import java.util.Map;
+import com.cloud.utils.component.ComponentContext;
import java.net.URI;
import java.util.List;
-import java.util.Objects;
public abstract class StorageStrategy {
- @Inject
private Utility utils;
-
- @Inject
+ // Replace @Inject Feign clients with FeignClientFactory
+ private FeignClientFactory feignClientFactory;
private VolumeFeignClient volumeFeignClient;
-
- @Inject
private SvmFeignClient svmFeignClient;
-
- @Inject
private JobFeignClient jobFeignClient;
- private final OntapStorage storage;
+ protected OntapStorage storage;
private List aggregates;
- private static final Logger s_logger = (Logger) LogManager.getLogger(StorageStrategy.class);
+ private static final Logger s_logger = LogManager.getLogger(StorageStrategy.class);
+
+ // Default constructor for Spring
+ public StorageStrategy() {
+ initializeDependencies();
+ }
public StorageStrategy(OntapStorage ontapStorage) {
storage = ontapStorage;
+ initializeDependencies();
+ }
+
+ private void initializeDependencies() {
+ utils = ComponentContext.inject(Utility.class);
+ // Initialize FeignClientFactory and create clients
+ this.feignClientFactory = new FeignClientFactory();
+ this.volumeFeignClient = feignClientFactory.createClient(VolumeFeignClient.class);
+ this.svmFeignClient = feignClientFactory.createClient(SvmFeignClient.class);
+ this.jobFeignClient = feignClientFactory.createClient(JobFeignClient.class);
+
+ }
+
+ public void setStorage(OntapStorage ontapStorage) {
+ this.storage = ontapStorage;
}
// Connect method to validate ONTAP cluster, credentials, protocol, and SVM
public boolean connect() {
+ s_logger.info(" storage object is {} ", storage.getIsDisaggregated());
+ s_logger.info(" storage object is {} ", storage.getManagementLIF());
+ s_logger.info(" storage object is {} ", storage.getProtocolType());
+ s_logger.info(" storage object is {} ", storage.getSvmName());
+ s_logger.info(" storage object is {} ", storage.getPassword());
+ s_logger.info(" storage object is {} ", storage.getUsername());
+
s_logger.info("Attempting to connect to ONTAP cluster at " + storage.getManagementLIF());
+
+
+ s_logger.info(" util is null or not {} ", utils);
//Get AuthHeader
String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
String svmName = storage.getSvmName();
try {
// Call the SVM API to check if the SVM exists
Svm svm = new Svm();
- URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GET_SVMs + "?name=" + svmName);
+ URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GET_SVMs + "?name=" + svmName + "&fields=aggregates");
OntapResponse svms = svmFeignClient.getSvmResponse(url, authHeader);
if (svms != null && svms.getRecords() != null && !svms.getRecords().isEmpty()) {
svm = svms.getRecords().get(0);
@@ -83,17 +88,17 @@ public boolean connect() {
}
// Validations
- if (!Objects.equals(svm.getState(), Constants.RUNNING)) {
- s_logger.error("SVM " + svmName + " is not in running state.");
- throw new CloudRuntimeException("SVM " + svmName + " is not in running state.");
- }
- if (Objects.equals(storage.getProtocol(), Constants.NFS) && !svm.getNfsEnabled()) {
- s_logger.error("NFS protocol is not enabled on SVM " + svmName);
- throw new CloudRuntimeException("NFS protocol is not enabled on SVM " + svmName);
- } else if (Objects.equals(storage.getProtocol(), Constants.ISCSI) && !svm.getIscsiEnabled()) {
- s_logger.error("iSCSI protocol is not enabled on SVM " + svmName);
- throw new CloudRuntimeException("iSCSI protocol is not enabled on SVM " + svmName);
- }
+// if (!Objects.equals(svm.getState(), Constants.RUNNING)) {
+// s_logger.error("SVM " + svmName + " is not in running state.");
+// throw new CloudRuntimeException("SVM " + svmName + " is not in running state.");
+// }
+// if (Objects.equals(storage.getProtocolType(), Constants.NFS) && !svm.getNfsEnabled()) {
+// s_logger.error("NFS protocol is not enabled on SVM " + svmName);
+// throw new CloudRuntimeException("NFS protocol is not enabled on SVM " + svmName);
+// } else if (Objects.equals(storage.getProtocolType(), Constants.ISCSI) && !svm.getIscsiEnabled()) {
+// s_logger.error("iSCSI protocol is not enabled on SVM " + svmName);
+// throw new CloudRuntimeException("iSCSI protocol is not enabled on SVM " + svmName);
+// }
List aggrs = svm.getAggregates();
if (aggrs == null || aggrs.isEmpty()) {
s_logger.error("No aggregates are assigned to SVM " + svmName);
@@ -102,7 +107,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());
}
return true;
}
@@ -131,20 +136,20 @@ public void createVolume(String volumeName, Long size) {
// Make the POST API call to create the volume
try {
// Create URI for POST CreateVolume API
- URI url = utils.generateURI(Constants.CREATE_VOLUME);
+ URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.CREATE_VOLUME);
// Call the VolumeFeignClient to create the volume
- JobResponse jobResponse = volumeFeignClient.createVolumeWithJob(url, authHeader, volumeRequest);
+ JobResponse jobResponse = volumeFeignClient.createVolumeWithJob( authHeader, volumeRequest);
if (jobResponse == null || jobResponse.getJob() == null) {
throw new CloudRuntimeException("Failed to initiate volume creation for " + volumeName);
}
String jobUUID = jobResponse.getJob().getUuid();
//Create URI for GET Job API
- url = utils.generateURI(Constants.GET_JOB_BY_UUID);
+ url = URI.create(Constants.HTTPS + storage.getManagementLIF() + Constants.GET_JOB_BY_UUID);
int jobRetryCount = 0;
Job createVolumeJob = null;
- while(createVolumeJob == null || !createVolumeJob.getState().equals(Constants.JOB_SUCCESS)) {
- if(jobRetryCount >= Constants.JOB_MAX_RETRIES) {
+ while (createVolumeJob == null || !createVolumeJob.getState().equals(Constants.JOB_SUCCESS)) {
+ if (jobRetryCount >= Constants.JOB_MAX_RETRIES) {
s_logger.error("Job to create volume " + volumeName + " did not complete within expected time.");
throw new CloudRuntimeException("Job to create volume " + volumeName + " did not complete within expected time.");
}
@@ -169,4 +174,4 @@ public void createVolume(String volumeName, Long size) {
}
s_logger.info("Volume created successfully: " + volumeName);
}
-}
+}
\ No newline at end of file
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 6c9a8735c4c1..d21359b0e2fb 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,16 +19,143 @@
package org.apache.cloudstack.storage.service;
+import com.cloud.utils.exception.CloudRuntimeException;
+import feign.FeignException;
+import org.apache.cloudstack.storage.feign.FeignClientFactory;
+import org.apache.cloudstack.storage.feign.client.NASFeignClient;
+import org.apache.cloudstack.storage.feign.client.VolumeFeignClient;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
+import org.apache.cloudstack.storage.feign.model.ExportPolicy;
+import org.apache.cloudstack.storage.feign.model.Svm;
+import org.apache.cloudstack.storage.feign.model.Volume;
+import org.apache.cloudstack.storage.feign.model.Nas;
+import org.apache.cloudstack.storage.feign.model.FileInfo;
+import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
+import org.apache.cloudstack.storage.utils.Constants;
+import org.apache.cloudstack.storage.utils.Utility;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.cloud.utils.component.ComponentContext;
+import java.net.URI;
public class UnifiedNASStrategy extends NASStrategy{
+
+ private Utility utils;
+// // Add missing Feign client setup for NAS operations
+ private FeignClientFactory feignClientFactory;
+ private NASFeignClient nasFeignClient;
+ private VolumeFeignClient volumeFeignClient;
+
+ private static final Logger s_logger = LogManager.getLogger(NASStrategy.class);
+
+ public UnifiedNASStrategy() {
+ super();
+ initializeDependencies();
+ }
+
public UnifiedNASStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
+ initializeDependencies();
+ }
+
+ private void initializeDependencies() {
+ utils = ComponentContext.inject(Utility.class);
+ // Initialize FeignClientFactory and create NAS client
+ feignClientFactory = new FeignClientFactory();
+ nasFeignClient = feignClientFactory.createClient(NASFeignClient.class);
+ this.volumeFeignClient = feignClientFactory.createClient(VolumeFeignClient.class);
}
@Override
- public String createExportPolicy(String svmName, String policyName) {
- return "";
+ public ExportPolicy getExportPolicy(String svmName, String policyName) {
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+ URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/protocols/nfs/export-policies?name=" + policyName + "&svm.name=" + svmName);
+
+ OntapResponse response = nasFeignClient.getExportPolicyResponse(url, authHeader);
+ if(response == null || response.getRecords() == null || response.getRecords().isEmpty()) {
+ s_logger.error("Error checking export policy existence");
+ throw new CloudRuntimeException("Failed to retrieve export policy");
+ }
+ return response.getRecords().get(0);
+
+ } catch (Exception e) {
+ s_logger.error("Error checking export policy existence: {}", e.getMessage());
+ throw new CloudRuntimeException("Failed to get export policy: " + policyName);
+ }
+ }
+
+ @Override
+ public ExportPolicy createExportPolicy(String svmName, String policyName) {
+ s_logger.info("Creating export policy: {} for SVM: {}", policyName, svmName);
+
+ try {
+ // Get AuthHeader
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ // Create ExportPolicy object
+ ExportPolicy exportPolicy = new ExportPolicy();
+ exportPolicy.setName(policyName);
+
+ // Set SVM
+ Svm svm = new Svm();
+ svm.setName(svmName);
+ exportPolicy.setSvm(svm);
+
+ // Create URI for export policy creation
+ URI url = URI.create(Constants.HTTPS + storage.getManagementLIF() + "/api/protocols/nfs/export-policies");
+
+ // Create export policy
+ ExportPolicy createdPolicy = nasFeignClient.createExportPolicy(url, authHeader, true, exportPolicy);
+
+ if (createdPolicy != null && createdPolicy.getId() != null) {
+ s_logger.info("Export policy created successfully with ID: {}", createdPolicy.getId());
+ return createdPolicy;
+ } else {
+ throw new CloudRuntimeException("Failed to create export policy: " + policyName);
+ }
+
+ } catch (FeignException e) {
+ s_logger.error("Failed to create export policy: {}", policyName, e);
+ throw new CloudRuntimeException("Failed to create export policy: " + e.getMessage());
+ } catch (Exception e) {
+ s_logger.error("Exception while creating export policy: {}", policyName, e);
+ throw new CloudRuntimeException("Failed to create export policy: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void deleteExportPolicy(String svmName, String policyName) {
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ // Get policy ID first
+ URI getUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/protocols/nfs/export-policies?name=" + policyName + "&svm.name=" + svmName);
+
+ OntapResponse policiesResponse = nasFeignClient.getExportPolicyResponse(getUrl, authHeader);
+
+ if (policiesResponse.getRecords() == null || policiesResponse.getRecords().isEmpty()) {
+ s_logger.warn("Export policy not found for deletion: {}", policyName);
+ throw new CloudRuntimeException("Export policy not found : " + policyName);
+ }
+
+ String policyId = policiesResponse.getRecords().get(0).getId().toString();
+
+ // Delete the policy
+ URI deleteUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/protocols/nfs/export-policies/" + policyId);
+
+ nasFeignClient.deleteExportPolicyById(deleteUrl, authHeader, policyId);
+
+ s_logger.info("Export policy deleted successfully: {}", policyName);
+
+ } catch (Exception e) {
+ s_logger.error("Failed to delete export policy: {}", policyName, e);
+ throw new CloudRuntimeException("Failed to delete export policy: " + policyName);
+ }
}
@Override
@@ -38,11 +165,186 @@ public String addExportRule(String policyName, String clientMatch, String[] prot
@Override
public String assignExportPolicyToVolume(String volumeUuid, String policyName) {
- return "";
+ s_logger.info("Assigning export policy: {} to volume: {}", policyName, volumeUuid);
+
+ try {
+ // Get AuthHeader
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ // First, get the export policy by name
+ URI getPolicyUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/protocols/nfs/export-policies?name=" + policyName + "&svm.name=" + storage.getSvmName());
+
+ OntapResponse policiesResponse = nasFeignClient.getExportPolicyResponse(getPolicyUrl, authHeader);
+
+ if (policiesResponse.getRecords() == null || policiesResponse.getRecords().isEmpty()) {
+ throw new CloudRuntimeException("Export policy not found: " + policyName);
+ }
+
+ ExportPolicy exportPolicy = policiesResponse.getRecords().get(0);
+
+ // Create Volume update object with NAS configuration
+ Volume volumeUpdate = new Volume();
+ Nas nas = new Nas();
+ nas.setExportPolicy(exportPolicy);
+ volumeUpdate.setNas(nas);
+
+ // Update the volume
+ URI updateVolumeUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/storage/volumes/" + volumeUuid);
+
+ //volumeFeignClient.updateVolumeRebalancing(updateVolumeUrl, authHeader, volumeUuid, volumeUpdate);
+
+ s_logger.info("Export policy successfully assigned to volume: {}", volumeUuid);
+ return "Export policy " + policyName + " assigned to volume " + volumeUuid;
+
+ } catch (FeignException e) {
+ s_logger.error("Failed to assign export policy to volume: {}", volumeUuid, e);
+ throw new CloudRuntimeException("Failed to assign export policy: " + e.getMessage());
+ } catch (Exception e) {
+ s_logger.error("Exception while assigning export policy to volume: {}", volumeUuid, e);
+ throw new CloudRuntimeException("Failed to assign export policy: " + e.getMessage());
+ }
}
- @Override
- public String enableNFS(String svmUuid) {
- return "";
+ // TODO should we return boolean or string ?
+ private boolean createFile(String volumeUuid, String filePath, Long fileSize) {
+ s_logger.info("Creating file: {} in volume: {}", filePath, volumeUuid);
+
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ FileInfo fileInfo = new FileInfo();
+ fileInfo.setPath(filePath);
+ fileInfo.setType(FileInfo.TypeEnum.FILE);
+
+ if (fileSize != null && fileSize > 0) {
+ fileInfo.setSize(fileSize);
+ }
+
+ URI createFileUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/storage/volumes/" + volumeUuid + "/files" + filePath);
+
+ nasFeignClient.createFile(createFileUrl, authHeader, volumeUuid, filePath, fileInfo);
+
+ s_logger.info("File created successfully: {} in volume: {}", filePath, volumeUuid);
+ return true;
+
+ } catch (FeignException e) {
+ s_logger.error("Failed to create file: {} in volume: {}", filePath, volumeUuid, e);
+ return false;
+ } catch (Exception e) {
+ s_logger.error("Exception while creating file: {} in volume: {}", filePath, volumeUuid, e);
+ return false;
+ }
}
-}
+
+ private boolean deleteFile(String volumeUuid, String filePath) {
+ s_logger.info("Deleting file: {} from volume: {}", filePath, volumeUuid);
+
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ // Check if file exists first
+ if (!fileExists(volumeUuid, filePath)) {
+ s_logger.warn("File does not exist: {} in volume: {}", filePath, volumeUuid);
+ return false;
+ }
+
+ URI deleteFileUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/storage/volumes/" + volumeUuid + "/files" + filePath);
+
+ nasFeignClient.deleteFile(deleteFileUrl, authHeader, volumeUuid, filePath);
+
+ s_logger.info("File deleted successfully: {} from volume: {}", filePath, volumeUuid);
+ return true;
+
+ } catch (FeignException e) {
+ s_logger.error("Failed to delete file: {} from volume: {}", filePath, volumeUuid, e);
+ return false;
+ } catch (Exception e) {
+ s_logger.error("Exception while deleting file: {} from volume: {}", filePath, volumeUuid, e);
+ return false;
+ }
+ }
+
+ private boolean fileExists(String volumeUuid, String filePath) {
+ s_logger.debug("Checking if file exists: {} in volume: {}", filePath, volumeUuid);
+
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ // Build URI for file info retrieval - volume-specific endpoint
+ URI getFileUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/storage/volumes/" + volumeUuid + "/files" + filePath);
+
+ nasFeignClient.getFileResponse(getFileUrl, authHeader, volumeUuid, filePath);
+
+ s_logger.debug("File exists: {} in volume: {}", filePath, volumeUuid);
+ return true;
+
+ } catch (FeignException e) {
+ // TODO check the status code while testing for file not found error
+ if (e.status() == 404) {
+ s_logger.debug("File does not exist: {} in volume: {}", filePath, volumeUuid);
+ return false;
+ }
+ s_logger.error("Error checking file existence: {} in volume: {}", filePath, volumeUuid, e);
+ return false;
+ } catch (Exception e) {
+ s_logger.error("Exception while checking file existence: {} in volume: {}", filePath, volumeUuid, e);
+ return false;
+ }
+ }
+
+ private OntapResponse getFileInfo(String volumeUuid, String filePath) {
+ s_logger.debug("Getting file info for: {} in volume: {}", filePath, volumeUuid);
+
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ URI getFileUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/storage/volumes/" + volumeUuid + "/files" + filePath);
+
+ OntapResponse response = nasFeignClient.getFileResponse(getFileUrl, authHeader, volumeUuid, filePath);
+
+ s_logger.debug("Retrieved file info for: {} in volume: {}", filePath, volumeUuid);
+ return response;
+
+ } catch (FeignException e) {
+ if (e.status() == 404) {
+ s_logger.debug("File not found: {} in volume: {}", filePath, volumeUuid);
+ return null;
+ }
+ s_logger.error("Failed to get file info: {} in volume: {}", filePath, volumeUuid, e);
+ throw new CloudRuntimeException("Failed to get file info: " + e.getMessage());
+ } catch (Exception e) {
+ s_logger.error("Exception while getting file info: {} in volume: {}", filePath, volumeUuid, e);
+ throw new CloudRuntimeException("Failed to get file info: " + e.getMessage());
+ }
+ }
+
+ private boolean updateFile(String volumeUuid, String filePath, FileInfo fileInfo) {
+ s_logger.info("Updating file: {} in volume: {}", filePath, volumeUuid);
+
+ try {
+ String authHeader = utils.generateAuthHeader(storage.getUsername(), storage.getPassword());
+
+ URI updateFileUrl = URI.create(Constants.HTTPS + storage.getManagementLIF() +
+ "/api/storage/volumes/" + volumeUuid + "/files" + filePath);
+
+ nasFeignClient.updateFile(updateFileUrl, authHeader, volumeUuid, filePath, fileInfo);
+
+ s_logger.info("File updated successfully: {} in volume: {}", filePath, volumeUuid);
+ return true;
+
+ } catch (FeignException e) {
+ s_logger.error("Failed to update file: {} in volume: {}", filePath, volumeUuid, e);
+ return false;
+ } catch (Exception e) {
+ s_logger.error("Exception while updating file: {} in volume: {}", filePath, volumeUuid, e);
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
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 e954ec312006..838753c3e757 100644
--- a/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java
+++ b/plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/service/UnifiedSANStrategy.java
@@ -19,13 +19,36 @@
package org.apache.cloudstack.storage.service;
+import com.cloud.utils.component.ComponentContext;
+import org.apache.cloudstack.storage.feign.FeignClientFactory;
+import org.apache.cloudstack.storage.feign.client.SANFeignClient;
import org.apache.cloudstack.storage.feign.model.OntapStorage;
+import org.apache.cloudstack.storage.utils.Utility;
public class UnifiedSANStrategy extends SANStrategy{
+
+ private Utility utils;
+ // Add missing Feign client setup for NAS operations
+ private FeignClientFactory feignClientFactory;
+ private SANFeignClient sanFeignClient;
+
+
+ public UnifiedSANStrategy() {
+ super();
+ initializeDependencies();
+ }
+
public UnifiedSANStrategy(OntapStorage ontapStorage) {
super(ontapStorage);
}
+ private void initializeDependencies() {
+ utils = ComponentContext.inject(Utility.class);
+ // Initialize FeignClientFactory and create NAS client
+ feignClientFactory = new FeignClientFactory();
+ sanFeignClient = feignClientFactory.createClient(SANFeignClient.class);// TODO needs to be changed
+ }
+
@Override
public String createLUN(String svmName, String volumeName, String lunName, long sizeBytes, String osType) {
return "";
@@ -46,3 +69,4 @@ public String enableISCSI(String svmUuid) {
return "";
}
}
+
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 4fe55eb2e1fd..a4f575d618b5 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
@@ -21,8 +21,18 @@
public class Constants {
public enum ProtocolType {
- NFS,
- ISCSI
+ NFS("nfs"),
+ ISCSI("iscsi");
+
+ private final String value;
+
+ ProtocolType(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
}
public static final String NFS = "nfs";
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 6fcf155e27b5..c14a21261188 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
@@ -20,17 +20,9 @@
package org.apache.cloudstack.storage.utils;
import com.cloud.utils.StringUtils;
-import org.apache.cloudstack.storage.feign.model.OntapStorage;
-import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
-import javax.inject.Inject;
-import java.net.URI;
-
-@Component
public class Utility {
- @Inject
- OntapStorage ontapStorage;
private static final String BASIC = "Basic";
private static final String AUTH_HEADER_COLON = ":";
@@ -45,8 +37,4 @@ public String generateAuthHeader(String username, String password) {
return BASIC + StringUtils.SPACE + new String(encodedBytes);
}
- public URI generateURI(String path) {
- String uriString = Constants.HTTPS + ontapStorage.getManagementLIF() + path;
- return URI.create(uriString);
- }
}