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