Added support for HTTP Proxy 65/5265/3
authorPatrikBuhr <patrik.buhr@est.tech>
Mon, 7 Dec 2020 14:15:08 +0000 (15:15 +0100)
committerPatrikBuhr <patrik.buhr@est.tech>
Wed, 9 Dec 2020 10:25:40 +0000 (11:25 +0100)
If configured, HTTP proxy will be used for callbacks to the NearRT RIC.

Change-Id: Iaed4b2a61cf4f8037e7c4374b4945836104a640b
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
Issue-ID: NONRTRIC-317

12 files changed:
enrichment-coordinator-service/api/ecs-api.json
enrichment-coordinator-service/api/ecs-api.yaml
enrichment-coordinator-service/config/application.yaml
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/AsyncRestClient.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/clients/AsyncRestClientFactory.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/ApplicationConfig.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/configuration/WebClientConfig.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/consumer/ConsumerCallbacks.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/controllers/producer/ProducerCallbacks.java
enrichment-coordinator-service/src/main/java/org/oransc/enrichment/tasks/ProducerSupervision.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/ApplicationTest.java
enrichment-coordinator-service/src/test/java/org/oransc/enrichment/clients/AsyncRestClientTest.java

index a27be46..79ab5d3 100644 (file)
             "tags": ["A1-EI (enrichment information)"]
         }}
     },
-    "host": "localhost:38585",
     "definitions": {
         "producer_ei_job_request": {
             "description": "The body of the EI producer callbacks for EI job creation and deletion",
index e317541..ac49e5a 100644 (file)
@@ -4,7 +4,7 @@ info:
   description: This page lists all the rest apis for the service.
   version: "1.0"
 servers:
-- url: //localhost:38585/
+- url: /
 tags:
 - name: A1-EI (enrichment information)
   description: Consumer Controller
index 850dc67..5900d63 100644 (file)
@@ -9,9 +9,11 @@ management:
   endpoints:
     web:
       exposure:
+        # Enabling of springboot actuator features. See springboot documentation.
         include: "loggers,logfile,health,info,metrics,threaddump,heapdump"
 
 logging:
+  # Configuration of logging
   level:
     ROOT: ERROR
     org.springframework: ERROR
@@ -21,6 +23,8 @@ logging:
   file:
     name: /var/log/enrichment-coordinator-service/application.log
 server:
+   # Configuration of the HTTP/REST server. The parameters are defined and handeled by the springboot framework.
+   # See springboot documentation.
    port : 8434
    http-port: 8083
    ssl:
@@ -30,10 +34,16 @@ server:
       key-password: policy_agent
       key-alias: policy_agent
 app:
-  filepath: /opt/app/enrichment-coordinator-service/data/application_configuration.json
   webclient:
+    # Configuration of the trust store used for the HTTP client (outgoing requests)
+    # The file location and the password for the truststore is only relevant if trust-store-used == true
+    # Note that the same keystore as for the server is used.
     trust-store-used: false
     trust-store-password: policy_agent
     trust-store: /opt/app/enrichment-coordinator-service/etc/cert/truststore.jks
+    # Configuration of usage of HTTP Proxy for the southbound accesses.
+    # The HTTP proxy (if configured) will only be used for accessing NearRT RIC:s
+    http.proxy-host:
+    http.proxy-port: 0
   vardata-directory: /var/enrichment-coordinator-service
 
index 76da624..f0f6c4b 100644 (file)
@@ -28,6 +28,7 @@ import io.netty.handler.timeout.WriteTimeoutHandler;
 import java.lang.invoke.MethodHandles;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.MediaType;
@@ -42,6 +43,7 @@ import org.springframework.web.reactive.function.client.WebClientResponseExcepti
 import reactor.core.publisher.Mono;
 import reactor.netty.http.client.HttpClient;
 import reactor.netty.resources.ConnectionProvider;
+import reactor.netty.tcp.ProxyProvider.Proxy;
 import reactor.netty.tcp.TcpClient;
 
 /**
@@ -54,19 +56,12 @@ public class AsyncRestClient {
     private final String baseUrl;
     private static final AtomicInteger sequenceNumber = new AtomicInteger();
     private final SslContext sslContext;
+    private final HttpProxyConfig httpProxyConfig;
 
-    /**
-     * Note that only http (not https) will work when this constructor is used.
-     * 
-     * @param baseUrl
-     */
-    public AsyncRestClient(String baseUrl) {
-        this(baseUrl, null);
-    }
-
-    public AsyncRestClient(String baseUrl, SslContext sslContext) {
+    public AsyncRestClient(String baseUrl, @Nullable SslContext sslContext, @Nullable HttpProxyConfig httpProxyConfig) {
         this.baseUrl = baseUrl;
         this.sslContext = sslContext;
+        this.httpProxyConfig = httpProxyConfig;
     }
 
     public Mono<ResponseEntity<String>> postForEntity(String uri, @Nullable String body) {
@@ -188,7 +183,7 @@ public class AsyncRestClient {
             logger.debug("{} HTTP error status = '{}', body '{}'", traceTag, exception.getStatusCode(),
                 exception.getResponseBodyAsString());
         } else {
-            logger.debug("{} HTTP error", traceTag, t);
+            logger.debug("{} HTTP error {}", traceTag, t.getMessage());
         }
     }
 
@@ -200,27 +195,31 @@ public class AsyncRestClient {
         }
     }
 
-    private TcpClient createTcpClientSecure(SslContext sslContext) {
-        return TcpClient.create(ConnectionProvider.newConnection()) //
-            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
-            .secure(c -> c.sslContext(sslContext)) //
-            .doOnConnected(connection -> {
-                connection.addHandlerLast(new ReadTimeoutHandler(30));
-                connection.addHandlerLast(new WriteTimeoutHandler(30));
-            });
+    private boolean isHttpProxyConfigured() {
+        return httpProxyConfig != null && httpProxyConfig.httpProxyPort() > 0
+            && !httpProxyConfig.httpProxyHost().isEmpty();
     }
 
-    private TcpClient createTcpClientInsecure() {
-        return TcpClient.create(ConnectionProvider.newConnection()) //
+    private TcpClient createTcpClient() {
+        TcpClient client = TcpClient.create(ConnectionProvider.newConnection()) //
             .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
             .doOnConnected(connection -> {
                 connection.addHandlerLast(new ReadTimeoutHandler(30));
                 connection.addHandlerLast(new WriteTimeoutHandler(30));
             });
+        if (this.sslContext != null) {
+            client = client.secure(c -> c.sslContext(sslContext));
+        }
+        if (isHttpProxyConfigured()) {
+            client = client.proxy(proxy -> proxy.type(Proxy.HTTP).host(httpProxyConfig.httpProxyHost())
+                .port(httpProxyConfig.httpProxyPort()));
+        }
+        return client;
     }
 
     private WebClient createWebClient(String baseUrl, TcpClient tcpClient) {
         HttpClient httpClient = HttpClient.from(tcpClient);
+
         ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
         ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder() //
             .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)) //
@@ -235,13 +234,8 @@ public class AsyncRestClient {
     private Mono<WebClient> getWebClient() {
         if (this.webClient == null) {
             try {
-                if (this.sslContext != null) {
-                    TcpClient tcpClient = createTcpClientSecure(sslContext);
-                    this.webClient = createWebClient(this.baseUrl, tcpClient);
-                } else {
-                    TcpClient tcpClient = createTcpClientInsecure();
-                    this.webClient = createWebClient(this.baseUrl, tcpClient);
-                }
+                TcpClient tcpClient = createTcpClient();
+                this.webClient = createWebClient(this.baseUrl, tcpClient);
             } catch (Exception e) {
                 logger.error("Could not create WebClient {}", e.getMessage());
                 return Mono.error(e);
index 07f23e9..4865df5 100644 (file)
@@ -42,6 +42,7 @@ import java.util.stream.Collectors;
 import javax.net.ssl.KeyManagerFactory;
 
 import org.oransc.enrichment.configuration.WebClientConfig;
+import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.ResourceUtils;
@@ -53,25 +54,38 @@ public class AsyncRestClientFactory {
     private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     private final SslContextFactory sslContextFactory;
+    private final HttpProxyConfig httpProxyConfig;
 
     public AsyncRestClientFactory(WebClientConfig clientConfig) {
         if (clientConfig != null) {
             this.sslContextFactory = new CachingSslContextFactory(clientConfig);
+            this.httpProxyConfig = clientConfig.httpProxyConfig();
         } else {
+            logger.warn("No configuration for web client defined, HTTPS will not work");
             this.sslContextFactory = null;
+            this.httpProxyConfig = null;
         }
     }
 
-    public AsyncRestClient createRestClient(String baseUrl) {
+    public AsyncRestClient createRestClientNoHttpProxy(String baseUrl) {
+        return createRestClient(baseUrl, false);
+    }
+
+    public AsyncRestClient createRestClientUseHttpProxy(String baseUrl) {
+        return createRestClient(baseUrl, true);
+    }
+
+    private AsyncRestClient createRestClient(String baseUrl, boolean useHttpProxy) {
         if (this.sslContextFactory != null) {
             try {
-                return new AsyncRestClient(baseUrl, this.sslContextFactory.createSslContext());
+                return new AsyncRestClient(baseUrl, this.sslContextFactory.createSslContext(),
+                    useHttpProxy ? httpProxyConfig : null);
             } catch (Exception e) {
                 String exceptionString = e.toString();
                 logger.error("Could not init SSL context, reason: {}", exceptionString);
             }
         }
-        return new AsyncRestClient(baseUrl);
+        return new AsyncRestClient(baseUrl, null, httpProxyConfig);
     }
 
     private class SslContextFactory {
@@ -175,5 +189,4 @@ public class AsyncRestClientFactory {
 
         }
     }
-
 }
index 8937464..fce9e22 100644 (file)
 
 package org.oransc.enrichment.configuration;
 
-import javax.validation.constraints.NotEmpty;
-
 import lombok.Getter;
 
+import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -31,10 +30,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 @EnableConfigurationProperties
 @ConfigurationProperties()
 public class ApplicationConfig {
-    @NotEmpty
-    @Getter
-    @Value("${app.filepath}")
-    private String localConfigurationFilePath;
 
     @Getter
     @Value("${app.vardata-directory}")
@@ -61,7 +56,17 @@ public class ApplicationConfig {
     @Value("${app.webclient.trust-store}")
     private String sslTrustStore = "";
 
+    @Value("${app.webclient.http.proxy-host:\"\"}")
+    private String httpProxyHost = "";
+
+    @Value("${app.webclient.http.proxy-port:0}")
+    private int httpProxyPort = 0;
+
     public WebClientConfig getWebClientConfig() {
+        HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
+            .httpProxyHost(this.httpProxyHost) //
+            .httpProxyPort(this.httpProxyPort) //
+            .build();
         return ImmutableWebClientConfig.builder() //
             .keyStoreType(this.sslKeyStoreType) //
             .keyStorePassword(this.sslKeyStorePassword) //
@@ -70,6 +75,7 @@ public class ApplicationConfig {
             .isTrustStoreUsed(this.sslTrustStoreUsed) //
             .trustStore(this.sslTrustStore) //
             .trustStorePassword(this.sslTrustStorePassword) //
+            .httpProxyConfig(httpProxyConfig) //
             .build();
     }
 
index 61d0f5a..0b68248 100644 (file)
@@ -42,4 +42,13 @@ public interface WebClientConfig {
 
     public String trustStore();
 
+    @Value.Immutable
+    public interface HttpProxyConfig {
+        public String httpProxyHost();
+
+        public int httpProxyPort();
+    }
+
+    public HttpProxyConfig httpProxyConfig();
+
 }
index 9087355..c222cfa 100644 (file)
@@ -55,7 +55,7 @@ public class ConsumerCallbacks {
     @Autowired
     public ConsumerCallbacks(ApplicationConfig config, EiTypes eiTypes, EiJobs eiJobs) {
         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config.getWebClientConfig());
-        this.restClient = restClientFactory.createRestClient("");
+        this.restClient = restClientFactory.createRestClientUseHttpProxy("");
         this.eiTypes = eiTypes;
         this.eiJobs = eiJobs;
     }
index dc732e1..00d9c14 100644 (file)
@@ -60,7 +60,7 @@ public class ProducerCallbacks {
     @Autowired
     public ProducerCallbacks(ApplicationConfig config, EiTypes eiTypes) {
         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config.getWebClientConfig());
-        this.restClient = restClientFactory.createRestClient("");
+        this.restClient = restClientFactory.createRestClientNoHttpProxy("");
         this.eiTypes = eiTypes;
     }
 
index e242166..b4c21d4 100644 (file)
@@ -57,7 +57,7 @@ public class ProducerSupervision {
     public ProducerSupervision(ApplicationConfig applicationConfig, EiProducers eiProducers, EiJobs eiJobs,
         EiTypes eiTypes, ConsumerCallbacks consumerCallbacks) {
         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(applicationConfig.getWebClientConfig());
-        this.restClient = restClientFactory.createRestClient("");
+        this.restClient = restClientFactory.createRestClientNoHttpProxy("");
         this.eiJobs = eiJobs;
         this.eiProducers = eiProducers;
         this.eiTypes = eiTypes;
index 51d0ee1..b62a965 100644 (file)
@@ -45,8 +45,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.oransc.enrichment.clients.AsyncRestClient;
 import org.oransc.enrichment.clients.AsyncRestClientFactory;
 import org.oransc.enrichment.configuration.ApplicationConfig;
+import org.oransc.enrichment.configuration.ImmutableHttpProxyConfig;
 import org.oransc.enrichment.configuration.ImmutableWebClientConfig;
 import org.oransc.enrichment.configuration.WebClientConfig;
+import org.oransc.enrichment.configuration.WebClientConfig.HttpProxyConfig;
 import org.oransc.enrichment.controller.ConsumerSimulatorController;
 import org.oransc.enrichment.controller.ProducerSimulatorController;
 import org.oransc.enrichment.controllers.consumer.ConsumerConsts;
@@ -161,7 +163,9 @@ class ApplicationTest {
         ResponseEntity<String> resp = restClient().getForEntity(url).block();
         assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
 
-        String indented = (new JSONObject(resp.getBody())).toString(4);
+        JSONObject jsonObj = new JSONObject(resp.getBody());
+        jsonObj.remove("host");
+        String indented = jsonObj.toString(4);
         try (PrintStream out = new PrintStream(new FileOutputStream("api/ecs-api.json"))) {
             out.print(indented);
         }
@@ -711,6 +715,10 @@ class ApplicationTest {
 
     private AsyncRestClient restClient(boolean useTrustValidation) {
         WebClientConfig config = this.applicationConfig.getWebClientConfig();
+        HttpProxyConfig httpProxyConfig = ImmutableHttpProxyConfig.builder() //
+            .httpProxyHost("") //
+            .httpProxyPort(0) //
+            .build();
         config = ImmutableWebClientConfig.builder() //
             .keyStoreType(config.keyStoreType()) //
             .keyStorePassword(config.keyStorePassword()) //
@@ -719,10 +727,10 @@ class ApplicationTest {
             .isTrustStoreUsed(useTrustValidation) //
             .trustStore(config.trustStore()) //
             .trustStorePassword(config.trustStorePassword()) //
-            .build();
+            .httpProxyConfig(httpProxyConfig).build();
 
         AsyncRestClientFactory restClientFactory = new AsyncRestClientFactory(config);
-        return restClientFactory.createRestClient(baseUrl());
+        return restClientFactory.createRestClientNoHttpProxy(baseUrl());
     }
 
     private AsyncRestClient restClient() {
index f879c7b..364203a 100644 (file)
@@ -58,7 +58,7 @@ class AsyncRestClientTest {
         InternalLoggerFactory.setDefaultFactory(JdkLoggerFactory.INSTANCE);
         Loggers.useJdkLoggers();
         mockWebServer = new MockWebServer();
-        clientUnderTest = new AsyncRestClient(mockWebServer.url(BASE_URL).toString());
+        clientUnderTest = new AsyncRestClient(mockWebServer.url(BASE_URL).toString(), null, null);
     }
 
     @AfterAll