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 callback when fetching a supergraph from Apollo Uplink fails #1812

Merged
merged 38 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
5237678
Add callback option when polling Uplink fails
benweatherman Apr 30, 2022
d619e67
Simplify setting compositionId
benweatherman Apr 30, 2022
d7979fa
Merge branch 'main' into weatherman/uplink-fallback
benweatherman May 9, 2022
aca3709
Merge branch 'main' into weatherman/uplink-fallback
benweatherman May 11, 2022
d8e10e3
Reorganize config, add tests
benweatherman May 17, 2022
b0edc7a
Merge branch 'main' into weatherman/uplink-fallback
benweatherman May 17, 2022
f83a6b1
Merge branch 'main' into weatherman/uplink-fallback
benweatherman May 23, 2022
cc7b6da
Rename UplinkFetcher -> UplinkSupergraphManager
benweatherman May 23, 2022
9c8ac97
Fix string in tests
benweatherman May 23, 2022
ae733e1
Allow gateway.stop from initialized/failed states
benweatherman May 24, 2022
e5c0186
Remove superfluous log
benweatherman May 24, 2022
0d48dcb
Shuffle and consolidate tests
benweatherman May 24, 2022
b1e699b
Add tests for setting uplink URLs via gateway constructor
benweatherman May 24, 2022
a06d71b
What's in a name?
benweatherman May 24, 2022
f2457ae
Merge branch 'main' into weatherman/uplink-fallback
benweatherman May 27, 2022
8d79770
Merge branch 'main' into weatherman/uplink-fallback
benweatherman May 27, 2022
b78c5a2
Improve test names and imports
benweatherman May 27, 2022
bbe47e8
Merge branch 'weatherman/uplink-fallback' of github.com:apollographql…
benweatherman May 27, 2022
dbe1fa9
No need to add types to function args
benweatherman May 27, 2022
c0d2986
Never `call` Saul (or onFailureToFetchSupergraphSdl)
benweatherman May 27, 2022
28d6291
More thorough tests
benweatherman May 27, 2022
9cfa978
Don't need to nullify gateway
benweatherman May 27, 2022
8733932
Simplify uplinkEndpoints
benweatherman May 27, 2022
dc2c87e
fall back to fallbackPollInterval
benweatherman May 27, 2022
9dd69e5
Use `.bind` to ensure the correct `this`
benweatherman May 27, 2022
c62b4a0
Test for specific errors when returning bad data
benweatherman May 27, 2022
acf140d
No need to muck with `keyHash`
benweatherman May 27, 2022
97d4a7c
Merge branch 'main' into weatherman/uplink-fallback
benweatherman Jun 1, 2022
e61c633
Refactor into 2 separate callbacks for init and post-init
benweatherman Jun 1, 2022
af21123
Merge branch 'main' into weatherman/uplink-fallback
benweatherman Jun 2, 2022
fb2bbc9
Export types
benweatherman Jun 2, 2022
80c3512
Small tweaks from code review
benweatherman Jun 9, 2022
0a58020
Merge branch 'main' into weatherman/uplink-fallback
benweatherman Jun 15, 2022
e0c132b
Allow setting `initialMaxRetries`
benweatherman Jun 15, 2022
b830e70
Add comment for magic math
benweatherman Jun 15, 2022
9807c73
Rejigger types to remove superfluous undefined
benweatherman Jun 15, 2022
4b52be2
Moar type reshuffling
benweatherman Jun 15, 2022
0434ecd
Add `mostRecentSuccessfulFetchAt`
benweatherman Jun 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Run prettier
f2f1d6041cd67c67fdacabe0570fe2ce642c052c
4 changes: 2 additions & 2 deletions codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ schema: [
"https://outofbandreporter.api.apollographql.com/",
]
documents:
- gateway-js/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts
- gateway-js/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts
- gateway-js/src/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.ts
- gateway-js/src/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.ts
generates:
gateway-js/src/__generated__/graphqlTypes.ts:
plugins:
Expand Down
4 changes: 4 additions & 0 deletions gateway-js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The Federation v0.x equivalent for this package can be found [here](https://github.com/apollographql/federation/blob/version-0.x/gateway-js/CHANGELOG.md) on the `version-0.x` branch of this repo.

## vNext

- Add callback when fetching a supergraph from Apollo Uplink fails [PR #1812](https://github.com/apollographql/federation/pull/1812).

## 2.0.5

- Fix bug with unsatisfiable query branch when handling federation 1 supergraph [PR #1908](https://github.com/apollographql/federation/pull/1908).
Expand Down
43 changes: 0 additions & 43 deletions gateway-js/src/__tests__/integration/configuration.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import gql from 'graphql-tag';
import http from 'http';
import mockedEnv from 'mocked-env';
import type { Logger } from '@apollo/utils.logger';
import { ApolloGateway } from '../..';
import {
mockSdlQuerySuccess,
mockSupergraphSdlRequestSuccess,
mockApolloConfig,
mockCloudConfigUrl1,
mockCloudConfigUrl2,
mockCloudConfigUrl3,
} from './nockMocks';
import { getTestingSupergraphSdl } from '../execution-utils';
import { fixtures, Fixture } from 'apollo-federation-integration-testsuite';
Expand Down Expand Up @@ -320,46 +317,6 @@ describe('gateway config / env behavior', () => {
);
});
});

describe('schema config delivery endpoint configuration', () => {
benweatherman marked this conversation as resolved.
Show resolved Hide resolved
it('A code config overrides the env variable', async () => {
cleanUp = mockedEnv({
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: 'env-config',
});

const config = {
logger,
uplinkEndpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2, mockCloudConfigUrl3],
};
gateway = new ApolloGateway(config);

expect(gateway['getUplinkEndpoints'](config)).toEqual([
mockCloudConfigUrl1,
mockCloudConfigUrl2,
mockCloudConfigUrl3,
]);

gateway = null;
});
});

describe('deprecated schema config delivery endpoint configuration', () => {
it('A code config overrides the env variable', async () => {
cleanUp = mockedEnv({
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: 'env-config',
});

const config = {
logger,
schemaConfigDeliveryEndpoint: 'code-config',
};
gateway = new ApolloGateway(config);

expect(gateway['getUplinkEndpoints'](config)).toEqual(['code-config']);

gateway = null;
});
});
});

describe('deprecation warnings', () => {
Expand Down
289 changes: 289 additions & 0 deletions gateway-js/src/__tests__/integration/managed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
import mockedEnv from 'mocked-env';

import { ApolloGateway, UplinkSupergraphManager } from '@apollo/gateway';
import { ApolloServer } from 'apollo-server';
import { ApolloServerPluginUsageReportingDisabled } from 'apollo-server-core';

import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
import {
mockSupergraphSdlRequestSuccess,
graphRef,
apiKey,
mockSupergraphSdlRequest,
} from '../integration/nockMocks';
import { getTestingSupergraphSdl } from '../execution-utils';

let gateway: ApolloGateway | undefined;
let server: ApolloServer | undefined;
let cleanUp: (() => void) | undefined;

beforeEach(() => {
nockBeforeEach();
});

afterEach(async () => {
nockAfterEach();

if (server) {
await server.stop();
server = undefined;
}

if (gateway) {
await gateway.stop();
gateway = undefined;
}

if (cleanUp) {
cleanUp();
cleanUp = undefined;
}
});

const logger = {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
};

describe('minimal gateway', () => {
it('uses managed federation', async () => {
cleanUp = mockedEnv({
APOLLO_KEY: apiKey,
APOLLO_GRAPH_REF: graphRef,
});
mockSupergraphSdlRequestSuccess({ url: /.*?apollographql.com/ });

gateway = new ApolloGateway({ logger });
server = new ApolloServer({
gateway,
plugins: [ApolloServerPluginUsageReportingDisabled()],
});
await server.listen({ port: 0 });
expect(gateway.supergraphManager).toBeInstanceOf(UplinkSupergraphManager);
});

it('fetches from provided `uplinkEndpoints`', async () => {
cleanUp = mockedEnv({
APOLLO_KEY: apiKey,
APOLLO_GRAPH_REF: graphRef,
});

const uplinkEndpoint = 'https://example.com';
mockSupergraphSdlRequestSuccess({ url: uplinkEndpoint });

gateway = new ApolloGateway({ logger, uplinkEndpoints: [uplinkEndpoint] });
server = new ApolloServer({
gateway,
plugins: [ApolloServerPluginUsageReportingDisabled()],
});
await server.listen({ port: 0 });
expect(gateway.supergraphManager).toBeInstanceOf(UplinkSupergraphManager);
const uplinkManager = gateway.supergraphManager as UplinkSupergraphManager;
expect(uplinkManager.uplinkEndpoints).toEqual([uplinkEndpoint]);
});

it('fetches from (deprecated) provided `schemaConfigDeliveryEndpoint`', async () => {
cleanUp = mockedEnv({
APOLLO_KEY: apiKey,
APOLLO_GRAPH_REF: graphRef,
});

const schemaConfigDeliveryEndpoint = 'https://example.com';
mockSupergraphSdlRequestSuccess({ url: schemaConfigDeliveryEndpoint });

gateway = new ApolloGateway({ logger, schemaConfigDeliveryEndpoint });
server = new ApolloServer({
gateway,
plugins: [ApolloServerPluginUsageReportingDisabled()],
});
await server.listen({ port: 0 });
expect(gateway.supergraphManager).toBeInstanceOf(UplinkSupergraphManager);
const uplinkManager = gateway.supergraphManager as UplinkSupergraphManager;
expect(uplinkManager.uplinkEndpoints).toEqual([
schemaConfigDeliveryEndpoint,
]);
});
});

describe('Managed gateway with explicit UplinkSupergraphManager', () => {
it('waits for supergraph schema to load', async () => {
mockSupergraphSdlRequestSuccess({ url: /.*?apollographql.com/ });

gateway = new ApolloGateway({
logger,
supergraphSdl: new UplinkSupergraphManager({
apiKey,
graphRef,
logger,
}),
});
await expect(gateway.load()).resolves.not.toThrow();
});

it('invokes callback if uplink throws an error during init', async () => {
mockSupergraphSdlRequest(null, /.*?apollographql.com/).reply(500);

const supergraphSchema = getTestingSupergraphSdl();
let hasFired;
gateway = new ApolloGateway({
logger,
supergraphSdl: new UplinkSupergraphManager({
apiKey,
graphRef,
logger,
maxRetries: 0,
fallbackPollIntervalInMs: 0,
async onFailureToFetchSupergraphSdlDuringInit() {
hasFired = true;
return supergraphSchema;
},
}),
});

benweatherman marked this conversation as resolved.
Show resolved Hide resolved
await expect(gateway.load()).resolves.not.toThrow();
expect(gateway.__testing().supergraphSdl).toBe(supergraphSchema);
expect(hasFired).toBeTruthy();
});

it('invokes callback if uplink throws an error after init', async () => {
// This is kinda wonky to read: we're responding the first time with success, then the next fetch should fail
mockSupergraphSdlRequestSuccess({ url: /.*?apollographql.com/ })
.post('/')
.reply(500);

const supergraphSchema = getTestingSupergraphSdl();
let hasFired;
gateway = new ApolloGateway({
logger,
supergraphSdl: new UplinkSupergraphManager({
apiKey,
graphRef,
logger,
maxRetries: 0,
fallbackPollIntervalInMs: 0,
async onFailureToFetchSupergraphSdlAfterInit() {
hasFired = true;
return supergraphSchema;
},
}),
});

await expect(gateway.load()).resolves.not.toThrow();
expect(hasFired).toBeFalsy();

const uplinkManager = gateway.supergraphManager as UplinkSupergraphManager;
await uplinkManager.nextFetch();

expect(hasFired).toBeTruthy();
});

it.each([
['x', 'Syntax Error: Unexpected Name "x".'],
['', 'Syntax Error: Unexpected <EOF>.'],
[' ', 'Syntax Error: Unexpected <EOF>.'],
['type Query {hi: String}', 'Invalid supergraph: must be a core schema'],
])(
'throws if invalid supergraph schema returned from callback during init: %p',
async (schemaText, expectedMessage) => {
mockSupergraphSdlRequest(null, /.*?apollographql.com/).reply(500);

gateway = new ApolloGateway({
logger,
supergraphSdl: new UplinkSupergraphManager({
apiKey,
graphRef,
logger,
maxRetries: 0,
fallbackPollIntervalInMs: 0,
async onFailureToFetchSupergraphSdlDuringInit() {
return schemaText;
},
}),
});

await expect(gateway.load()).rejects.toThrowError(expectedMessage);
},
);

it.each([
['x', 'Syntax Error: Unexpected Name "x".'],
[' ', 'Syntax Error: Unexpected <EOF>.'],
['type Query {hi: String}', 'Invalid supergraph: must be a core schema'],
])(
'throws if invalid supergraph schema returned from callback after init: %p',
async (schemaText, expectedMessage) => {
// This is kinda wonky to read: we're responding the first time with success, then the next fetch should fail
mockSupergraphSdlRequestSuccess({ url: /.*?apollographql.com/ })
.post('/')
.reply(500);

let hasFired;
gateway = new ApolloGateway({
logger,
supergraphSdl: new UplinkSupergraphManager({
apiKey,
graphRef,
logger,
maxRetries: 0,
fallbackPollIntervalInMs: 0,
async onFailureToFetchSupergraphSdlAfterInit() {
hasFired = true;
return schemaText;
},
}),
});

await expect(gateway.load()).resolves.not.toThrow();
expect(hasFired).toBeFalsy();

const uplinkManager =
gateway.supergraphManager as UplinkSupergraphManager;
await uplinkManager.nextFetch();

expect(hasFired).toBeTruthy();
expect(logger.error).toBeCalledWith(
`UplinkSupergraphManager failed to update supergraph with the following error: ${expectedMessage}`,
);
},
);

it.each([null, ''])(
'uses existing supergraph schema is false-y value returned from callback after init: %p',
async (schemaText) => {
// This is kinda wonky to read: we're responding the first time with success, then the next fetch should fail
mockSupergraphSdlRequestSuccess({ url: /.*?apollographql.com/ })
.post('/')
.reply(500);

let hasFired;
gateway = new ApolloGateway({
logger,
supergraphSdl: new UplinkSupergraphManager({
apiKey,
graphRef,
logger,
maxRetries: 0,
fallbackPollIntervalInMs: 0,
async onFailureToFetchSupergraphSdlAfterInit() {
hasFired = true;
return schemaText;
},
}),
});

await expect(gateway.load()).resolves.not.toThrow();
expect(hasFired).toBeFalsy();

const uplinkManager =
gateway.supergraphManager as UplinkSupergraphManager;
await uplinkManager.nextFetch();

expect(hasFired).toBeTruthy();

const supergraphSchema = getTestingSupergraphSdl();
expect(gateway.__testing().supergraphSdl).toBe(supergraphSchema);
},
);
});
Loading