Skip to content

Commit 1b0c7f7

Browse files
sandeeplocharlaLocharla, Sandeep
andauthored
Feature/cstackex-01: Primary Storage pool creation
* CSTACKEX-1: Feign changes and fixes for getting storage pool creation to work * CSTACKEX-01: Create Primary Storage pool changes with working code * CSTACKEX-01: Addressed all review comments and updated some code * CSTACKEX-01: Made some changes to fix some errors seen during testing * CSTACKEX-01: Addressed additional comments --------- Co-authored-by: Locharla, Sandeep <[email protected]>
1 parent 6c4b24e commit 1b0c7f7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+624
-454
lines changed

plugins/storage/volume/ontap/pom.xml

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
<spring-cloud.version>2021.0.7</spring-cloud.version>
3232
<openfeign.version>11.0</openfeign.version>
3333
<json.version>20230227</json.version>
34+
<jackson-databind.version>2.15.2</jackson-databind.version>
35+
<httpclient.version>4.5.14</httpclient.version>
3436
<swagger-annotations.version>1.6.2</swagger-annotations.version>
3537
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
3638
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
@@ -58,24 +60,30 @@
5860
<version>${json.version}</version>
5961
</dependency>
6062
<dependency>
61-
<groupId>org.springframework.cloud</groupId>
62-
<artifactId>spring-cloud-commons</artifactId>
63+
<groupId>io.github.openfeign</groupId>
64+
<artifactId>feign-core</artifactId>
65+
<version>${openfeign.version}</version>
6366
</dependency>
6467
<dependency>
65-
<groupId>org.springframework.cloud</groupId>
66-
<artifactId>spring-cloud-starter-openfeign</artifactId>
67-
<exclusions>
68-
<exclusion>
69-
<groupId>org.springframework.security</groupId>
70-
<artifactId>spring-security-crypto</artifactId>
71-
</exclusion>
72-
</exclusions>
68+
<groupId>io.github.openfeign</groupId>
69+
<artifactId>feign-httpclient</artifactId>
70+
<version>${openfeign.version}</version>
7371
</dependency>
7472
<dependency>
7573
<groupId>io.github.openfeign</groupId>
76-
<artifactId>feign-httpclient</artifactId>
74+
<artifactId>feign-jackson</artifactId>
7775
<version>${openfeign.version}</version>
7876
</dependency>
77+
<dependency>
78+
<groupId>com.fasterxml.jackson.core</groupId>
79+
<artifactId>jackson-databind</artifactId>
80+
<version>${jackson-databind.version}</version>
81+
</dependency>
82+
<dependency>
83+
<groupId>org.apache.httpcomponents</groupId>
84+
<artifactId>httpclient</artifactId>
85+
<version>${httpclient.version}</version>
86+
</dependency>
7987
<dependency>
8088
<groupId>org.apache.cloudstack</groupId>
8189
<artifactId>cloud-engine-storage-volume</artifactId>

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/driver/OntapPrimaryDatastoreDriver.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
4545
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
4646
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
47+
import org.apache.cloudstack.storage.feign.model.OntapStorage;
48+
import org.apache.cloudstack.storage.provider.StorageProviderFactory;
4749
import org.apache.cloudstack.storage.service.StorageStrategy;
4850
import org.apache.cloudstack.storage.service.model.CloudStackVolume;
4951
import org.apache.cloudstack.storage.service.model.ProtocolType;
@@ -60,7 +62,6 @@ public class OntapPrimaryDatastoreDriver implements PrimaryDataStoreDriver {
6062

6163
private static final Logger s_logger = LogManager.getLogger(OntapPrimaryDatastoreDriver.class);
6264

63-
@Inject private Utility utils;
6465
@Inject private StoragePoolDetailsDao storagePoolDetailsDao;
6566
@Inject private PrimaryDataStoreDao storagePoolDao;
6667
@Override
@@ -126,9 +127,9 @@ private String createCloudStackVolumeForTypeVolume(DataStore dataStore, DataObje
126127
throw new CloudRuntimeException("createCloudStackVolume : Storage Pool not found for id: " + dataStore.getId());
127128
}
128129
Map<String, String> details = storagePoolDetailsDao.listDetailsKeyPairs(dataStore.getId());
129-
StorageStrategy storageStrategy = utils.getStrategyByStoragePoolDetails(details);
130+
StorageStrategy storageStrategy = getStrategyByStoragePoolDetails(details);
130131
s_logger.info("createCloudStackVolumeForTypeVolume: Connection to Ontap SVM [{}] successful, preparing CloudStackVolumeRequest", details.get(Constants.SVM_NAME));
131-
CloudStackVolume cloudStackVolumeRequest = utils.createCloudStackVolumeRequestByProtocol(storagePool, details, dataObject);
132+
CloudStackVolume cloudStackVolumeRequest = Utility.createCloudStackVolumeRequestByProtocol(storagePool, details, dataObject);
132133
CloudStackVolume cloudStackVolume = storageStrategy.createCloudStackVolume(cloudStackVolumeRequest);
133134
if (ProtocolType.ISCSI.name().equalsIgnoreCase(details.get(Constants.PROTOCOL)) && cloudStackVolume.getLun() != null && cloudStackVolume.getLun().getName() != null) {
134135
return cloudStackVolume.getLun().getName();
@@ -268,4 +269,24 @@ public boolean isStorageSupportHA(Storage.StoragePoolType type) {
268269
public void detachVolumeFromAllStorageNodes(Volume volume) {
269270

270271
}
272+
273+
private StorageStrategy getStrategyByStoragePoolDetails(Map<String, String> details) {
274+
if (details == null || details.isEmpty()) {
275+
s_logger.error("getStrategyByStoragePoolDetails: Storage pool details are null or empty");
276+
throw new CloudRuntimeException("getStrategyByStoragePoolDetails: Storage pool details are null or empty");
277+
}
278+
String protocol = details.get(Constants.PROTOCOL);
279+
OntapStorage ontapStorage = new OntapStorage(details.get(Constants.USERNAME), details.get(Constants.PASSWORD),
280+
details.get(Constants.MANAGEMENT_LIF), details.get(Constants.SVM_NAME), ProtocolType.valueOf(protocol),
281+
Boolean.parseBoolean(details.get(Constants.IS_DISAGGREGATED)));
282+
StorageStrategy storageStrategy = StorageProviderFactory.getStrategy(ontapStorage);
283+
boolean isValid = storageStrategy.connect();
284+
if (isValid) {
285+
s_logger.info("Connection to Ontap SVM [{}] successful", details.get(Constants.SVM_NAME));
286+
return storageStrategy;
287+
} else {
288+
s_logger.error("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details.get(Constants.SVM_NAME) + "] failed");
289+
throw new CloudRuntimeException("getStrategyByStoragePoolDetails: Connection to Ontap SVM [" + details.get(Constants.SVM_NAME) + "] failed");
290+
}
291+
}
271292
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.cloudstack.storage.feign;
21+
22+
import feign.Feign;
23+
24+
public class FeignClientFactory {
25+
26+
private final FeignConfiguration feignConfiguration;
27+
28+
public FeignClientFactory() {
29+
this.feignConfiguration = new FeignConfiguration();
30+
}
31+
32+
public FeignClientFactory(FeignConfiguration feignConfiguration) {
33+
this.feignConfiguration = feignConfiguration;
34+
}
35+
36+
public <T> T createClient(Class<T> clientClass, String baseURL) {
37+
return Feign.builder()
38+
.client(feignConfiguration.createClient())
39+
.encoder(feignConfiguration.createEncoder())
40+
.decoder(feignConfiguration.createDecoder())
41+
.retryer(feignConfiguration.createRetryer())
42+
.requestInterceptor(feignConfiguration.createRequestInterceptor())
43+
.target(clientClass, baseURL);
44+
}
45+
}
Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,68 @@
1-
/*
2-
* Licensed to the Apache Software Foundation (ASF) under one
3-
* or more contributor license agreements. See the NOTICE file
4-
* distributed with this work for additional information
5-
* regarding copyright ownership. The ASF licenses this file
6-
* to you under the Apache License, Version 2.0 (the
7-
* "License"); you may not use this file except in compliance
8-
* with the License. You may obtain a copy of the License at
9-
*
10-
* http://www.apache.org/licenses/LICENSE-2.0
11-
*
12-
* Unless required by applicable law or agreed to in writing,
13-
* software distributed under the License is distributed on an
14-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15-
* KIND, either express or implied. See the License for the
16-
* specific language governing permissions and limitations
17-
* under the License.
18-
*/
19-
201
package org.apache.cloudstack.storage.feign;
212

22-
233
import feign.RequestInterceptor;
24-
import feign.RequestTemplate;
254
import feign.Retryer;
26-
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
5+
import feign.Client;
6+
import feign.httpclient.ApacheHttpClient;
7+
import feign.codec.Decoder;
8+
import feign.codec.Encoder;
9+
import feign.Response;
10+
import feign.codec.DecodeException;
11+
import feign.codec.EncodeException;
12+
import com.fasterxml.jackson.core.JsonProcessingException;
13+
import com.fasterxml.jackson.databind.DeserializationFeature;
14+
import com.fasterxml.jackson.databind.json.JsonMapper;
2715
import org.apache.http.conn.ConnectionKeepAliveStrategy;
2816
import org.apache.http.conn.ssl.NoopHostnameVerifier;
2917
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
3018
import org.apache.http.conn.ssl.TrustAllStrategy;
3119
import org.apache.http.impl.client.CloseableHttpClient;
20+
import org.apache.http.impl.client.HttpClientBuilder;
3221
import org.apache.http.ssl.SSLContexts;
3322
import org.apache.logging.log4j.LogManager;
3423
import org.apache.logging.log4j.Logger;
35-
import org.springframework.context.annotation.Bean;
36-
import org.springframework.context.annotation.Configuration;
37-
import feign.Client;
38-
import feign.httpclient.ApacheHttpClient;
24+
3925
import javax.net.ssl.SSLContext;
26+
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.lang.reflect.Type;
29+
import java.nio.charset.StandardCharsets;
4030
import java.util.concurrent.TimeUnit;
4131

42-
@Configuration
4332
public class FeignConfiguration {
44-
private static Logger logger = LogManager.getLogger(FeignConfiguration.class);
45-
46-
private int retryMaxAttempt = 3;
47-
48-
private int retryMaxInterval = 5;
33+
private static final Logger logger = LogManager.getLogger(FeignConfiguration.class);
4934

50-
private String ontapFeignMaxConnection = "80";
35+
private final int retryMaxAttempt = 3;
36+
private final int retryMaxInterval = 5;
37+
private final String ontapFeignMaxConnection = "80";
38+
private final String ontapFeignMaxConnectionPerRoute = "20";
39+
private final JsonMapper jsonMapper;
5140

52-
private String ontapFeignMaxConnectionPerRoute = "20";
53-
54-
@Bean
55-
public Client client(ApacheHttpClientFactory httpClientFactory) {
41+
public FeignConfiguration() {
42+
this.jsonMapper = JsonMapper.builder()
43+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
44+
.findAndAddModules()
45+
.build();
46+
}
5647

48+
public Client createClient() {
5749
int maxConn;
5850
int maxConnPerRoute;
5951
try {
6052
maxConn = Integer.parseInt(this.ontapFeignMaxConnection);
6153
} catch (Exception e) {
62-
logger.error("ontapFeignClient: encounter exception while parse the max connection from env. setting default value");
54+
logger.error("ontapFeignClient: parse max connection failed, using default");
6355
maxConn = 20;
6456
}
6557
try {
6658
maxConnPerRoute = Integer.parseInt(this.ontapFeignMaxConnectionPerRoute);
6759
} catch (Exception e) {
68-
logger.error("ontapFeignClient: encounter exception while parse the max connection per route from env. setting default value");
60+
logger.error("ontapFeignClient: parse max connection per route failed, using default");
6961
maxConnPerRoute = 2;
7062
}
71-
// Disable Keep Alive for Http Connection
72-
logger.debug("ontapFeignClient: Setting the feign client config values as max connection: {}, max connections per route: {}", maxConn, maxConnPerRoute);
63+
logger.debug("ontapFeignClient: maxConn={}, maxConnPerRoute={}", maxConn, maxConnPerRoute);
7364
ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> 0;
74-
CloseableHttpClient httpClient = httpClientFactory.createBuilder()
65+
CloseableHttpClient httpClient = HttpClientBuilder.create()
7566
.setMaxConnTotal(maxConn)
7667
.setMaxConnPerRoute(maxConnPerRoute)
7768
.setKeepAliveStrategy(keepAliveStrategy)
@@ -83,30 +74,64 @@ public Client client(ApacheHttpClientFactory httpClientFactory) {
8374

8475
private SSLConnectionSocketFactory getSSLSocketFactory() {
8576
try {
86-
// The TrustAllStrategy is a strategy used in SSL context configuration that accepts any certificate
8777
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustAllStrategy()).build();
8878
return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
8979
} catch (Exception ex) {
9080
throw new RuntimeException(ex);
9181
}
9282
}
9383

84+
public RequestInterceptor createRequestInterceptor() {
85+
return template -> {
86+
logger.info("Feign Request URL: {}", template.url());
87+
logger.info("HTTP Method: {}", template.method());
88+
logger.info("Headers: {}", template.headers());
89+
if (template.body() != null) {
90+
logger.info("Body: {}", new String(template.body(), StandardCharsets.UTF_8));
91+
}
92+
};
93+
}
9494

95-
@Bean
96-
public RequestInterceptor requestInterceptor() {
97-
return new RequestInterceptor() {
95+
public Retryer createRetryer() {
96+
return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt);
97+
}
98+
99+
public Encoder createEncoder() {
100+
return new Encoder() {
98101
@Override
99-
public void apply(RequestTemplate template) {
100-
logger.info("Feign Request URL: {}", template.url());
101-
logger.info("HTTP Method: {}", template.method());
102-
logger.info("Headers: {}", template.headers());
103-
logger.info("Body: {}", template.requestBody().asString());
102+
public void encode(Object object, Type bodyType, feign.RequestTemplate template) throws EncodeException {
103+
if (object == null) {
104+
template.body(null, StandardCharsets.UTF_8);
105+
return;
106+
}
107+
try {
108+
byte[] jsonBytes = jsonMapper.writeValueAsBytes(object);
109+
template.body(jsonBytes, StandardCharsets.UTF_8);
110+
template.header("Content-Type", "application/json");
111+
} catch (JsonProcessingException e) {
112+
throw new EncodeException("Error encoding object to JSON", e);
113+
}
104114
}
105115
};
106116
}
107117

108-
@Bean
109-
public Retryer feignRetryer() {
110-
return new Retryer.Default(1000L, retryMaxInterval * 1000L, retryMaxAttempt);
118+
public Decoder createDecoder() {
119+
return new Decoder() {
120+
@Override
121+
public Object decode(Response response, Type type) throws IOException, DecodeException {
122+
if (response.body() == null) {
123+
return null;
124+
}
125+
String json = null;
126+
try (InputStream bodyStream = response.body().asInputStream()) {
127+
json = new String(bodyStream.readAllBytes(), StandardCharsets.UTF_8);
128+
logger.debug("Decoding JSON response: {}", json);
129+
return jsonMapper.readValue(json, jsonMapper.getTypeFactory().constructType(type));
130+
} catch (IOException e) {
131+
logger.error("Error decoding JSON response. Status: {}, Raw body: {}", response.status(), json, e);
132+
throw new DecodeException(response.status(), "Error decoding JSON response", response.request(), e);
133+
}
134+
}
135+
};
111136
}
112137
}

plugins/storage/volume/ontap/src/main/java/org/apache/cloudstack/storage/feign/client/AggregateFeignClient.java

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,18 @@
2020
package org.apache.cloudstack.storage.feign.client;
2121

2222
import org.apache.cloudstack.storage.feign.model.Aggregate;
23-
import org.apache.cloudstack.storage.feign.FeignConfiguration;
2423
import org.apache.cloudstack.storage.feign.model.response.OntapResponse;
25-
import org.springframework.cloud.openfeign.FeignClient;
26-
import org.springframework.context.annotation.Lazy;
27-
import org.springframework.web.bind.annotation.PathVariable;
28-
import org.springframework.web.bind.annotation.RequestHeader;
29-
import org.springframework.web.bind.annotation.RequestMapping;
30-
import org.springframework.web.bind.annotation.RequestMethod;
24+
import feign.Headers;
25+
import feign.Param;
26+
import feign.RequestLine;
3127

32-
import java.net.URI;
33-
34-
@Lazy
35-
@FeignClient(name="AggregateClient", url="https://{clusterIP}/api/storage/aggregates", configuration = FeignConfiguration.class)
3628
public interface AggregateFeignClient {
3729

38-
//this method to get all aggregates and also filtered aggregates based on query params as a part of URL
39-
@RequestMapping(method=RequestMethod.GET)
40-
OntapResponse<Aggregate> getAggregateResponse(URI baseURL, @RequestHeader("Authorization") String header);
41-
42-
@RequestMapping(method=RequestMethod.GET, value="/{uuid}")
43-
Aggregate getAggregateByUUID(URI baseURL,@RequestHeader("Authorization") String header, @PathVariable(name = "uuid", required = true) String uuid);
30+
@RequestLine("GET /api/storage/aggregates")
31+
@Headers({"Authorization: {authHeader}"})
32+
OntapResponse<Aggregate> getAggregateResponse(@Param("authHeader") String authHeader);
4433

34+
@RequestLine("GET /api/storage/aggregates/{uuid}")
35+
@Headers({"Authorization: {authHeader}"})
36+
Aggregate getAggregateByUUID(@Param("authHeader") String authHeader, @Param("uuid") String uuid);
4537
}

0 commit comments

Comments
 (0)