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

feat: Idempotent Provisioning #1004

Merged
merged 13 commits into from
Aug 14, 2024
Merged

feat: Idempotent Provisioning #1004

merged 13 commits into from
Aug 14, 2024

Conversation

darunrs
Copy link
Collaborator

@darunrs darunrs commented Aug 10, 2024

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 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 as a code owner August 14, 2024 05:13
private tablesInSource: string[],
) {}

static async loadProvisioningState (hasuraClient: HasuraClient, provisioningConfig: ProvisioningConfig): Promise<ProvisioningState> {
Copy link
Collaborator 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
Collaborator 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
Copy link
Collaborator Author

darunrs commented Aug 14, 2024

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
Collaborator 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 accountId = 'morgs.near';
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
Collaborator 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 databaseName = indexerConfig.databaseName();
const schemaName = indexerConfig.schemaName();

await this.runIndexerSql(databaseName, schemaName, indexerConfig.schema);
const onlySystemTablesCreated = provisioningState.getCreatedTables().every((table) => this.SYSTEM_TABLES.includes(table));
Copy link
Collaborator 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
Copy link
Collaborator Author

darunrs commented Aug 14, 2024

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
3 checks passed
@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