Skip to content
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

Provide a way to specify a fallback for Central Dogma server endpoints #959

Open
jrhee17 opened this issue Jun 3, 2024 · 0 comments
Open
Milestone

Comments

@jrhee17
Copy link
Contributor

jrhee17 commented Jun 3, 2024

CentralDogma supports multi-idc replication. However in usual cases we don't encourage sending requests to multiple IDCs since inter-replica traffic may increase resulting in slower latency. For this reason, users currently rely on manual operation when the primary data center may go down.

If there are no available endpoints in the primary IDC, we may want to let users specify a fallback EndpointGroup so that no manual intervention is required.

Currently, users can create a CentralDogma client with endpoints using the following APIs.

/**
* Sets the paths to look for to read the {@code .json} file that contains the client profiles.
* The paths are tried in the order of iteration. The default value of this property is
* <code>[ {@value #TEST_PROFILE_RESOURCE_PATH}, {@value #PROFILE_RESOURCE_PATH} ]</code>, which means
* the builder will check if {@value #TEST_PROFILE_RESOURCE_PATH} exists first and will try
* {@value #PROFILE_RESOURCE_PATH} only if {@value #TEST_PROFILE_RESOURCE_PATH} is missing.
*/
public final B profileResources(String... paths) {
return profileResources(ImmutableList.copyOf(requireNonNull(paths, "paths")));
}
/**
* Sets the paths to look for to read the {@code .json} file that contains the client profiles.
* The paths are tried in the order of iteration. The default value of this property is
* <code>[ {@value #TEST_PROFILE_RESOURCE_PATH}, {@value #PROFILE_RESOURCE_PATH} ]</code>, which means
* the builder will check if {@value #TEST_PROFILE_RESOURCE_PATH} exists first and will try
* {@value #PROFILE_RESOURCE_PATH} only if {@value #TEST_PROFILE_RESOURCE_PATH} is missing.
*/
public final B profileResources(Iterable<String> paths) {
final List<String> newPaths = ImmutableList.copyOf(requireNonNull(paths, "paths"));
checkArgument(!newPaths.isEmpty(), "paths is empty.");
checkState(selectedProfile == null, "profileResources cannot be set after profile() is called.");
profileResourcePaths = newPaths;
return self();
}
/**
* Adds the host names (or IP addresses) and the port numbers of the Central Dogma servers loaded from the
* client profile resources. When more than one profile is matched, the last matching one will be used. See
* <a href="https://line.github.io/centraldogma/client-java.html#using-client-profiles">Using client
* profiles</a> for more information.
*
* @param profiles the list of profile names
*
* @throws IllegalArgumentException if failed to load any hosts from all the specified profiles
*/
public final B profile(String... profiles) {
requireNonNull(profiles, "profiles");
return profile(ImmutableList.copyOf(profiles));
}
/**
* Adds the host names (or IP addresses) and the port numbers of the Central Dogma servers loaded from the
* client profile resources. When more than one profile is matched, the last matching one will be used. See
* <a href="https://line.github.io/centraldogma/client-java.html#using-client-profiles">Using client
* profiles</a> for more information.
*
* @param profiles the list of profile names
*
* @throws IllegalArgumentException if failed to load any hosts from all the specified profiles
*/
public final B profile(ClassLoader classLoader, String... profiles) {
requireNonNull(profiles, "profiles");
return profile(classLoader, ImmutableList.copyOf(profiles));
}
/**
* Adds the host names (or IP address) and the port numbers of the Central Dogma servers loaded from the
* client profile resources. When more than one profile is matched, the last matching one will be used. See
* <a href="https://line.github.io/centraldogma/client-java.html#using-client-profiles">Using client
* profiles</a> for more information.
*
* @param profiles the list of profile names
*
* @throws IllegalArgumentException if failed to load any hosts from all the specified profiles
*/
public final B profile(Iterable<String> profiles) {
final ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl != null) {
profile(ccl, profiles);
} else {
profile(getClass().getClassLoader(), profiles);
}
return self();
}
/**
* Adds the host names (or IP address) and the port numbers of the Central Dogma servers loaded from the
* client profile resources. When more than one profile is matched, the last matching one will be used. See
* <a href="https://line.github.io/centraldogma/client-java.html#using-client-profiles">Using client
* profiles</a> for more information.
*
* @param profiles the list of profile names
*
* @throws IllegalArgumentException if failed to load any hosts from all the specified profiles
*/
public final B profile(ClassLoader classLoader, Iterable<String> profiles) {
requireNonNull(classLoader, "classLoader");
requireNonNull(profiles, "profiles");
checkState(selectedProfile == null, "profile cannot be loaded more than once.");
checkState(hosts.isEmpty(), "profile() and host() cannot be used together.");
final Map<String, ClientProfile> availableProfiles = new HashMap<>();
try {
final List<URL> resourceUrls = findProfileResources(classLoader);
checkState(!resourceUrls.isEmpty(), "failed to find any of: ", profileResourcePaths);
for (URL resourceUrl : resourceUrls) {
final List<ClientProfile> availableProfileList =
new ObjectMapper().readValue(resourceUrl, new TypeReference<List<ClientProfile>>() {});
// Collect all profiles checking the profiles ignoring the duplicate profile names.
availableProfileList.forEach(profile -> {
final String name = profile.name();
final ClientProfile existingProfile = availableProfiles.get(name);
if (existingProfile == null || existingProfile.priority() < profile.priority()) {
// Not a duplicate or higher priority
availableProfiles.put(name, profile);
}
});
}
} catch (IOException e) {
throw new IllegalStateException("failed to load: " + PROFILE_RESOURCE_PATH, e);
}
final List<String> reversedProfiles = reverse(profiles);
checkArgument(!reversedProfiles.isEmpty(), "profiles is empty.");
for (String candidateName : reversedProfiles) {
checkNotNull(candidateName, "profiles contains null: %s", profiles);
final ClientProfile candidate = availableProfiles.get(candidateName);
if (candidate == null) {
continue;
}
final ImmutableSet.Builder<InetSocketAddress> newHostsBuilder = ImmutableSet.builder();
candidate.hosts().stream()
.filter(e -> (useTls ? "https" : "http").equals(e.protocol()))
.forEach(e -> newHostsBuilder.add(newEndpoint(e.host(), e.port())));
final ImmutableSet<InetSocketAddress> newHosts = newHostsBuilder.build();
if (!newHosts.isEmpty()) {
selectedProfile = candidateName;
hosts = newHosts;
return self();
}
}
throw new IllegalArgumentException("no profile matches: " + profiles);
}

I propose that we allow users to specify a fallback EndpointGroup via the following APIs.

class AbstractCentralDogmaBuilder {
  public final B fallbackHost(String host, int port) {}
  public final B fallbackProfile(String... profiles) {}
  public final B fallbackProfileResources(String... paths) {}
}

Note that this can be added in parallel with xDS related features.

ref: @imasahiro

@jrhee17 jrhee17 added this to the 0.66.0 milestone Jun 3, 2024
@minwoox minwoox modified the milestones: 0.66.0, 0.67.0 Jun 13, 2024
@minwoox minwoox modified the milestones: 0.67.0, 0.67.1 Jul 10, 2024
@minwoox minwoox modified the milestones: 0.67.1, 0.67.2, 0.69.0 Jul 23, 2024
@ikhoon ikhoon modified the milestones: 0.68.1, 0.69.0, 0.70.0 Aug 16, 2024
@minwoox minwoox modified the milestones: 0.70.0, 0.71.0 Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants