1   /*
2    * Copyright 2020 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  
17  package com.linecorp.centraldogma.testing.internal;
18  
19  import java.io.File;
20  import java.io.IOError;
21  import java.net.InetSocketAddress;
22  import java.net.UnknownHostException;
23  import java.util.concurrent.CompletableFuture;
24  
25  import javax.annotation.Nullable;
26  
27  import com.linecorp.armeria.client.ClientFactory;
28  import com.linecorp.armeria.client.WebClient;
29  import com.linecorp.armeria.client.WebClientBuilder;
30  import com.linecorp.armeria.common.SessionProtocol;
31  import com.linecorp.armeria.common.util.Exceptions;
32  import com.linecorp.armeria.internal.common.util.SelfSignedCertificate;
33  import com.linecorp.armeria.server.ServerPort;
34  import com.linecorp.centraldogma.client.CentralDogma;
35  import com.linecorp.centraldogma.client.armeria.AbstractArmeriaCentralDogmaBuilder;
36  import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder;
37  import com.linecorp.centraldogma.client.armeria.legacy.LegacyCentralDogmaBuilder;
38  import com.linecorp.centraldogma.server.CentralDogmaBuilder;
39  import com.linecorp.centraldogma.server.GracefulShutdownTimeout;
40  import com.linecorp.centraldogma.server.MirroringService;
41  import com.linecorp.centraldogma.server.TlsConfig;
42  import com.linecorp.centraldogma.server.storage.project.ProjectManager;
43  
44  import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
45  import io.netty.util.NetUtil;
46  
47  /**
48   * A delegate that has common testing methods of {@link com.linecorp.centraldogma.server.CentralDogma}.
49   */
50  public class CentralDogmaRuleDelegate {
51  
52      private static final InetSocketAddress TEST_PORT = new InetSocketAddress(NetUtil.LOCALHOST4, 0);
53  
54      private final boolean useTls;
55      @Nullable
56      private volatile com.linecorp.centraldogma.server.CentralDogma dogma;
57      @Nullable
58      private volatile CentralDogma client;
59      @Nullable
60      private volatile CentralDogma legacyClient;
61      @Nullable
62      private volatile WebClient webClient;
63      @Nullable
64      private volatile InetSocketAddress serverAddress;
65  
66      /**
67       * Creates a new instance.
68       */
69      public CentralDogmaRuleDelegate(boolean useTls) {
70          this.useTls = useTls;
71      }
72  
73      /**
74       * Starts an embedded server and calls {@link #scaffold(CentralDogma)}.
75       */
76      public void setUp(File dataDir) {
77          startAsync(dataDir).join();
78          final CentralDogma client = this.client;
79          assert client != null;
80          assert legacyClient != null;
81          scaffold(client);
82      }
83  
84      /**
85       * Creates a new server, configures it with {@link #configure(CentralDogmaBuilder)},
86       * and starts the server asynchronously.
87       */
88      public final CompletableFuture<Void> startAsync(File dataDir) {
89          final CentralDogmaBuilder builder = new CentralDogmaBuilder(dataDir)
90                  .port(TEST_PORT, useTls ? SessionProtocol.HTTPS : SessionProtocol.HTTP)
91                  .webAppEnabled(false)
92                  .mirroringEnabled(false)
93                  .gracefulShutdownTimeout(new GracefulShutdownTimeout(0, 0));
94  
95          if (useTls) {
96              try {
97                  final SelfSignedCertificate ssc = new SelfSignedCertificate();
98                  builder.tls(new TlsConfig(null, null,
99                                            "file:" + ssc.certificate(), "file:" + ssc.privateKey(), null));
100             } catch (Exception e) {
101                 Exceptions.throwUnsafely(e);
102             }
103         }
104 
105         // specify a new meterRegistry since multiple instances writing to a single meterRegistry
106         // can be a source of flakiness
107         builder.meterRegistry(new CompositeMeterRegistry());
108 
109         configure(builder);
110 
111         final com.linecorp.centraldogma.server.CentralDogma dogma = builder.build();
112         this.dogma = dogma;
113         return dogma.start().thenRun(() -> {
114             final ServerPort activePort = dogma.activePort();
115             if (activePort == null) {
116                 // Stopped already.
117                 return;
118             }
119 
120             final InetSocketAddress serverAddress = activePort.localAddress();
121             this.serverAddress = serverAddress;
122 
123             final ArmeriaCentralDogmaBuilder clientBuilder = new ArmeriaCentralDogmaBuilder();
124             final LegacyCentralDogmaBuilder legacyClientBuilder = new LegacyCentralDogmaBuilder();
125 
126             configureClientCommon(clientBuilder);
127             configureClientCommon(legacyClientBuilder);
128             configureClient(clientBuilder);
129             configureClient(legacyClientBuilder);
130 
131             try {
132                 client = clientBuilder.build();
133                 legacyClient = legacyClientBuilder.build();
134             } catch (UnknownHostException e) {
135                 // Should never reach here.
136                 throw new IOError(e);
137             }
138 
139             final String uri = "h2c://" + serverAddress.getHostString() + ':' + serverAddress.getPort();
140             final WebClientBuilder webClientBuilder = WebClient.builder(uri);
141             configureHttpClient(webClientBuilder);
142             webClient = webClientBuilder.build();
143         });
144     }
145 
146     /**
147      * Stops the server.
148      */
149     public final CompletableFuture<Void> stopAsync() {
150         final com.linecorp.centraldogma.server.CentralDogma dogma = this.dogma;
151         this.dogma = null;
152 
153         if (dogma != null) {
154             return dogma.stop();
155         } else {
156             return CompletableFuture.completedFuture(null);
157         }
158     }
159 
160     /**
161      * Returns whether the server is running over TLS or not.
162      */
163     public boolean useTls() {
164         return useTls;
165     }
166 
167     /**
168      * Returns the server.
169      *
170      * @throws IllegalStateException if Central Dogma did not start yet
171      */
172     public final com.linecorp.centraldogma.server.CentralDogma dogma() {
173         final com.linecorp.centraldogma.server.CentralDogma dogma = this.dogma;
174         if (dogma == null) {
175             throw new IllegalStateException("Central Dogma not available");
176         }
177         return dogma;
178     }
179 
180     /**
181      * Returns the {@link ProjectManager} of the server.
182      *
183      * @throws IllegalStateException if Central Dogma did not start yet
184      */
185     public ProjectManager projectManager() {
186         return dogma().projectManager();
187     }
188 
189     /**
190      * Returns the {@link MirroringService} of the server.
191      *
192      * @throws IllegalStateException if Central Dogma did not start yet
193      */
194     public final MirroringService mirroringService() {
195         return dogma().mirroringService().get();
196     }
197 
198     /**
199      * Returns the HTTP-based {@link CentralDogma} client.
200      *
201      * @throws IllegalStateException if Central Dogma did not start yet
202      */
203     public final CentralDogma client() {
204         final CentralDogma client = this.client;
205         if (client == null) {
206             throw new IllegalStateException("Central Dogma client not available");
207         }
208         return client;
209     }
210 
211     /**
212      * Returns the Thrift-based {@link CentralDogma} client.
213      *
214      * @throws IllegalStateException if Central Dogma did not start yet
215      */
216     public final CentralDogma legacyClient() {
217         final CentralDogma legacyClient = this.legacyClient;
218         if (legacyClient == null) {
219             throw new IllegalStateException("Central Dogma not started");
220         }
221         return legacyClient;
222     }
223 
224     /**
225      * Returns the HTTP client.
226      *
227      * @throws IllegalStateException if Central Dogma did not start yet
228      */
229     public final WebClient httpClient() {
230         final WebClient webClient = this.webClient;
231         if (webClient == null) {
232             throw new IllegalStateException("Central Dogma not started");
233         }
234         return webClient;
235     }
236 
237     /**
238      * Returns the server address.
239      *
240      * @throws IllegalStateException if Central Dogma did not start yet
241      */
242     public final InetSocketAddress serverAddress() {
243         final InetSocketAddress serverAddress = this.serverAddress;
244         if (serverAddress == null) {
245             throw new IllegalStateException("Central Dogma not started");
246         }
247         return serverAddress;
248     }
249 
250     /**
251      * Override this method to configure the server.
252      */
253     protected void configure(CentralDogmaBuilder builder) {}
254 
255     /**
256      * Override this method to configure the HTTP-based {@link CentralDogma} client builder.
257      */
258     protected void configureClient(ArmeriaCentralDogmaBuilder builder) {}
259 
260     /**
261      * Override this method to configure the Thrift-based {@link CentralDogma} client builder.
262      */
263     protected void configureClient(LegacyCentralDogmaBuilder builder) {}
264 
265     /**
266      * Override this method to configure the {@link WebClient} builder.
267      */
268     protected void configureHttpClient(WebClientBuilder builder) {}
269 
270     /**
271      * Override this method to perform the initial updates on the server,
272      * such as creating a repository and populating sample data.
273      */
274     protected void scaffold(CentralDogma client) {}
275 
276     private void configureClientCommon(AbstractArmeriaCentralDogmaBuilder<?> builder) {
277         final InetSocketAddress serverAddress = this.serverAddress;
278         assert serverAddress != null;
279         builder.host(serverAddress.getHostString(), serverAddress.getPort());
280 
281         if (useTls) {
282             builder.useTls();
283             builder.clientFactory(ClientFactory.insecure());
284         }
285     }
286 }