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

Add an API compatibility check to Nessie clients #6818

Merged
merged 18 commits into from
May 17, 2023

Conversation

adutra
Copy link
Contributor

@adutra adutra commented May 12, 2023

... and change default URI of GC tool to a v2 URI.

Fixes #6805.

@snazy
Copy link
Member

snazy commented May 12, 2023

Can you move the fix for the GC tool from this PR?

Copy link
Member

@dimas-b dimas-b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR LGTM overall. Did you have a chance to test how the new error manifests on the Iceberg side (e.g. in Spark) when the Nessie Catalog URI is misconfigured? We may need a corresponding change in the Iceberg NessieCatalog.

// FIXME we need a better way to detect API vs URI prefix mismatches.
int serverApiVersion = specVersion != null && !specVersion.isEmpty() ? 2 : 1;
if (clientApiVersion != serverApiVersion) {
throw new IllegalArgumentException(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about using NessieServiceException instead? NessieServiceException is often reported when the URL is misconfigured in such as way that Nessie end points are not even reachable... Would it make sense to reuse it for this failure mode too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an idea, although here the error is raised on the client side. What would you say if we create a dedicated NessieApiCompatibilityException instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NessieServiceException is also raised on the client side.

+1 to NessieApiCompatibilityException

@adutra adutra marked this pull request as draft May 12, 2023 16:40
@adutra
Copy link
Contributor Author

adutra commented May 12, 2023

Switching to draft, sorry, because unsurprisingly, these changes are breaking a few compatibility tests, especially ITOlderServers. Obviously I could fix them by simply disabling the compatibility check, but I would like to first understand why and where the tests are failing.

@adutra
Copy link
Contributor Author

adutra commented May 12, 2023

Did you have a chance to test how the new error manifests on the Iceberg side (e.g. in Spark) when the Nessie Catalog URI is misconfigured? We may need a corresponding change in the Iceberg NessieCatalog.

Very good point, I will check.

@adutra
Copy link
Contributor Author

adutra commented May 12, 2023

So, the compatibility tests were failing because of a combination of factors:

  • The NessieApi instances are eagerly injected into the test classes, and so when e.g. the server doesn't support V2 at all, the apiV2 field cannot be populated and the whole test class fails.
  • A more interesting failure is e.g. when the server does support V2, but returns null for specVersion. This actually happens for many server versions, I believe because specVersion is a rather recent addition.

I temporarily fixed the issues by disabling the client compat check.

But this unfortunately uncovers a deeper problem: specVersion is not a good indicator of whether the server is replying with v1 or v2. I will think about this more and see if there is a better alternative.

@adutra
Copy link
Contributor Author

adutra commented May 12, 2023

OK, I am proposing something different: test what the /trees/tree/{defaultBranch} endpoint returns. If it returns 200, then the server used v1 unambiguously, since this path cannot exist in v2. I could have used /trees/tree, but in the unlikely case where the default branch is named tree, this path is ambiguous.

@adutra adutra marked this pull request as ready for review May 12, 2023 22:14
@dimas-b
Copy link
Member

dimas-b commented May 12, 2023

tree/main is also a valid branch name 🤔

@dimas-b
Copy link
Member

dimas-b commented May 12, 2023

How about returning the "effective" API version from the /config endpoint (existing NessieConfiguration object or something new)?

return httpClient.newRequest().path("config").get().readEntity(NessieConfiguration.class);
}

private static int fetchActualServerApiVersion(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check requires that authz works for you on that branch.
Also not sure whether it's really worth to run some arbitrary request as a guess implying an API version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to return the "effective" version (the one that was used on the server side) via NessieConfiguration

}

private static NessieConfiguration fetchConfig(HttpClient httpClient) {
return httpClient.newRequest().path("config").get().readEntity(NessieConfiguration.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd actually have to read it into a Map<String, Object>, because newer servers may return more elements, which would breal this check.

public static void check(int clientApiVersion, HttpClient httpClient)
throws NessieApiCompatibilityException {
NessieConfiguration config = fetchConfig(httpClient);
int minServerApiVersion = config.getMinSupportedApiVersion();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If min/max are not present (see below), then you can safely assume V1.
If those are present, you can safely assume that V2.
No need for any other checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true, unfortunately.

Min and max were introduced in Nessie 0.55.0, way after the introduction of v2 in 0.46.0. So all servers between [0.46.0,0.55.0[ reply with the same payload for both versions, e.g. 0.47.0:

  • v1: {"defaultBranch":"main","maxSupportedApiVersion":2}
  • v2: {"defaultBranch":"main","maxSupportedApiVersion":2}

Starting with 0.55.0, indeed the response is different:

  • v1: {"defaultBranch":"main","maxSupportedApiVersion":2}
  • v2: {"defaultBranch":"main","minSupportedApiVersion":1,"maxSupportedApiVersion":2,"specVersion":"2.0-beta.1"}

So this is not a good indicator of the actual server version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v2 is still in beta, we can somewhat ignore older versions w/ v2.

@@ -323,12 +337,18 @@ public <API extends NessieApi> API build(Class<API> apiVersion) {
if (apiVersion.isAssignableFrom(HttpApiV1.class)) {
builder.setJsonView(Views.V1.class);
HttpClient httpClient = builder.build();
if (enableApiCompatibilityCheck) {
NessieApiCompatibility.check(1, httpClient);
}
return (API) new HttpApiV1(new NessieHttpClient(httpClient));
}

if (apiVersion.isAssignableFrom(HttpApiV2.class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this if up (minor nit)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this would break things: HttpApiV2 is a subclass of NessieApiV1.

@Test
void testApiCompatibility() {
// Good cases
assertThatCode(() -> testConfig(NessieApiV1.class, 1, 1, 1, true)).doesNotThrowAnyException();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Candidate for softly-assertions (soft.assertThatCode...)

@adutra
Copy link
Contributor Author

adutra commented May 14, 2023

tree/main is also a valid branch name 🤔

Hmm right. There will always be a risk of ambiguity it seems.

This check requires that authz works for you on that branch.

That is true as well, I didn't think of it so far. This may be a blocker.

How about returning the "effective" API version from the /config endpoint

That is imho the proper way to solve this, but keep in mind that this won't solve the problem for existing Nessie versions, only for future ones.

I think this PR is getting stuck. As it stands, I see 2 options going forward:

  1. Give up on this work and just acknowledge that there is no good way to detect URI vs API version mismatches.
  2. Implement "returning the effective API version from the /config endpoint" and only run the compatibility check for versions that implemented this endpoint.

Wdyt?

@snazy
Copy link
Member

snazy commented May 15, 2023

  1. Implement "returning the effective API version from the /config endpoint" and only run the compatibility check for versions that implemented this endpoint.

That could work.

dimas-b
dimas-b previously approved these changes May 16, 2023
Copy link
Member

@dimas-b dimas-b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest approach (actual API version in NessieConfiguration) looks good to me.

@Override
void getConfig() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe rename to getConfigV1() for clarity?

.hasMessageContaining(
expectation == Expectation.MISMATCH
? "mismatch"
: expectation == Expectation.TOO_OLD ? "too old" : "too new")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: put too old, etc. into enum object fields in Expectation and use smth like expectation.getExpectedText()

snazy
snazy previously approved these changes May 17, 2023
@adutra adutra dismissed stale reviews from snazy and dimas-b via 52b90ec May 17, 2023 16:58
@adutra adutra merged commit cff1788 into projectnessie:main May 17, 2023
@adutra adutra deleted the gc-tool branch May 17, 2023 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GC Tool is mixing v1 and v2 URLs
3 participants