Skip to content

feat: Idempotent Provisioning - #1004

Merged
darunrs merged 13 commits into
mainfrom
multi-step-provisioning
Aug 14, 2024
Merged

darunrs merged 13 commits into
mainfrom
multi-step-provisioning

Conversation

@darunrs

@darunrs darunrs commented Aug 10, 2024 •

Copy link
Copy Markdown
Contributor

Provisioning is occasionally finnicky, failing on different parts of the process. Having retryable provisioning allows us to heal indexer state through retries rather than manually actioning this. To this, the provisioning step needs to have access to accurate status of provisioning in order to make the correct decisions on which non-idempotent actions to take.

This PR introduces a ProvisioningState class which represents the status of provisioning, and can be used to make decisions on what parts of provisioning need to be completed.

@darunrs
darunrs force-pushed the multi-step-provisioning branch from bb3e948 to c975259 Compare August 14, 2024 05:11
@darunrs darunrs changed the title feat: Retryable Provisioning feat: Idempotent Provisioning Aug 14, 2024
@darunrs
darunrs marked this pull request as ready for review August 14, 2024 05:13
@darunrs
darunrs requested a review from a team August 14, 2024 05:13
private tablesInSource: string[],
) {}

static async loadProvisioningState (hasuraClient: HasuraClient, provisioningConfig: ProvisioningConfig): Promise<ProvisioningState> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I decided not to have the hasuraClient as a direct dependency for this class as it usually does not need access to the client, once initialized.

return new ProvisioningState(provisioningConfig, hasuraMetadata, tablesInSource);
}

async reload (hasuraClient: HasuraClient): Promise<void> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Technically, you can just recreate the state class to reload it, but I thought adding a specific function felt more semantically correct.

@darunrs

darunrs commented Aug 14, 2024

Copy link
Copy Markdown
Contributor Author

Tested locally with the following cases:

  1. Completely new source, schema, and such
  2. Re-provision the above with no changes
  3. Remove the provisioning user resources part, such that only system resources are provisioned. Then retry with all provisioning enabled.
  4. Remove adding permissions to system tables. Then retry with it enabled again.

In all above cases, provisioning properly handled or healed the state.

import { type HasuraTableMetadata, type HasuraMetadata, type HasuraSource, HASURA_PERMISSION_TYPES } from '../hasura-client';
import type HasuraClient from '../hasura-client';

export default class ProvisioningState {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The intention with this class is to be a lightweight interface which exposes the state of resources currently, and can be used to bring the current state closer to the intended one.

const functionName = 'test-function';
const databaseSchema = 'CREATE TABLE blocks (height numeric)';
indexerConfig = new IndexerConfig('', accountId, functionName, 0, '', databaseSchema, LogLevel.INFO);
const emptyHasuraMetadata = generateDefaultHasuraMetadata();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I chose to make the state class a variable rather than a class variable because the provisioner class is reused for all provisioning tasks and I did not want to manage cleaning the state between calls. If I re-created the state, it would make DI much harder. So, I decided to keep things simple and have it used in line. This does make mocking a little bit tougher as I need to mock metadata instead, but it's done in multiple places, so I felt this was fine.

const schemaName = indexerConfig.schemaName();

await this.runIndexerSql(databaseName, schemaName, indexerConfig.schema);
const onlySystemTablesCreated = provisioningState.getCreatedTables().every((table) => this.SYSTEM_TABLES.includes(table));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There's only two states in which we reach this point in the code. Either the system provisioning was successful in this current pass, or we are revisiting. If we are in the initial pass, the state will actually say we have created and tracked nothing since we have not reloaded. That's fine for this line through, as we are really just interested if any non system tables have already been created or not. It's not important if the state is up to date following system provisioning, as the outcome would be the same.

@darunrs

darunrs commented Aug 14, 2024

Copy link
Copy Markdown
Contributor Author

Pointed my local Provisioner to prod and ran provisioning over the broken strachu.near/easy-poll-v0.3_testing indexer. It completely fixed it and the indexer now runs.

@darunrs
darunrs merged commit 5633b4a into main Aug 14, 2024
@darunrs
darunrs deleted the multi-step-provisioning branch August 14, 2024 18:14
@darunrs darunrs linked an issue Aug 14, 2024 that may be closed by this pull request
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.

Refactor Provisioning into Multi-Step Process

1 participant