diff options
Diffstat (limited to 'aai-client-loadbalancer')
4 files changed, 463 insertions, 0 deletions
diff --git a/aai-client-loadbalancer/pom.xml b/aai-client-loadbalancer/pom.xml new file mode 100644 index 00000000..3a100371 --- /dev/null +++ b/aai-client-loadbalancer/pom.xml @@ -0,0 +1,90 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onap.aai.aai-common</groupId> + <artifactId>aai-common</artifactId> + <version>1.2.1-SNAPSHOT</version> + </parent> + <artifactId>aai-client-loadbalancer</artifactId> + <version>1.2.1-SNAPSHOT</version> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + </configuration> + </plugin> + </plugins> + </build> + <packaging>jar</packaging> + + <name>aai-client-loadbalancer</name> + <description>AAI Client Side Loader Balancer to replace Dmaap</description> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <java.version>1.8</java.version> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-ribbon</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-commons</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <version>1.5.2.RELEASE</version> + </dependency> + <dependency> + <groupId>com.att.eelf</groupId> + <artifactId>eelf-core</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.springframework.retry</groupId> + <artifactId>spring-retry</artifactId> + <version>1.2.1.RELEASE</version> + </dependency> + </dependencies> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-dependencies</artifactId> + <version>Camden.SR5</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <repositories> + <repository> + <id>spring-snapshots</id> + <name>Spring Snapshots</name> + <url>https://repo.spring.io/snapshot</url> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + <repository> + <id>spring-milestones</id> + <name>Spring Milestones</name> + <url>https://repo.spring.io/milestone</url> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + </repositories> + +</project> diff --git a/aai-client-loadbalancer/src/main/java/org/onap/aai/AAIRibbonConfiguration.java b/aai-client-loadbalancer/src/main/java/org/onap/aai/AAIRibbonConfiguration.java new file mode 100644 index 00000000..4c639e44 --- /dev/null +++ b/aai-client-loadbalancer/src/main/java/org/onap/aai/AAIRibbonConfiguration.java @@ -0,0 +1,54 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai; + +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.LoadBalancerBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestTemplate; + +/** + * AAIRibbonConfiguration is responsible for configuring the dmaap + * and it reads the users the application properties class + * and is mostly configurable via properties + */ +public class AAIRibbonConfiguration { + + @Autowired + IClientConfig ribbonClientConfig; + + @Bean + public ILoadBalancer ribbonLoadBalancer() { + return LoadBalancerBuilder.newBuilder() + .withClientConfig(ribbonClientConfig) + .buildLoadBalancerFromConfigWithReflection(); + } + + @LoadBalanced + @Bean + public RestTemplate loadBalancedRestTemplate(){ + return new RestTemplate(); + } +} diff --git a/aai-client-loadbalancer/src/main/java/org/onap/aai/config/HttpPing.java b/aai-client-loadbalancer/src/main/java/org/onap/aai/config/HttpPing.java new file mode 100644 index 00000000..4ecbfb60 --- /dev/null +++ b/aai-client-loadbalancer/src/main/java/org/onap/aai/config/HttpPing.java @@ -0,0 +1,68 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.config; + +import java.util.Optional; + +/** + * <b>HttpPing</b> interface provides access to update the endpoint and + * and the security level of the server that the user is trying to access + */ +public interface HttpPing { + + /** + * Sets the endpoint that the http get request will + * make to verify if the url can be reached + * + * @param endpoint - the endpoint of the url that is used to do healthcheck + */ + void setHealthCheckEndpoint(String endpoint); + + /** + * Returns the health check endpoint that the implementation + * will use in order to verify if the server is reachable at that location + * + * @return endpoint - the endpoint of the url that is used to do healthcheck + */ + String getHealthCheckEndpoint(); + + /** + * Set the credentials for the rest endpoint to verify authorization + * + * @param username - the username to the server trying to connect to + * @param password - the password to the server trying to connect to + */ + void setCredentials(String username, String password); + + /** + * Return the base64 authorization string set from the username and password + * + * @return encoded string using base64 of the username and password values + * like this: + * <pre> + * @{code + * "username:password" => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + * } + * </pre> + */ + Optional<String> getAuthorization(); +} diff --git a/aai-client-loadbalancer/src/main/java/org/onap/aai/config/HttpPingImpl.java b/aai-client-loadbalancer/src/main/java/org/onap/aai/config/HttpPingImpl.java new file mode 100644 index 00000000..2e5804eb --- /dev/null +++ b/aai-client-loadbalancer/src/main/java/org/onap/aai/config/HttpPingImpl.java @@ -0,0 +1,251 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + * + * ECOMP is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.config; + +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.IPing; +import com.netflix.loadbalancer.Server; +import org.springframework.http.*; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +public class HttpPingImpl implements HttpPing, IPing { + + private static final EELFLogger logger = EELFManager.getInstance().getLogger(HttpPingImpl.class); + + private static final Base64.Encoder base64Encoder = Base64.getEncoder(); + + private static final HttpHeaders HTTP_HEADERS = new HttpHeaders(); + + // This is a workaround for the topics that the user + // does not have the access to read their own topic status + private static final String MR_STATUS_PATTERN = ".*\"mrstatus\":\\s*4002.*"; + + private static final int HTTPS_PORT = 3905; + private static final int DEFAULT_TIMEOUT = 2; + + private String healthCheckEndpoint; + private String username; + private String password; + + private int timeout; + + private final RestTemplate restTemplate; + + public HttpPingImpl(String healthCheckEndpoint) { + this(new RestTemplate()); + this.healthCheckEndpoint = healthCheckEndpoint; + this.timeout = DEFAULT_TIMEOUT; + } + + public HttpPingImpl(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.healthCheckEndpoint = ""; + this.timeout = DEFAULT_TIMEOUT; + } + + public HttpPingImpl() { + this(""); + } + + public HttpPingImpl(IClientConfig clientConfig) { + + if (!(clientConfig instanceof DefaultClientConfigImpl)) { + throw new UnsupportedOperationException("Unable to support the client config implementation: " + clientConfig.getClass().getName()); + } + + DefaultClientConfigImpl defaultClientConfig = (DefaultClientConfigImpl) clientConfig; + + Map<String, Object> map = defaultClientConfig.getProperties(); + + this.setCredentials(map.get("username").toString(), map.get("password").toString()); + this.setHealthCheckEndpoint(map.get("health.endpoint").toString()); + this.setTimeoutInSecs(Integer.valueOf(map.get("pingport.timeout").toString())); + + this.restTemplate = new RestTemplate(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setHealthCheckEndpoint(String endpoint) { + this.healthCheckEndpoint = endpoint; + } + + /** + * {@inheritDoc} + */ + @Override + public String getHealthCheckEndpoint() { + return healthCheckEndpoint; + } + + @Override + public void setCredentials(String username, String password) { + this.username = username; + this.password = password; + } + + public void setTimeoutInSecs(int timeout) { + this.timeout = timeout; + } + + @Override + public Optional<String> getAuthorization() { + + if (username == null && password == null) { + return Optional.empty(); + } + + if (username == null || username.isEmpty()) { + logger.error("Username is null while the password is not correctly set"); + return Optional.empty(); + } + + if (password == null || password.isEmpty()) { + logger.error("Password is null while the username is not correctly set"); + return Optional.empty(); + } + + String auth = String.format("%s:%s", username, password); + return Optional.ofNullable("Basic " + base64Encoder.encodeToString(auth.getBytes())); + } + + /** + * @{inheritDoc} + */ + @Override + public boolean isAlive(Server server) { + + String url = null; + + // If unable to ping the port then return immediately + if (!pingPort(server)) { + return false; + } + + if (server.getPort() == HTTPS_PORT) { + url = "https://"; + } else { + + url = "http://"; + } + + url = url + server.getId(); + url = url + this.getHealthCheckEndpoint(); + + boolean isAlive = false; + + Optional<String> authorization = getAuthorization(); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + + if (authorization.isPresent()) { + httpHeaders.add("Authorization", authorization.get()); + } + + HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders); + try { + + ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class); + + HttpStatus httpStatus = responseEntity.getStatusCode(); + + if (httpStatus == HttpStatus.OK) { + isAlive = true; + logger.info("Successfully established connection to the following url {}", url); + return isAlive; + } + + logger.warn("Unable to establish a connection the following url {} due to HTTP Code {}, and reason {}", + url, httpStatus.value(), httpStatus.getReasonPhrase()); + + } catch (HttpClientErrorException ex) { + HttpStatus httpStatus = ex.getStatusCode(); + if (httpStatus == HttpStatus.FORBIDDEN) { + // This is a workaround being in play for the topics + // that are unable to read themselves for this user + // In the case of the username and password being + // wrong the response would be unauthorized (401) but if the + // user is authorized but unable to read this topic, then + // we get back the (403) with the message mrstatus 4002 + // This is a temporary workaround to properly identify which server is down + String body = ex.getResponseBodyAsString(); + if (body.matches(MR_STATUS_PATTERN)) { + isAlive = true; + logger.info("Successfully connected by workaround due to unable to read own topic {}", url); + return isAlive; + } else { + logger.warn("Unable to establish a connection to {} due to {}", server.getHostPort(), ex.getMessage()); + } + } else { + logger.warn("Unable to establish a connection to {} due to {}", server.getHostPort(), ex.getMessage()); + } + } catch (Exception ex) { + logger.warn("Unable to establish a connection to {} due to {}", server.getHostPort(), ex.getMessage()); + } + + return isAlive; + } + + /** + * Returns true if it can connect to the host and port within + * the given timeout from the given server parameter + * + * @param server - server that will be taken from the src/main/resources/application.yml file + * @return true if it can make a successful socket connection to the port on the host + */ + public boolean pingPort(Server server) { + + String host = server.getHost(); + Integer port = server.getPort(); + + boolean success = false; + SocketAddress socketAddress = new InetSocketAddress(host, port); + + try (Socket socket = new Socket()) { + socket.connect(socketAddress, timeout * 1000); + if (socket.isConnected()) { + success = true; + } + } catch (IOException e) { + logger.warn("Unable to connect to the host {} on port {} due to {}", host, port, e.getLocalizedMessage()); + success = false; + } + + return success; + } +} |