Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions plugins/storage/volume/ontap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@
<artifactId>feign-httpclient</artifactId>
<version>${openfeign.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.github.openfeign</groupId>-->
<!-- <artifactId>feign-slf4j</artifactId>-->
<!-- <version>${openfeign.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${openfeign.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-engine-storage-volume</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> getCapabilities() {
s_logger.trace("OntapPrimaryDatastoreDriver: getCapabilities: Called");
Expand Down
Original file line number Diff line number Diff line change
@@ -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> T createClient(Class<T> 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/");
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

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

Hardcoded base URL in FeignClientFactory should be configurable to support different environments.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

lets not merge such changes, you can do this for internal testing.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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);
}
}
};
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Aggregate> 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 /")

Choose a reason for hiding this comment

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

add url

@Headers("Authorization: {authHeader}")
OntapResponse<Aggregate> 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading
Loading