diff --git a/runner/src/provisioner/provisioner.test.ts b/runner/src/provisioner/provisioner.test.ts index cabb7e59f..c9ca564ae 100644 --- a/runner/src/provisioner/provisioner.test.ts +++ b/runner/src/provisioner/provisioner.test.ts @@ -257,7 +257,15 @@ describe('Provisioner', () => { await expect(provisioner.provisionUserApi(indexerConfig)).rejects.toThrow('Failed to provision endpoint: Failed to setup partitioned logs table: Failed to schedule log partition jobs: some error'); }); - it('provisions and tracks logs and metadata table once, adds permissions twice', async () => { + it('provisions logs and metadata tables once', async () => { + hasuraClient.getTableNames = jest.fn().mockReturnValueOnce(['blocks']).mockReturnValue(['blocks', '__logs', '__metadata']); + await provisioner.provisionLogsAndMetadataIfNeeded(indexerConfig); + expect(hasuraClient.executeSqlOnSchema).toBeCalledTimes(2); + expect(cronPgClient.query).toBeCalledTimes(2); + expect(userPgClientQuery).toBeCalledTimes(2); + }); + + it('tracks logs and metadata table once, adds permissions twice', async () => { hasuraClient.getTrackedTablePermissions = jest.fn().mockReturnValueOnce([]).mockReturnValueOnce([ { table: { @@ -279,27 +287,23 @@ describe('Provisioner', () => { } ]); hasuraClient.getTableNames = jest.fn().mockReturnValueOnce(['blocks']).mockReturnValueOnce(['blocks', '__logs', '__metadata']); - await provisioner.provisionLogsAndMetadataIfNeeded(indexerConfig); - await provisioner.provisionLogsAndMetadataIfNeeded(indexerConfig); + await provisioner.ensureConsistentHasuraState(indexerConfig); + await provisioner.ensureConsistentHasuraState(indexerConfig); - expect(hasuraClient.executeSqlOnSchema).toBeCalledTimes(2); - expect(cronPgClient.query).toBeCalledTimes(2); expect(hasuraClient.trackTables).toBeCalledTimes(1); expect(hasuraClient.addPermissionsToTables).toBeCalledTimes(2); }); - it('provision logs and metadata table function caches result', async () => { + it('ensuring consistent state caches result', async () => { hasuraClient.getTrackedTablePermissions = jest.fn().mockReturnValue([ generateTableConfig('morgs_near_test_function', 'blocks', 'morgs_near'), generateTableConfig('morgs_near_test_function', '__logs', 'morgs_near'), generateTableConfig('morgs_near_test_function', '__metadata', 'morgs_near'), ]); hasuraClient.getTableNames = jest.fn().mockReturnValue(['blocks', '__logs', '__metadata']); - await provisioner.provisionLogsAndMetadataIfNeeded(indexerConfig); - await provisioner.provisionLogsAndMetadataIfNeeded(indexerConfig); + await provisioner.ensureConsistentHasuraState(indexerConfig); + await provisioner.ensureConsistentHasuraState(indexerConfig); - expect(hasuraClient.executeSqlOnSchema).not.toBeCalled(); - expect(cronPgClient.query).not.toBeCalled(); expect(hasuraClient.trackTables).not.toBeCalled(); expect(hasuraClient.addPermissionsToTables).not.toBeCalled(); }); diff --git a/runner/src/provisioner/provisioner.ts b/runner/src/provisioner/provisioner.ts index 4503fa43f..f3d0b5513 100644 --- a/runner/src/provisioner/provisioner.ts +++ b/runner/src/provisioner/provisioner.ts @@ -51,6 +51,7 @@ export default class Provisioner { tracer: Tracer = trace.getTracer('queryapi-runner-provisioner'); #hasBeenProvisioned: Record> = {}; #hasLogsMetadataBeenProvisioned: Record> = {}; + #hasuraConsistentState: Record> = {}; constructor ( private readonly hasuraClient: HasuraClient = new HasuraClient(), @@ -82,6 +83,11 @@ export default class Provisioner { this.#hasBeenProvisioned[accountId][functionName] = true; } + private setConsistentState (accountId: string, functionName: string): void { + this.#hasuraConsistentState[accountId] ??= {}; + this.#hasuraConsistentState[accountId][functionName] = true; + } + async createDatabase (name: string): Promise { await this.adminDefaultPgClient.query(this.pgFormat('CREATE DATABASE %I', name)); } @@ -236,8 +242,6 @@ export default class Provisioner { } const logsTable = '__logs'; const metadataTable = '__metadata'; - const permissionsToAdd: HasuraPermission[] = ['select', 'insert', 'update', 'delete']; - let provisioningComplete = false; await wrapError( async () => { @@ -251,18 +255,38 @@ export default class Provisioner { await this.createMetadataTable(indexerConfig.databaseName(), indexerConfig.schemaName()); tableNames.push(metadataTable); } + }, + 'Failed logs and metadata provisioning' + ); + + this.#hasLogsMetadataBeenProvisioned[indexerConfig.accountId] ??= {}; + this.#hasLogsMetadataBeenProvisioned[indexerConfig.accountId][indexerConfig.functionName] = true; + } + + /** + * Tracks and adds permissions to any Postgres tables successfully created in schema and which lack tracking and/or permissions. + * + * */ + async ensureConsistentHasuraState (indexerConfig: IndexerConfig): Promise { + if (this.#hasuraConsistentState[indexerConfig.accountId]?.[indexerConfig.functionName]) { + return; + } + await wrapError( + async () => { + const tableNamesToCheck = await this.getTableNames(indexerConfig.schemaName(), indexerConfig.databaseName()); + const permissionsToAdd: HasuraPermission[] = ['select', 'insert', 'update', 'delete']; const hasuraTablesMetadata = await this.getTrackedTablesWithPermissions(indexerConfig); - const untrackedTables = this.getUntrackedTables(tableNames, hasuraTablesMetadata); + const untrackedTables = this.getUntrackedTables(tableNamesToCheck, hasuraTablesMetadata); const tablesWithoutPermissions = this.getTablesWithoutPermissions( indexerConfig.hasuraRoleName(), - tableNames, + tableNamesToCheck, hasuraTablesMetadata, permissionsToAdd ); if (untrackedTables.length === 0 && tablesWithoutPermissions.length === 0) { - provisioningComplete = true; + this.setConsistentState(indexerConfig.accountId, indexerConfig.functionName); } else { if (untrackedTables.length > 0) { await this.trackTables(indexerConfig.schemaName(), untrackedTables, indexerConfig.databaseName()); @@ -271,14 +295,7 @@ export default class Provisioner { await this.addPermissionsToTables(indexerConfig, tablesWithoutPermissions, permissionsToAdd); } } - }, - 'Failed logs and metadata provisioning' - ); - - if (provisioningComplete) { - this.#hasLogsMetadataBeenProvisioned[indexerConfig.accountId] ??= {}; - this.#hasLogsMetadataBeenProvisioned[indexerConfig.accountId][indexerConfig.functionName] = true; - } + }, 'Failed to ensure consistent Hasura state'); } async getTrackedTablesWithPermissions (indexerConfig: IndexerConfig): Promise {