Skip to content

Commit

Permalink
Generic provisioners (#6084)
Browse files Browse the repository at this point in the history
* Make provisioners generic

* Make provisioners conform to the new interface

* Add database table

* Partially migrate deployments to provisioner resources

* Review

* Update deployment logic

* Fix reset deployments job

* Update validate_deployments job

* Persist healt check status

* Linto fixo

* Testifix

* Rename provisioner capacity job

* Comment about static runtime versions

* Idempotent static provisioner

* Consistent field order in provisioner.Resource

* Self review

* QA
  • Loading branch information
begelundmuller authored Nov 20, 2024
1 parent d9e0335 commit 24e7a45
Show file tree
Hide file tree
Showing 33 changed files with 3,460 additions and 2,807 deletions.
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ linters-settings:
include:
# Require explicit handling of all fields in the database options structs (to prevent nullification due to missing handling)
- github.com/rilldata/rill/admin/database\..*Options
# Require explicit handling of all fields related to deployments
- github.com/rilldata/rill/admin\..*DeploymentOptions
- github.com/rilldata/rill/admin\.provisionRuntimeOptions
- github.com/rilldata/rill/admin/provisioner\.Resource
- github.com/rilldata/rill/admin/provisioner\.ResourceOptions
gocritic:
enabled-tags:
- diagnostic
Expand Down
119 changes: 97 additions & 22 deletions admin/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type DB interface {
InsertProject(ctx context.Context, opts *InsertProjectOptions) (*Project, error)
DeleteProject(ctx context.Context, id string) error
UpdateProject(ctx context.Context, id string, opts *UpdateProjectOptions) (*Project, error)
CountProjectsForOrganization(ctx context.Context, orgID string) (int, error)
CountProjectsQuotaUsage(ctx context.Context, orgID string) (*ProjectsQuotaUsage, error)
FindProjectWhitelistedDomain(ctx context.Context, projectID, domain string) (*ProjectWhitelistedDomain, error)
FindProjectWhitelistedDomainForProjectWithJoinedRoleNames(ctx context.Context, projectID string) ([]*ProjectWhitelistedDomainWithJoinedRoleNames, error)
FindProjectWhitelistedDomainsForDomain(ctx context.Context, domain string) ([]*ProjectWhitelistedDomain, error)
Expand All @@ -103,13 +103,18 @@ type DB interface {
FindDeploymentByInstanceID(ctx context.Context, instanceID string) (*Deployment, error)
InsertDeployment(ctx context.Context, opts *InsertDeploymentOptions) (*Deployment, error)
DeleteDeployment(ctx context.Context, id string) error
UpdateDeployment(ctx context.Context, id string, opts *UpdateDeploymentOptions) (*Deployment, error)
UpdateDeploymentStatus(ctx context.Context, id string, status DeploymentStatus, msg string) (*Deployment, error)
UpdateDeploymentRuntimeVersion(ctx context.Context, id, version string) (*Deployment, error)
UpdateDeploymentBranch(ctx context.Context, id, branch string) (*Deployment, error)
UpdateDeploymentUsedOn(ctx context.Context, ids []string) error
CountDeploymentsForOrganization(ctx context.Context, orgID string) (*DeploymentsCount, error)

ResolveRuntimeSlotsUsed(ctx context.Context) ([]*RuntimeSlotsUsed, error)
// UpsertStaticRuntimeAssignment tracks the host and slots registered for a provisioner resource.
// It is used by the "static" runtime provisioner to track slot usage on each host.
UpsertStaticRuntimeAssignment(ctx context.Context, id string, host string, slots int) error
// DeleteStaticRuntimeAssignment removes the host and slots assignment for a provisioner resource.
// The implementation should be idempotent.
DeleteStaticRuntimeAssignment(ctx context.Context, id string) error
// ResolveStaticRuntimeSlotsUsed returns the current slot usage for each runtime host as tracked by UpsertStaticRuntimeAssignment.
ResolveStaticRuntimeSlotsUsed(ctx context.Context) ([]*StaticRuntimeSlotsUsed, error)

FindUsers(ctx context.Context) ([]*User, error)
FindUsersByEmailPattern(ctx context.Context, emailPattern, afterEmail string, limit int) ([]*User, error)
Expand Down Expand Up @@ -286,6 +291,11 @@ type DB interface {
FindProjectVariables(ctx context.Context, projectID string, environment *string) ([]*ProjectVariable, error)
UpsertProjectVariable(ctx context.Context, projectID, environment string, vars map[string]string, userID string) ([]*ProjectVariable, error)
DeleteProjectVariables(ctx context.Context, projectID, environment string, vars []string) error

FindProvisionerResourcesForDeployment(ctx context.Context, deploymentID string) ([]*ProvisionerResource, error)
InsertProvisionerResource(ctx context.Context, opts *InsertProvisionerResourceOptions) (*ProvisionerResource, error)
UpdateProvisionerResource(ctx context.Context, id string, opts *UpdateProvisionerResourceOptions) (*ProvisionerResource, error)
DeleteProvisionerResource(ctx context.Context, id string) error
}

// Tx represents a database transaction. It can only be used to commit and rollback transactions.
Expand Down Expand Up @@ -453,10 +463,6 @@ func (d DeploymentStatus) String() string {
type Deployment struct {
ID string `db:"id"`
ProjectID string `db:"project_id"`
Provisioner string `db:"provisioner"`
ProvisionID string `db:"provision_id"`
RuntimeVersion string `db:"runtime_version"`
Slots int `db:"slots"`
Branch string `db:"branch"`
RuntimeHost string `db:"runtime_host"`
RuntimeInstanceID string `db:"runtime_instance_id"`
Expand All @@ -471,22 +477,28 @@ type Deployment struct {
// InsertDeploymentOptions defines options for inserting a new Deployment.
type InsertDeploymentOptions struct {
ProjectID string
Provisioner string `validate:"required"`
ProvisionID string
RuntimeVersion string
Slots int
Branch string
RuntimeHost string `validate:"required"`
RuntimeInstanceID string `validate:"required"`
RuntimeHost string
RuntimeInstanceID string
RuntimeAudience string
Status DeploymentStatus
StatusMessage string
}

// UpdateDeploymentOptions defines options for updating a Deployment.
type UpdateDeploymentOptions struct {
Branch string
RuntimeHost string
RuntimeInstanceID string
RuntimeAudience string
Status DeploymentStatus
StatusMessage string
}

// RuntimeSlotsUsed is the result of a ResolveRuntimeSlotsUsed query.
type RuntimeSlotsUsed struct {
RuntimeHost string `db:"runtime_host"`
SlotsUsed int `db:"slots_used"`
// StaticRuntimeSlotsUsed is the number of slots currently assigned to a runtime host.
type StaticRuntimeSlotsUsed struct {
Host string `db:"host"`
Slots int `db:"slots"`
}

// User is a person registered in Rill.
Expand Down Expand Up @@ -840,9 +852,10 @@ type Invite struct {
InvitedBy string `db:"invited_by"`
}

type DeploymentsCount struct {
Deployments int
Slots int
type ProjectsQuotaUsage struct {
Projects int `db:"projects"`
Deployments int `db:"deployments"`
Slots int `db:"slots"`
}

type OrganizationWhitelistedDomain struct {
Expand Down Expand Up @@ -1115,3 +1128,65 @@ type UpsertBillingIssueOptions struct {
Metadata BillingIssueMetadata
EventTime time.Time `validate:"required"`
}

// ProvisionerResourceStatus is an enum representing the state of a provisioner resource
type ProvisionerResourceStatus int

const (
ProvisionerResourceStatusUnspecified ProvisionerResourceStatus = 0
ProvisionerResourceStatusPending ProvisionerResourceStatus = 1
ProvisionerResourceStatusOK ProvisionerResourceStatus = 2
ProvisionerResourceStatusError ProvisionerResourceStatus = 4
)

func (d ProvisionerResourceStatus) String() string {
switch d {
case ProvisionerResourceStatusPending:
return "Pending"
case ProvisionerResourceStatusOK:
return "OK"
case ProvisionerResourceStatusError:
return "Error"
default:
return "Unspecified"
}
}

// ProvisionerResource represents a resource created by a provisioner (see admin/provisioner/README.md for details about provisioners).
type ProvisionerResource struct {
ID string `db:"id"`
DeploymentID string `db:"deployment_id"`
Type string `db:"type"`
Name string `db:"name"`
Status ProvisionerResourceStatus `db:"status"`
StatusMessage string `db:"status_message"`
Provisioner string `db:"provisioner"`
Args map[string]any `db:"args_json"`
State map[string]any `db:"state_json"`
Config map[string]any `db:"config_json"`
CreatedOn time.Time `db:"created_on"`
UpdatedOn time.Time `db:"updated_on"`
}

// InsertProvisionerResourceOptions defines options for inserting a new ProvisionerResource.
type InsertProvisionerResourceOptions struct {
ID string
DeploymentID string
Type string
Name string
Status ProvisionerResourceStatus
StatusMessage string
Provisioner string
Args map[string]any
State map[string]any
Config map[string]any
}

// UpdateProvisionerResourceOptions defines options for updating a ProvisionerResource.
type UpdateProvisionerResourceOptions struct {
Status ProvisionerResourceStatus
StatusMessage string
Args map[string]any
State map[string]any
Config map[string]any
}
63 changes: 63 additions & 0 deletions admin/database/postgres/migrations/0054.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
CREATE TABLE provisioner_resources (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
deployment_id UUID NOT NULL REFERENCES deployments (id) ON DELETE RESTRICT,
"type" TEXT NOT NULL,
name TEXT NOT NULL,
status INTEGER NOT NULL,
status_message TEXT NOT NULL DEFAULT '',
provisioner TEXT NOT NULL,
args_json JSONB NOT NULL,
state_json JSONB NOT NULL,
config_json JSONB NOT NULL,
created_on TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_on TIMESTAMPTZ DEFAULT now() NOT NULL
);

CREATE UNIQUE INDEX provisioner_resources_deployment_id_type_name_idx ON provisioner_resources (deployment_id, "type", lower(name));

INSERT INTO provisioner_resources (
id,
deployment_id,
"type",
name,
status,
status_message,
provisioner,
args_json,
state_json,
config_json,
created_on,
updated_on
) SELECT
uuid(provision_id),
id as deployment_id,
'runtime' as "type",
'' as name,
status,
status_message,
provisioner,
jsonb_build_object('slots', slots, 'version', 'latest') as args_json,
jsonb_build_object('slots', slots, 'version', runtime_version) as state_json,
jsonb_build_object('host', runtime_host, 'audience', runtime_audience, 'cpu', slots, 'memory_gb', 4*slots, 'storage_bytes', 40*slots) as config_json,
created_on,
updated_on
FROM deployments;

CREATE TABLE static_runtime_assignments (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
-- This could be a foreign key to provisioner_resources.id, but we don't enforce it to avoid tight coupling with the provisioner's internal data model.
resource_id UUID NOT NULL,
host TEXT NOT NULL,
slots INTEGER NOT NULL DEFAULT 0
);

CREATE UNIQUE INDEX static_runtime_assignments_resource_id_idx ON static_runtime_assignments (resource_id);

INSERT INTO static_runtime_assignments (resource_id, host, slots)
SELECT uuid(d.provision_id), d.runtime_host, d.slots FROM deployments d WHERE d.provisioner = 'static';

ALTER TABLE deployments
DROP COLUMN slots,
DROP COLUMN provisioner,
DROP COLUMN provision_id,
DROP COLUMN runtime_version;
Loading

1 comment on commit 24e7a45

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

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

Please sign in to comment.