1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package com.linecorp.centraldogma.client.spring;
17  
18  import java.net.UnknownHostException;
19  import java.util.List;
20  import java.util.Optional;
21  import java.util.concurrent.ExecutionException;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.TimeoutException;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
28  import org.springframework.boot.context.properties.EnableConfigurationProperties;
29  import org.springframework.context.annotation.Bean;
30  import org.springframework.context.annotation.Configuration;
31  import org.springframework.core.env.Environment;
32  
33  import com.google.common.net.HostAndPort;
34  
35  import com.linecorp.armeria.client.ClientFactory;
36  import com.linecorp.armeria.client.ClientFactoryBuilder;
37  import com.linecorp.centraldogma.client.CentralDogma;
38  import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder;
39  import com.linecorp.centraldogma.client.armeria.ArmeriaClientConfigurator;
40  import com.linecorp.centraldogma.client.armeria.DnsAddressEndpointGroupConfigurator;
41  
42  import io.micrometer.core.instrument.MeterRegistry;
43  
44  /**
45   * Spring bean configuration for {@link CentralDogma} client.
46   */
47  @Configuration
48  @ConditionalOnMissingBean(CentralDogma.class)
49  @EnableConfigurationProperties(CentralDogmaSettings.class)
50  public class CentralDogmaClientAutoConfiguration {
51  
52      private static final Logger logger = LoggerFactory.getLogger(CentralDogmaClientAutoConfiguration.class);
53  
54      static final long DEFAULT_INITIALIZATION_TIMEOUT_MILLIS = 15000;
55  
56      /**
57       * Returns a newly created {@link CentralDogma} client.
58       */
59      @Bean
60      public CentralDogma dogmaClient(
61              Environment env,
62              CentralDogmaSettings settings,
63              Optional<List<CentralDogmaClientFactoryConfigurator>> factoryConfigurators,
64              Optional<ArmeriaClientConfigurator> armeriaClientConfigurator,
65              Optional<DnsAddressEndpointGroupConfigurator> dnsAddressEndpointGroupConfigurator,
66              Optional<MeterRegistry> meterRegistry)
67              throws UnknownHostException {
68  
69          final ArmeriaCentralDogmaBuilder builder = new ArmeriaCentralDogmaBuilder();
70  
71          if (factoryConfigurators.isPresent()) {
72              final ClientFactoryBuilder clientFactoryBuilder = ClientFactory.builder();
73              factoryConfigurators.get().forEach(configurator -> configurator.configure(clientFactoryBuilder));
74              builder.clientFactory(clientFactoryBuilder.build());
75          }
76  
77          builder.clientConfigurator(cb -> armeriaClientConfigurator.ifPresent(
78                  configurator -> configurator.configure(cb)));
79          dnsAddressEndpointGroupConfigurator.ifPresent(builder::dnsAddressEndpointGroupConfigurator);
80  
81          // Set health check interval.
82          final Long healthCheckIntervalMillis = settings.getHealthCheckIntervalMillis();
83          if (healthCheckIntervalMillis != null) {
84              builder.healthCheckIntervalMillis(healthCheckIntervalMillis);
85          }
86  
87          // Enable or disable TLS.
88          final Boolean useTls = settings.getUseTls();
89          if (useTls != null) {
90              builder.useTls(useTls);
91          }
92  
93          // Set access token.
94          final String accessToken = settings.getAccessToken();
95          if (accessToken != null) {
96              builder.accessToken(accessToken);
97          }
98  
99          // Set profile or hosts.
100         final String profile = settings.getProfile();
101         final List<String> hosts = settings.getHosts();
102         if (profile != null) {
103             if (hosts != null) {
104                 throw new IllegalStateException(
105                         "'hosts' and 'profile' are mutually exclusive. Do not set both of them.");
106             }
107             builder.profile(CentralDogmaClientAutoConfiguration.class.getClassLoader(), profile);
108         } else if (hosts != null) {
109             for (String h : hosts) {
110                 final HostAndPort hostAndPort = HostAndPort.fromString(h);
111                 if (hostAndPort.hasPort()) {
112                     builder.host(hostAndPort.getHost(), hostAndPort.getPort());
113                 } else {
114                     builder.host(hostAndPort.getHost());
115                 }
116             }
117         } else {
118             // Use the currently active Spring Boot profiles if neither profile nor hosts was specified.
119             final String[] springBootProfiles = env.getActiveProfiles();
120             logger.info("Using the Spring Boot profiles as the source of the Central Dogma client profile: {}",
121                         springBootProfiles);
122             builder.profile(springBootProfiles);
123         }
124 
125         // Replication lag tolerance settings.
126         final Integer maxNumRetriesOnReplicationLag = settings.getMaxNumRetriesOnReplicationLag();
127         if (maxNumRetriesOnReplicationLag != null) {
128             builder.maxNumRetriesOnReplicationLag(maxNumRetriesOnReplicationLag);
129         }
130 
131         final Long retryIntervalOnReplicationLagMillis = settings.getRetryIntervalOnReplicationLagMillis();
132         if (retryIntervalOnReplicationLagMillis != null) {
133             builder.retryIntervalOnReplicationLagMillis(retryIntervalOnReplicationLagMillis);
134         }
135 
136         meterRegistry.ifPresent(builder::meterRegistry);
137 
138         final CentralDogma centralDogma = builder.build();
139         Long initializationTimeoutMillis = settings.getInitializationTimeoutMillis();
140         if (initializationTimeoutMillis == null) {
141             initializationTimeoutMillis = DEFAULT_INITIALIZATION_TIMEOUT_MILLIS;
142         }
143         if (initializationTimeoutMillis > 0) {
144             try {
145                 centralDogma.whenEndpointReady().get(initializationTimeoutMillis, TimeUnit.MILLISECONDS);
146             } catch (InterruptedException | ExecutionException | TimeoutException e) {
147                 throw new IllegalStateException(
148                         "Failed to initialize the endpoints of " + centralDogma + " in " +
149                         initializationTimeoutMillis + " milliseconds", e);
150             }
151         }
152         return centralDogma;
153     }
154 }