-
Notifications
You must be signed in to change notification settings - Fork 900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Nacos Support #5409
base: main
Are you sure you want to change the base?
Add Nacos Support #5409
Conversation
…stead of QueryParams, making it easier to append additional parameters (e.g., accessToken).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overall direction looks great.
String accessToken; | ||
|
||
Integer tokenTtl; | ||
|
||
@Nullable | ||
Boolean globalAdmin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make them immutable.
String accessToken; | |
Integer tokenTtl; | |
@Nullable | |
Boolean globalAdmin; | |
private final String accessToken; | |
private final Integer tokenTtl; | |
@Nullable | |
private final Boolean globalAdmin; |
* A Nacos client that is responsible for | ||
* <a href="https://nacos.io/en-us/docs/auth.html">Nacos Authentication</a>. | ||
*/ | ||
final class LoginClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decorator pattern may hide the details of authentication.
How about making LoginClient
as a decorator to decorate WebClient
?
|
||
private static final String NACOS_ACCESS_TOKEN_CACHE_KEY = "NACOS_ACCESS_TOKEN_CACHE_KEY"; | ||
private final AsyncLoadingCache<String, LoginResult> tokenCache = Caffeine.newBuilder() | ||
.maximumSize(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional) It seems overkill to use Caffeine only for 1 element.
What do you think of creating a function that triggers loginInternal()
if access tokens are expired while invoking login()
?
If you find it more difficult than Caffeine approach, I'm okay to keep this as is.
final StringBuilder path = new StringBuilder("/").append(nacosApiVersion).append("/ns/instance/list?"); | ||
final QueryParamsBuilder paramsBuilder = NacosClientUtil | ||
.queryParamsBuilder(namespaceId, groupName, serviceName, clusterName, healthyOnly, app, null, | ||
null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be inefficient to create QueryParams
for the same parameters for all requests.
How about creating and caching the QueryParams
as a member field when QueryInstancesClient
is created?
.as(HttpEntity::content) | ||
.execute(); | ||
} else { | ||
return loginClient.login().thenCompose(accessToken -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned above, would you make loginClient
as a decorator?
@JsonIgnoreProperties(ignoreUnknown = true) | ||
private static final class Host { | ||
@Nullable | ||
String instanceId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto. Let's make them immutable.
Hide login-related context from other clients as much as possible.
…peatedly produced.
…sarily repeatedly produced.
…ll immutable parameters to properties.
a5e4a8c
to
0120fd7
Compare
Thank you for your review comments. I've applied your suggestions into the code.
The implementation to replace the Caffein, I made it as simple as possible. So now the login api can be called concurrently. Considering the usability of Thank you! |
return UnmodifiableFuture.completedFuture(cacheEntry.loginResult.accessToken); | ||
} | ||
|
||
return login().thenApply(loginResult -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may result in sending multiple login requests that are not what we really want.
If login()
is being performed, other requests can piggyback on the returned future.
By the way, I think an AsyncLoader
loading a value when it is expired or periodically would be useful in many places.
Created #5506
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we shouldn't let @KonaEspresso94 wait for too long for the PR to be merged but just use Caffeine internally and later replace the Caffeine usage with AsyncLoader
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know it would take this long. 😅 Let's leave that part as is. I will continue to review this PR tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your consideration. Following your comments, I have reverted the commit to use Caffeine instead of my implementation.
@KonaEspresso94 Gentle ping - looking forward to having this feature merged 🙇 |
… implementation." This reverts commit 0120fd7. excluding linting style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direction looks good to me 👍 Left some minor comments
* 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. | ||
*/ | ||
public class NacosEndpointGroup extends DynamicEndpointGroup { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Can NacosEndpointGroup
be package-private?
final class NacosEndpointGroup {
...
public static EndpointGroup of(URI
}
private final long registryFetchIntervalMillis; | ||
|
||
@Nullable | ||
private volatile ScheduledFuture<?> scheduledFuture; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit; I don't think we need to worry about race conditions when closing the EndpointGroup
if we pre-choose an EventExecutor
private final EventExecutor eventLoop;
...
NacosEndpointGroup(...) {
eventLoop = CommonPools.workerGroup().next();
}
...
protected void doCloseAsync(CompletableFuture<?> future) {
if (!eventExecutor.inEventLoop()) {
eventExecutor.execute(() -> doCloseAsync(future));
}
...
* sb.serverListener(listener); | ||
* }</pre> | ||
*/ | ||
public class NacosEndpointGroupBuilder extends AbstractDynamicEndpointGroupBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public class NacosEndpointGroupBuilder extends AbstractDynamicEndpointGroupBuilder { | |
public final class NacosEndpointGroupBuilder extends AbstractDynamicEndpointGroupBuilder<NacosEndpointGroupBuilder> { |
* Sets properties for building a Nacos client. | ||
*/ | ||
@UnstableApi | ||
public interface NacosConfigSetters { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional; What do you think of generalizing this interface and adding the related methods?
e.g.
public interface NacosConfigSetters<SELF extends NacosConfigSetters<SELF>> {
SELF nacosApiVersion(...)
}
.map(QueryInstancesClient::toEndpoint) | ||
.filter(Objects::nonNull) | ||
.collect(toImmutableList())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mind running an auto-indent for files in this PR in general one more time?
|
||
CompletableFuture<List<Endpoint>> endpoints() { | ||
return queryInstances() | ||
.thenApply(response -> response.data.hosts.stream() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question) Just to make sure, even if code != 0
(which I assume means successful) will data
and hosts
always exist?
I'm asking because there doesn't seem to defence against null
check
return nacosUri; | ||
} | ||
|
||
protected static int[] unusedPorts(int numPorts) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To other reviewers) I guess the ideal approach (binding to port 0) can be done for all similar tests at once. I think this code is fine if the CI passes reliably at least for now.
@Override | ||
public NacosEndpointGroupBuilder allowEmptyEndpoints(boolean allowEmptyEndpoints) { | ||
return (NacosEndpointGroupBuilder) super.allowEmptyEndpoints(allowEmptyEndpoints); | ||
} | ||
|
||
/** | ||
* Sets the timeout to wait until a successful {@link Endpoint} selection. | ||
* {@link Duration#ZERO} disables the timeout. | ||
* If unspecified, {@link Flags#defaultResponseTimeoutMillis()} is used by default. | ||
*/ | ||
@Override | ||
public NacosEndpointGroupBuilder selectionTimeout(Duration selectionTimeout) { | ||
return (NacosEndpointGroupBuilder) super.selectionTimeout(selectionTimeout); | ||
} | ||
|
||
/** | ||
* Sets the timeout to wait until a successful {@link Endpoint} selection. | ||
* {@code 0} disables the timeout. | ||
* If unspecified, {@link Flags#defaultResponseTimeoutMillis()} is used by default. | ||
*/ | ||
@Override | ||
public NacosEndpointGroupBuilder selectionTimeoutMillis(long selectionTimeoutMillis) { | ||
return (NacosEndpointGroupBuilder) super.selectionTimeoutMillis(selectionTimeoutMillis); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unrelated to your PR, but we can probably remove these lines now
…a single eventLoop, instead of using volatile.
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:
nacos
module.NacosClient
.Its internal implementation includes
LoginClient
for obtaining an accessToken through basic Nacos authentication,QueryInstancesClient
for querying service instances, andRegisterInstanceClient
for handling the registration and deregistration of instances.NacosEndpointGroup
andNacosUpdatingListener
.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.