-
Notifications
You must be signed in to change notification settings - Fork 920
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Motivation: Nacos is a service discovery application extensively used. By providing native support for Nacos, it will be beneficial to Armeria users. #5360 #5365 Modification: - Add a `nacos` module. - Add `NacosClient`. Its internal implementation includes `LoginClient` for obtaining an accessToken through basic Nacos authentication, `QueryInstancesClient` for querying service instances, and `RegisterInstanceClient` for handling the registration and deregistration of instances. - Implement `NacosEndpointGroup` and `NacosUpdatingListener`. - Configure unit tests using Testcontainers and the Nacos image to facilitate actual call testing. Result: Closes #5365 Nacos discovery and instance registration functionalities are now integrated into Armeria. --- In writing this code, I was strongly influenced by the implementation on the consul and eureka modules. I appreciate your review and feedback. Thank you. --------- Co-authored-by: jrhee17 <guins_j@guins.org> Co-authored-by: Ikhun Um <ikhun.um@linecorp.com> Co-authored-by: minwoox <songmw725@gmail.com>
- Loading branch information
1 parent
011741d
commit 0b36df3
Showing
22 changed files
with
1,998 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
dependencies { | ||
implementation(libs.caffeine) | ||
testImplementation(libs.testcontainers.junit.jupiter) | ||
} |
133 changes: 133 additions & 0 deletions
133
nacos/src/main/java/com/linecorp/armeria/client/nacos/NacosEndpointGroup.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* Copyright 2024 LY Corporation | ||
* | ||
* LY Corporation licenses this file to you 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: | ||
* | ||
* https://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. | ||
*/ | ||
package com.linecorp.armeria.client.nacos; | ||
|
||
import static java.util.Objects.requireNonNull; | ||
|
||
import java.net.URI; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ScheduledFuture; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import com.google.common.base.MoreObjects; | ||
|
||
import com.linecorp.armeria.client.Endpoint; | ||
import com.linecorp.armeria.client.endpoint.DynamicEndpointGroup; | ||
import com.linecorp.armeria.client.endpoint.EndpointGroup; | ||
import com.linecorp.armeria.client.endpoint.EndpointSelectionStrategy; | ||
import com.linecorp.armeria.common.CommonPools; | ||
import com.linecorp.armeria.common.annotation.Nullable; | ||
import com.linecorp.armeria.common.annotation.UnstableApi; | ||
import com.linecorp.armeria.internal.nacos.NacosClient; | ||
|
||
import io.netty.util.concurrent.EventExecutor; | ||
|
||
/** | ||
* A Nacos-based {@link EndpointGroup} implementation that retrieves the list of {@link Endpoint} from Nacos | ||
* using <a href="https://nacos.io/en-us/docs/v2/guide/user/open-api.html">Nacos's HTTP Open API</a> | ||
* and updates the {@link Endpoint}s periodically. | ||
*/ | ||
@UnstableApi | ||
public final class NacosEndpointGroup extends DynamicEndpointGroup { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(NacosEndpointGroup.class); | ||
|
||
/** | ||
* Returns a {@link NacosEndpointGroup} with the specified {@code serviceName}. | ||
*/ | ||
public static NacosEndpointGroup of(URI nacosUri, String serviceName) { | ||
return builder(nacosUri, serviceName).build(); | ||
} | ||
|
||
/** | ||
* Returns a newly-created {@link NacosEndpointGroupBuilder} with the specified {@code nacosUri} | ||
* and {@code serviceName} to build {@link NacosEndpointGroupBuilder}. | ||
* | ||
* @param nacosUri the URI of Nacos API service, including the path up to but not including API version. | ||
* (example: http://localhost:8848/nacos) | ||
*/ | ||
public static NacosEndpointGroupBuilder builder(URI nacosUri, String serviceName) { | ||
return new NacosEndpointGroupBuilder(nacosUri, serviceName); | ||
} | ||
|
||
private final NacosClient nacosClient; | ||
|
||
private final long registryFetchIntervalMillis; | ||
|
||
private final EventExecutor eventLoop; | ||
|
||
@Nullable | ||
private ScheduledFuture<?> scheduledFuture; | ||
|
||
NacosEndpointGroup(EndpointSelectionStrategy selectionStrategy, boolean allowEmptyEndpoints, | ||
long selectionTimeoutMillis, NacosClient nacosClient, | ||
long registryFetchIntervalMillis) { | ||
super(selectionStrategy, allowEmptyEndpoints, selectionTimeoutMillis); | ||
this.nacosClient = requireNonNull(nacosClient, "nacosClient"); | ||
this.registryFetchIntervalMillis = registryFetchIntervalMillis; | ||
eventLoop = CommonPools.workerGroup().next(); | ||
|
||
update(); | ||
} | ||
|
||
private void update() { | ||
if (isClosing()) { | ||
return; | ||
} | ||
|
||
nacosClient.endpoints() | ||
.handleAsync((endpoints, cause) -> { | ||
if (isClosing()) { | ||
return null; | ||
} | ||
|
||
if (cause != null) { | ||
logger.warn("Unexpected exception while fetching the registry from: {}", | ||
nacosClient.uri(), cause); | ||
} else { | ||
setEndpoints(endpoints); | ||
} | ||
|
||
scheduledFuture = eventLoop.schedule(this::update, registryFetchIntervalMillis, | ||
TimeUnit.MILLISECONDS); | ||
return null; | ||
}, eventLoop); | ||
} | ||
|
||
@Override | ||
protected void doCloseAsync(CompletableFuture<?> future) { | ||
if (!eventLoop.inEventLoop()) { | ||
eventLoop.execute(() -> doCloseAsync(future)); | ||
return; | ||
} | ||
|
||
if (scheduledFuture != null) { | ||
scheduledFuture.cancel(true); | ||
} | ||
|
||
future.complete(null); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return MoreObjects.toStringHelper(this) | ||
.add("registryFetchIntervalMillis", registryFetchIntervalMillis) | ||
.toString(); | ||
} | ||
} |
138 changes: 138 additions & 0 deletions
138
nacos/src/main/java/com/linecorp/armeria/client/nacos/NacosEndpointGroupBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/* | ||
* Copyright 2024 LY Corporation | ||
* LY Corporation licenses this file to you 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: | ||
* | ||
* https://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. | ||
*/ | ||
package com.linecorp.armeria.client.nacos; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
import static java.util.Objects.requireNonNull; | ||
|
||
import java.net.URI; | ||
import java.time.Duration; | ||
|
||
import com.linecorp.armeria.client.endpoint.AbstractDynamicEndpointGroupBuilder; | ||
import com.linecorp.armeria.client.endpoint.EndpointSelectionStrategy; | ||
import com.linecorp.armeria.common.Flags; | ||
import com.linecorp.armeria.common.annotation.UnstableApi; | ||
import com.linecorp.armeria.common.nacos.NacosConfigSetters; | ||
import com.linecorp.armeria.internal.nacos.NacosClient; | ||
import com.linecorp.armeria.internal.nacos.NacosClientBuilder; | ||
|
||
/** | ||
* A builder class for {@link NacosEndpointGroup}. | ||
* <h2>Examples</h2> | ||
* <pre>{@code | ||
* NacosEndpointGroup endpointGroup = NacosEndpointGroup.builder(nacosUri, "myService") | ||
* .build(); | ||
* WebClient client = WebClient.of(SessionProtocol.HTTPS, endpointGroup); | ||
* }</pre> | ||
*/ | ||
@UnstableApi | ||
public final class NacosEndpointGroupBuilder | ||
extends AbstractDynamicEndpointGroupBuilder<NacosEndpointGroupBuilder> | ||
implements NacosConfigSetters<NacosEndpointGroupBuilder> { | ||
|
||
private static final long DEFAULT_CHECK_INTERVAL_MILLIS = 10_000; | ||
|
||
private final NacosClientBuilder nacosClientBuilder; | ||
private EndpointSelectionStrategy selectionStrategy = EndpointSelectionStrategy.weightedRoundRobin(); | ||
private long registryFetchIntervalMillis = DEFAULT_CHECK_INTERVAL_MILLIS; | ||
|
||
NacosEndpointGroupBuilder(URI nacosUri, String serviceName) { | ||
super(Flags.defaultResponseTimeoutMillis()); | ||
nacosClientBuilder = NacosClient.builder(nacosUri, requireNonNull(serviceName, "serviceName")); | ||
} | ||
|
||
/** | ||
* Sets the {@link EndpointSelectionStrategy} of the {@link NacosEndpointGroup}. | ||
*/ | ||
public NacosEndpointGroupBuilder selectionStrategy(EndpointSelectionStrategy selectionStrategy) { | ||
this.selectionStrategy = requireNonNull(selectionStrategy, "selectionStrategy"); | ||
return this; | ||
} | ||
|
||
@Override | ||
public NacosEndpointGroupBuilder namespaceId(String namespaceId) { | ||
nacosClientBuilder.namespaceId(namespaceId); | ||
return this; | ||
} | ||
|
||
@Override | ||
public NacosEndpointGroupBuilder groupName(String groupName) { | ||
nacosClientBuilder.groupName(groupName); | ||
return this; | ||
} | ||
|
||
@Override | ||
public NacosEndpointGroupBuilder clusterName(String clusterName) { | ||
nacosClientBuilder.clusterName(clusterName); | ||
return this; | ||
} | ||
|
||
@Override | ||
public NacosEndpointGroupBuilder app(String app) { | ||
nacosClientBuilder.app(app); | ||
return this; | ||
} | ||
|
||
@Override | ||
public NacosEndpointGroupBuilder nacosApiVersion(String nacosApiVersion) { | ||
nacosClientBuilder.nacosApiVersion(nacosApiVersion); | ||
return this; | ||
} | ||
|
||
@Override | ||
public NacosEndpointGroupBuilder authorization(String username, String password) { | ||
nacosClientBuilder.authorization(username, password); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the healthy to retrieve only healthy instances from Nacos. | ||
* Make sure that your target endpoints are health-checked by Nacos before enabling this feature. | ||
* If not set, false is used by default. | ||
*/ | ||
public NacosEndpointGroupBuilder useHealthyEndpoints(boolean useHealthyEndpoints) { | ||
nacosClientBuilder.healthyOnly(useHealthyEndpoints); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the interval between fetching registry requests. | ||
* If not set, {@value #DEFAULT_CHECK_INTERVAL_MILLIS} milliseconds is used by default. | ||
*/ | ||
public NacosEndpointGroupBuilder registryFetchInterval(Duration registryFetchInterval) { | ||
requireNonNull(registryFetchInterval, "registryFetchInterval"); | ||
return registryFetchIntervalMillis(registryFetchInterval.toMillis()); | ||
} | ||
|
||
/** | ||
* Sets the interval between fetching registry requests. | ||
* If not set, {@value #DEFAULT_CHECK_INTERVAL_MILLIS} milliseconds is used by default. | ||
*/ | ||
public NacosEndpointGroupBuilder registryFetchIntervalMillis(long registryFetchIntervalMillis) { | ||
checkArgument(registryFetchIntervalMillis > 0, "registryFetchIntervalMillis: %s (expected: > 0)", | ||
registryFetchIntervalMillis); | ||
this.registryFetchIntervalMillis = registryFetchIntervalMillis; | ||
return this; | ||
} | ||
|
||
/** | ||
* Returns a newly-created {@link NacosEndpointGroup}. | ||
*/ | ||
public NacosEndpointGroup build() { | ||
return new NacosEndpointGroup(selectionStrategy, shouldAllowEmptyEndpoints(), selectionTimeoutMillis(), | ||
nacosClientBuilder.build(), registryFetchIntervalMillis); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
nacos/src/main/java/com/linecorp/armeria/client/nacos/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* Copyright 2024 LY Corporation | ||
* LY Corporation licenses this file to you 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: | ||
* | ||
* https://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. | ||
*/ | ||
|
||
/** | ||
* Nacos-based {@link com.linecorp.armeria.client.endpoint.EndpointGroup} implementation. | ||
*/ | ||
@NonNullByDefault | ||
@UnstableApi | ||
package com.linecorp.armeria.client.nacos; | ||
|
||
import com.linecorp.armeria.common.annotation.NonNullByDefault; | ||
import com.linecorp.armeria.common.annotation.UnstableApi; |
64 changes: 64 additions & 0 deletions
64
nacos/src/main/java/com/linecorp/armeria/common/nacos/NacosConfigSetters.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright 2024 LY Corporation | ||
* | ||
* LY Corporation licenses this file to you 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: | ||
* | ||
* https://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. | ||
*/ | ||
package com.linecorp.armeria.common.nacos; | ||
|
||
import com.linecorp.armeria.common.annotation.UnstableApi; | ||
import com.linecorp.armeria.internal.nacos.NacosClientBuilder; | ||
|
||
/** | ||
* Sets properties for building a Nacos client. | ||
*/ | ||
@UnstableApi | ||
public interface NacosConfigSetters<SELF extends NacosConfigSetters<SELF>> { | ||
|
||
/** | ||
* Sets the namespace ID to query or register instances. | ||
*/ | ||
SELF namespaceId(String namespaceId); | ||
|
||
/** | ||
* Sets the group name to query or register instances. | ||
*/ | ||
SELF groupName(String groupName); | ||
|
||
/** | ||
* Sets the cluster name to query or register instances. | ||
*/ | ||
SELF clusterName(String clusterName); | ||
|
||
/** | ||
* Sets the app name to query or register instances. | ||
*/ | ||
SELF app(String app); | ||
|
||
/** | ||
* Sets the specified Nacos's API version. | ||
* @param nacosApiVersion the version of Nacos API service, default: {@value | ||
* NacosClientBuilder#DEFAULT_NACOS_API_VERSION} | ||
*/ | ||
SELF nacosApiVersion(String nacosApiVersion); | ||
|
||
/** | ||
* Sets the username and password pair for Nacos's API. | ||
* Please refer to the | ||
* <a href=https://nacos.io/en-us/docs/v2/guide/user/auth.html>Nacos Authentication Document</a> | ||
* for more details. | ||
* | ||
* @param username the username for access Nacos API, default: {@code null} | ||
* @param password the password for access Nacos API, default: {@code null} | ||
*/ | ||
SELF authorization(String username, String password); | ||
} |
25 changes: 25 additions & 0 deletions
25
nacos/src/main/java/com/linecorp/armeria/common/nacos/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* Copyright 2024 LY Corporation | ||
* LY Corporation licenses this file to you 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: | ||
* | ||
* https://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. | ||
*/ | ||
|
||
/** | ||
* Various classes used internally. Anything in this package can be changed or removed at any time. | ||
*/ | ||
@NonNullByDefault | ||
@UnstableApi | ||
package com.linecorp.armeria.common.nacos; | ||
|
||
import com.linecorp.armeria.common.annotation.NonNullByDefault; | ||
import com.linecorp.armeria.common.annotation.UnstableApi; |
Oops, something went wrong.