2e5804eb4b6e41821dc87620b7fb4fba227b4649
[aai/aai-common.git] / aai-client-loadbalancer / src / main / java / org / onap / aai / config / HttpPingImpl.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
4  * ================================================================================
5  * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *    http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  *
20  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
21  */
22 package org.onap.aai.config;
23
24 import com.att.eelf.configuration.EELFLogger;
25 import com.att.eelf.configuration.EELFManager;
26 import com.netflix.client.config.DefaultClientConfigImpl;
27 import com.netflix.client.config.IClientConfig;
28 import com.netflix.loadbalancer.IPing;
29 import com.netflix.loadbalancer.Server;
30 import org.springframework.http.*;
31 import org.springframework.web.client.HttpClientErrorException;
32 import org.springframework.web.client.RestTemplate;
33
34 import java.io.IOException;
35 import java.net.InetSocketAddress;
36 import java.net.Socket;
37 import java.net.SocketAddress;
38 import java.util.Base64;
39 import java.util.Collections;
40 import java.util.Map;
41 import java.util.Optional;
42
43 public class HttpPingImpl implements HttpPing, IPing {
44
45     private static final EELFLogger logger = EELFManager.getInstance().getLogger(HttpPingImpl.class);
46
47     private static final Base64.Encoder base64Encoder = Base64.getEncoder();
48
49     private static final HttpHeaders HTTP_HEADERS = new HttpHeaders();
50
51     // This is a workaround for the topics that the user
52     // does not have the access to read their own topic status
53     private static final String MR_STATUS_PATTERN = ".*\"mrstatus\":\\s*4002.*";
54
55     private static final int HTTPS_PORT = 3905;
56     private static final int DEFAULT_TIMEOUT = 2;
57
58     private String healthCheckEndpoint;
59     private String username;
60     private String password;
61
62     private int timeout;
63
64     private final RestTemplate restTemplate;
65
66     public HttpPingImpl(String healthCheckEndpoint) {
67         this(new RestTemplate());
68         this.healthCheckEndpoint = healthCheckEndpoint;
69         this.timeout = DEFAULT_TIMEOUT;
70     }
71
72     public HttpPingImpl(RestTemplate restTemplate) {
73         this.restTemplate = restTemplate;
74         this.healthCheckEndpoint = "";
75         this.timeout = DEFAULT_TIMEOUT;
76     }
77
78     public HttpPingImpl() {
79         this("");
80     }
81
82     public HttpPingImpl(IClientConfig clientConfig) {
83
84         if (!(clientConfig instanceof DefaultClientConfigImpl)) {
85             throw new UnsupportedOperationException("Unable to support the client config implementation: " + clientConfig.getClass().getName());
86         }
87
88         DefaultClientConfigImpl defaultClientConfig = (DefaultClientConfigImpl) clientConfig;
89
90         Map<String, Object> map = defaultClientConfig.getProperties();
91
92         this.setCredentials(map.get("username").toString(), map.get("password").toString());
93         this.setHealthCheckEndpoint(map.get("health.endpoint").toString());
94         this.setTimeoutInSecs(Integer.valueOf(map.get("pingport.timeout").toString()));
95
96         this.restTemplate = new RestTemplate();
97     }
98
99     /**
100      * {@inheritDoc}
101      */
102     @Override
103     public void setHealthCheckEndpoint(String endpoint) {
104         this.healthCheckEndpoint = endpoint;
105     }
106
107     /**
108      * {@inheritDoc}
109      */
110     @Override
111     public String getHealthCheckEndpoint() {
112         return healthCheckEndpoint;
113     }
114
115     @Override
116     public void setCredentials(String username, String password) {
117         this.username = username;
118         this.password = password;
119     }
120
121     public void setTimeoutInSecs(int timeout) {
122         this.timeout = timeout;
123     }
124
125     @Override
126     public Optional<String> getAuthorization() {
127
128         if (username == null && password == null) {
129             return Optional.empty();
130         }
131
132         if (username == null || username.isEmpty()) {
133             logger.error("Username is null while the password is not correctly set");
134             return Optional.empty();
135         }
136
137         if (password == null || password.isEmpty()) {
138             logger.error("Password is null while the username is not correctly set");
139             return Optional.empty();
140         }
141
142         String auth = String.format("%s:%s", username, password);
143         return Optional.ofNullable("Basic " + base64Encoder.encodeToString(auth.getBytes()));
144     }
145
146     /**
147      * @{inheritDoc}
148      */
149     @Override
150     public boolean isAlive(Server server) {
151
152         String url = null;
153
154         // If unable to ping the port then return immediately
155         if (!pingPort(server)) {
156             return false;
157         }
158
159         if (server.getPort() == HTTPS_PORT) {
160             url = "https://";
161         } else {
162
163             url = "http://";
164         }
165
166         url = url + server.getId();
167         url = url + this.getHealthCheckEndpoint();
168
169         boolean isAlive = false;
170
171         Optional<String> authorization = getAuthorization();
172
173         HttpHeaders httpHeaders = new HttpHeaders();
174         httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
175
176         if (authorization.isPresent()) {
177             httpHeaders.add("Authorization", authorization.get());
178         }
179
180         HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
181         try {
182
183             ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
184
185             HttpStatus httpStatus = responseEntity.getStatusCode();
186
187             if (httpStatus == HttpStatus.OK) {
188                 isAlive = true;
189                 logger.info("Successfully established connection to the following url {}", url);
190                 return isAlive;
191             }
192
193             logger.warn("Unable to establish a connection the following url {} due to HTTP Code {}, and reason {}",
194                     url, httpStatus.value(), httpStatus.getReasonPhrase());
195
196         } catch (HttpClientErrorException ex) {
197             HttpStatus httpStatus = ex.getStatusCode();
198             if (httpStatus == HttpStatus.FORBIDDEN) {
199                 // This is a workaround being in play for the topics
200                 // that are unable to read themselves for this user
201                 // In the case of the username and password being
202                 // wrong the response would be unauthorized (401) but if the
203                 // user is authorized but unable to read this topic, then
204                 // we get back the (403) with the message mrstatus 4002
205                 // This is a temporary workaround to properly identify which server is down
206                 String body = ex.getResponseBodyAsString();
207                 if (body.matches(MR_STATUS_PATTERN)) {
208                     isAlive = true;
209                     logger.info("Successfully connected by workaround due to unable to read own topic {}", url);
210                     return isAlive;
211                 } else {
212                     logger.warn("Unable to establish a connection to {} due to {}", server.getHostPort(), ex.getMessage());
213                 }
214             } else {
215                 logger.warn("Unable to establish a connection to {} due to {}", server.getHostPort(), ex.getMessage());
216             }
217         } catch (Exception ex) {
218             logger.warn("Unable to establish a connection to {} due to {}", server.getHostPort(), ex.getMessage());
219         }
220
221         return isAlive;
222     }
223
224     /**
225      * Returns true if it can connect to the host and port within
226      * the given timeout from the given server parameter
227      *
228      * @param server - server that will be taken from the src/main/resources/application.yml file
229      * @return true if it can make a successful socket connection to the port on the host
230      */
231     public boolean pingPort(Server server) {
232
233         String host = server.getHost();
234         Integer port = server.getPort();
235
236         boolean success = false;
237         SocketAddress socketAddress = new InetSocketAddress(host, port);
238
239         try (Socket socket = new Socket()) {
240             socket.connect(socketAddress, timeout * 1000);
241             if (socket.isConnected()) {
242                 success = true;
243             }
244         } catch (IOException e) {
245             logger.warn("Unable to connect to the host {} on port {} due to {}", host, port, e.getLocalizedMessage());
246             success = false;
247         }
248
249         return success;
250     }
251 }