1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.caravan.io.http.impl;
21
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.TreeSet;
27 import java.util.stream.Collectors;
28
29 import org.apache.commons.configuration.Configuration;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.felix.scr.annotations.Activate;
32 import org.apache.felix.scr.annotations.Component;
33 import org.apache.felix.scr.annotations.ConfigurationPolicy;
34 import org.apache.felix.scr.annotations.Deactivate;
35 import org.apache.felix.scr.annotations.Property;
36 import org.apache.felix.scr.annotations.PropertyOption;
37 import org.apache.felix.scr.annotations.Reference;
38 import org.apache.sling.commons.osgi.PropertiesUtil;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import io.wcm.caravan.io.http.impl.ribbon.CachingLoadBalancerFactory;
43 import io.wcm.caravan.io.http.impl.ribbon.LoadBalancerFactory;
44
45
46
47
48
49 @Component(immediate = true, metatype = true,
50 label = "wcm.io Caravan Resilient Http Service Configuration",
51 description = "Configures transport layer options for service access.",
52 configurationFactory = true, policy = ConfigurationPolicy.REQUIRE)
53 @Property(name = "webconsole.configurationFactory.nameHint", value = "{serviceId}{serviceName}: {ribbonHosts}")
54 public class CaravanHttpServiceConfig {
55
56 @Reference(target = "(type=" + LoadBalancerFactory.CACHING + ")")
57 private LoadBalancerFactory loadBalancerFactory;
58
59
60
61
62 @Property(label = "Service ID", description = "Internal or external service identifier.")
63 public static final String SERVICE_ID_PROPERTY = "serviceId";
64 private static final String DEPRECATED_SERVICE_NAME_PROPERTY = "serviceName";
65
66 static final boolean THROW_EXCEPTION_FOR_STATUS_500_DEFAULT = true;
67
68
69
70
71 @Property(label = "Throw exception for response status > 500",
72 description = "If true, responses with status > 500 will be handled as error(hystrix failures). If the value is set to false, "
73 + "the caller service will has to handle the failure itself.",
74 boolValue = THROW_EXCEPTION_FOR_STATUS_500_DEFAULT
75 )
76 public static final String THROW_EXCEPTION_FOR_STATUS_500 = "exceptionForResponseStatus500";
77
78
79
80
81 @Property(label = "Hosts",
82 description = "Ribbon: List of hostnames/IP addresses and ports to use for service (if multiple are defined software "
83 + "load balancing is applied). Optionally you can add a protocol as well. If you have mutliple entries "
84 + "all have to use the same protocol. Example entry: 'http://host1:8080'.",
85 cardinality = Integer.MAX_VALUE)
86 public static final String RIBBON_HOSTS_PROPERTY = "ribbonHosts";
87
88
89
90
91 @Property(label = "Protocol",
92 description = "Choose between HTTP and HTTPS protocol for communicating with the Hosts. "
93 + "If set to 'Auto' the protocol is detected automatically from the port number (443 and 8443 = HTTPS).",
94 value = CaravanHttpServiceConfig.PROTOCOL_PROPERTY_DEFAULT,
95 options = {
96 @PropertyOption(name = RequestUtil.PROTOCOL_AUTO, value = "Auto"),
97 @PropertyOption(name = RequestUtil.PROTOCOL_HTTP, value = "HTTP"),
98 @PropertyOption(name = RequestUtil.PROTOCOL_HTTPS, value = "HTTPS")
99 })
100 public static final String PROTOCOL_PROPERTY = "http.protocol";
101 static final String PROTOCOL_PROPERTY_DEFAULT = RequestUtil.PROTOCOL_AUTO;
102
103
104
105
106 @Property(label = "Max. Auto Retries",
107 description = "Ribbon: Max number of retries on the same server (excluding the first try).",
108 intValue = CaravanHttpServiceConfig.RIBBON_MAXAUTORETRIES_DEFAULT)
109 public static final String RIBBON_MAXAUTORETRIES_PROPERTY = "ribbonMaxAutoRetries";
110 static final int RIBBON_MAXAUTORETRIES_DEFAULT = 0;
111
112
113
114
115 @Property(label = "Max. Auto Retries Next Server",
116 description = "Ribbon: Max number of next servers to retry (excluding the first server).",
117 intValue = CaravanHttpServiceConfig.RIBBON_MAXAUTORETRIESONSERVER_DEFAULT)
118 public static final String RIBBON_MAXAUTORETRIESNEXTSERVER_PROPERTY = "ribbonMaxAutoRetriesNextServer";
119 static final int RIBBON_MAXAUTORETRIESONSERVER_DEFAULT = 0;
120
121
122
123
124 @Property(label = "Isolation Timeout",
125 description = "Hystrix: Time in milliseconds after which the calling thread will timeout and walk away from the "
126 + "HystrixCommand.run() execution and mark the HystrixCommand as a TIMEOUT and perform fallback logic.",
127 intValue = CaravanHttpServiceConfig.HYSTRIX_TIMEOUT_MS_DEFAULT)
128 public static final String HYSTRIX_TIMEOUT_MS_PROPERTY = "hystrixTimeoutMs";
129 static final int HYSTRIX_TIMEOUT_MS_DEFAULT = 120000;
130
131
132
133
134 @Property(label = "Fallback",
135 description = "Hystrix: Whether HystrixCommand.getFallback() will be attempted when failure or rejection occurs.",
136 boolValue = CaravanHttpServiceConfig.HYSTRIX_FALLBACK_ENABLED_DEFAULT)
137 public static final String HYSTRIX_FALLBACK_ENABLED_PROPERTY = "hystrixFallbackEnabled";
138 static final boolean HYSTRIX_FALLBACK_ENABLED_DEFAULT = true;
139
140
141
142
143 @Property(label = "Circuit Breaker",
144 description = "Hystrix: Whether a circuit breaker will be used to track health and short-circuit requests if it trips.",
145 boolValue = CaravanHttpServiceConfig.HYSTRIX_CIRCUITBREAKER_ENABLED_DEFAULT)
146 public static final String HYSTRIX_CIRCUITBREAKER_ENABLED_PROPERTY = "hystrixCircuitBreakerEnabled";
147 static final boolean HYSTRIX_CIRCUITBREAKER_ENABLED_DEFAULT = true;
148
149
150
151
152 @Property(label = "Request Volume Threshold",
153 description = "Hystrix: Circuit Breaker - Minimum number of requests in rolling window needed before tripping the circuit will occur.",
154 intValue = CaravanHttpServiceConfig.HYSTRIX_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD_DEFAULT)
155 public static final String HYSTRIX_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD_PROPERTY = "hystrixCircuitBreakerRequestVolumeThreshold";
156 static final int HYSTRIX_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD_DEFAULT = 20;
157
158
159
160
161 @Property(label = "Sleep Window",
162 description = "Hystrix: Circuit Breaker - After tripping the circuit how long in milliseconds to reject requests before allowing "
163 + "attempts again to determine if the circuit should be closed.",
164 intValue = CaravanHttpServiceConfig.HYSTRIX_CIRCUITBREAKER_SLEEPWINDOW_MS_DEFAULT)
165 public static final String HYSTRIX_CIRCUITBREAKER_SLEEPWINDOW_MS_PROPERTY = "hystrixCircuitBreakerSleepWindowMs";
166 static final int HYSTRIX_CIRCUITBREAKER_SLEEPWINDOW_MS_DEFAULT = 5000;
167
168
169
170
171 @Property(label = "Error Threshold Percentage",
172 description = "Hystrix: Circuit Breaker - Error percentage at which the circuit should trip open and start short-circuiting "
173 + "requests to fallback logic.",
174 intValue = CaravanHttpServiceConfig.HYSTRIX_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE_DEFAULT)
175 public static final String HYSTRIX_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE_PROPERTY = "hystrixCircuitBreakerErrorThresholdPercentage";
176 static final int HYSTRIX_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE_DEFAULT = 50;
177
178
179
180
181 @Property(label = "Force Open",
182 description = "Hystrix: Circuit Breaker - If true the circuit breaker will be forced open (tripped) and reject all requests.",
183 boolValue = CaravanHttpServiceConfig.HYSTRIX_CIRCUITBREAKER_FORCEOPEN_DEFAULT)
184 public static final String HYSTRIX_CIRCUITBREAKER_FORCEOPEN_PROPERTY = "hystrixCircuitBreakerForceOpen";
185 static final boolean HYSTRIX_CIRCUITBREAKER_FORCEOPEN_DEFAULT = false;
186
187
188
189
190 @Property(label = "Force Closed",
191 description = "Hystrix: Circuit Breaker - If true the circuit breaker will remain closed and allow requests regardless of the error percentage.",
192 boolValue = CaravanHttpServiceConfig.HYSTRIX_CIRCUITBREAKER_FORCECLOSED_DEFAULT)
193 public static final String HYSTRIX_CIRCUITBREAKER_FORCECLOSED_PROPERTY = "hystrixCircuitBreakerForceClosed";
194 static final boolean HYSTRIX_CIRCUITBREAKER_FORCECLOSED_DEFAULT = false;
195
196 @Property(label = "Thread Pool Name",
197 description = "Hystrix: Overrides the default thread pool for the service")
198 static final String HYSTRIX_EXECUTIONISOLATIONTHREADPOOLKEY_OVERRIDE_PROPERTY = "hystrixThreadPoolKeyOverride";
199
200 static final String RIBBON_PARAM_LISTOFSERVERS = ".ribbon.listOfServers";
201 static final String RIBBON_PARAM_MAXAUTORETRIES = ".ribbon.MaxAutoRetries";
202 static final String RIBBON_PARAM_MAXAUTORETRIESONSERVER = ".ribbon.MaxAutoRetriesNextServer";
203 static final String RIBBON_PARAM_OKTORETRYONALLOPERATIONS = ".ribbon.OkToRetryOnAllOperations";
204
205 static final String HYSTRIX_COMMAND_PREFIX = "hystrix.command.";
206 static final String HYSTRIX_PARAM_TIMEOUT_MS = ".execution.isolation.thread.timeoutInMilliseconds";
207 static final String HYSTRIX_PARAM_FALLBACK_ENABLED = ".fallback.enabled";
208 static final String HYSTRIX_PARAM_CIRCUITBREAKER_ENABLED = ".circuitBreaker.enabled";
209 static final String HYSTRIX_PARAM_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD = ".circuitBreaker.requestVolumeThreshold";
210 static final String HYSTRIX_PARAM_CIRCUITBREAKER_SLEEPWINDOW_MS = ".circuitBreaker.sleepWindowInMilliseconds";
211 static final String HYSTRIX_PARAM_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE = ".circuitBreaker.errorThresholdPercentage";
212 static final String HYSTRIX_PARAM_CIRCUITBREAKER_FORCEOPEN = ".circuitBreaker.forceOpen";
213 static final String HYSTRIX_PARAM_CIRCUITBREAKER_FORCECLOSED = ".circuitBreaker.forceClosed";
214 static final String HYSTRIX_PARAM_EXECUTIONISOLATIONTHREADPOOLKEY_OVERRIDE = ".threadPoolKeyOverride";
215
216
217
218
219 public static final String HTTP_PARAM_PROTOCOL = ".http.protocol";
220
221 static final String LIST_SEPARATOR = ",";
222
223 private static final Logger log = LoggerFactory.getLogger(CaravanHttpServiceConfig.class);
224
225 @Activate
226 protected void activate(Map<String, Object> config) {
227 String serviceId = getServiceId(config);
228 if (CaravanHttpServiceConfigValidator.isValidServiceConfig(serviceId, config)) {
229 setArchiausProperties(serviceId, config);
230 }
231 }
232
233 @Deactivate
234 protected void deactivate(Map<String, Object> config) {
235
236 String serviceId = getServiceId(config);
237 clearArchiausProperties(serviceId);
238
239 if (loadBalancerFactory != null && loadBalancerFactory instanceof CachingLoadBalancerFactory) {
240 ((CachingLoadBalancerFactory)loadBalancerFactory).unregister(serviceId);
241 }
242 }
243
244 private String getServiceId(Map<String, Object> config) {
245 return PropertiesUtil.toString(config.get(SERVICE_ID_PROPERTY),
246 PropertiesUtil.toString(config.get(DEPRECATED_SERVICE_NAME_PROPERTY), null));
247 }
248
249
250
251
252
253
254 private void setArchiausProperties(String serviceId, Map<String, Object> config) {
255 Configuration archaiusConfig = ArchaiusConfig.getConfiguration();
256
257
258 archaiusConfig.setProperty(serviceId + RIBBON_PARAM_LISTOFSERVERS,
259 StringUtils.join(PropertiesUtil.toStringArray(config.get(RIBBON_HOSTS_PROPERTY), new String[0]), LIST_SEPARATOR));
260 archaiusConfig.setProperty(serviceId + RIBBON_PARAM_MAXAUTORETRIES,
261 PropertiesUtil.toInteger(config.get(RIBBON_MAXAUTORETRIES_PROPERTY), RIBBON_MAXAUTORETRIES_DEFAULT));
262 archaiusConfig.setProperty(serviceId + RIBBON_PARAM_MAXAUTORETRIESONSERVER,
263 PropertiesUtil.toInteger(config.get(RIBBON_MAXAUTORETRIESNEXTSERVER_PROPERTY), RIBBON_MAXAUTORETRIESONSERVER_DEFAULT));
264 archaiusConfig.setProperty(serviceId + RIBBON_PARAM_OKTORETRYONALLOPERATIONS, "true");
265
266
267 archaiusConfig.setProperty("hystrix.threadpool.default.maxQueueSize", CaravanHttpThreadPoolConfig.HYSTRIX_THREADPOOL_MAXQUEUESIZE_DEFAULT);
268 archaiusConfig.setProperty("hystrix.threadpool.default.queueSizeRejectionThreshold",
269 CaravanHttpThreadPoolConfig.HYSTRIX_THREADPOOL_QUEUESIZEREJECTIONTHRESHOLD_DEFAULT);
270
271 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_TIMEOUT_MS,
272 PropertiesUtil.toInteger(config.get(HYSTRIX_TIMEOUT_MS_PROPERTY), HYSTRIX_TIMEOUT_MS_DEFAULT));
273 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_FALLBACK_ENABLED,
274 PropertiesUtil.toBoolean(config.get(HYSTRIX_FALLBACK_ENABLED_PROPERTY), HYSTRIX_FALLBACK_ENABLED_DEFAULT));
275 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_ENABLED,
276 PropertiesUtil.toBoolean(config.get(HYSTRIX_CIRCUITBREAKER_ENABLED_PROPERTY), HYSTRIX_CIRCUITBREAKER_ENABLED_DEFAULT));
277 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD,
278 PropertiesUtil.toInteger(config.get(HYSTRIX_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD_PROPERTY), HYSTRIX_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD_DEFAULT));
279 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_SLEEPWINDOW_MS,
280 PropertiesUtil.toInteger(config.get(HYSTRIX_CIRCUITBREAKER_SLEEPWINDOW_MS_PROPERTY), HYSTRIX_CIRCUITBREAKER_SLEEPWINDOW_MS_DEFAULT));
281 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE,
282 PropertiesUtil.toInteger(config.get(HYSTRIX_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE_PROPERTY),
283 HYSTRIX_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE_DEFAULT));
284 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_FORCEOPEN,
285 PropertiesUtil.toBoolean(config.get(HYSTRIX_CIRCUITBREAKER_FORCEOPEN_PROPERTY), HYSTRIX_CIRCUITBREAKER_FORCEOPEN_DEFAULT));
286 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_FORCECLOSED,
287 PropertiesUtil.toBoolean(config.get(HYSTRIX_CIRCUITBREAKER_FORCECLOSED_PROPERTY), HYSTRIX_CIRCUITBREAKER_FORCECLOSED_DEFAULT));
288 if (config.get(HYSTRIX_EXECUTIONISOLATIONTHREADPOOLKEY_OVERRIDE_PROPERTY) != null) {
289
290 archaiusConfig.setProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_EXECUTIONISOLATIONTHREADPOOLKEY_OVERRIDE,
291 config.get(HYSTRIX_EXECUTIONISOLATIONTHREADPOOLKEY_OVERRIDE_PROPERTY));
292 }
293
294
295 archaiusConfig.setProperty(serviceId + HTTP_PARAM_PROTOCOL, PropertiesUtil.toString(config.get(PROTOCOL_PROPERTY), PROTOCOL_PROPERTY_DEFAULT));
296 archaiusConfig.setProperty(serviceId + THROW_EXCEPTION_FOR_STATUS_500, PropertiesUtil.toBoolean(config.get(THROW_EXCEPTION_FOR_STATUS_500),
297 THROW_EXCEPTION_FOR_STATUS_500_DEFAULT));
298
299
300 applyRibbonHostsProcotol(serviceId);
301 }
302
303
304
305
306
307
308 private void applyRibbonHostsProcotol(String serviceId) {
309 Configuration archaiusConfig = ArchaiusConfig.getConfiguration();
310
311 String[] listOfServers = archaiusConfig.getStringArray(serviceId + RIBBON_PARAM_LISTOFSERVERS);
312 String protocolForAllServers = archaiusConfig.getString(serviceId + HTTP_PARAM_PROTOCOL);
313
314
315 Set<String> protocolsFromListOfServers = Arrays.stream(listOfServers)
316 .filter(server -> StringUtils.contains(server, "://"))
317 .map(server -> StringUtils.substringBefore(server, "://"))
318 .collect(Collectors.toSet());
319
320
321 if (protocolsFromListOfServers.isEmpty()) {
322 return;
323 }
324
325
326 String protocol = new TreeSet<String>(protocolsFromListOfServers).iterator().next();
327 if (protocolsFromListOfServers.size() > 1) {
328 log.warn("Different protocols are defined for property {}: {}. Only protocol '{}' is used.",
329 RIBBON_HOSTS_PROPERTY, StringUtils.join(listOfServers, LIST_SEPARATOR), protocol);
330 }
331
332
333 if (!(StringUtils.equals(protocolForAllServers, RequestUtil.PROTOCOL_AUTO)
334 || StringUtils.equals(protocolForAllServers, protocol))) {
335 log.warn("Protocol '{}' is defined for property {}: {}, but an other protocol is defined in the server list: {}. Only protocol '{}' is used.",
336 protocolForAllServers, PROTOCOL_PROPERTY, StringUtils.join(listOfServers, LIST_SEPARATOR), protocol);
337 }
338
339
340 List<String> listOfServersWithoutProtocol = Arrays.stream(listOfServers)
341 .map(server -> StringUtils.substringAfter(server, "://"))
342 .collect(Collectors.toList());
343 archaiusConfig.setProperty(serviceId + RIBBON_PARAM_LISTOFSERVERS, StringUtils.join(listOfServersWithoutProtocol, LIST_SEPARATOR));
344 archaiusConfig.setProperty(serviceId + HTTP_PARAM_PROTOCOL, protocol);
345 }
346
347
348
349
350
351 private void clearArchiausProperties(String serviceId) {
352 Configuration archaiusConfig = ArchaiusConfig.getConfiguration();
353
354
355 archaiusConfig.clearProperty(serviceId + RIBBON_PARAM_LISTOFSERVERS);
356 archaiusConfig.clearProperty(serviceId + RIBBON_PARAM_MAXAUTORETRIES);
357 archaiusConfig.clearProperty(serviceId + RIBBON_PARAM_MAXAUTORETRIESONSERVER);
358 archaiusConfig.clearProperty(serviceId + RIBBON_PARAM_OKTORETRYONALLOPERATIONS);
359
360
361 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_TIMEOUT_MS);
362 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_FALLBACK_ENABLED);
363 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_ENABLED);
364 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_REQUESTVOLUMETHRESHOLD);
365 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_SLEEPWINDOW_MS);
366 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_ERRORTHRESHOLDPERCENTAGE);
367 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_FORCEOPEN);
368 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_CIRCUITBREAKER_FORCECLOSED);
369 archaiusConfig.clearProperty(HYSTRIX_COMMAND_PREFIX + serviceId + HYSTRIX_PARAM_EXECUTIONISOLATIONTHREADPOOLKEY_OVERRIDE);
370
371
372 archaiusConfig.clearProperty(serviceId + HTTP_PARAM_PROTOCOL);
373 }
374
375 }