diff --git a/components/usage/go.mod b/components/usage/go.mod index fb6f247ffa7586..fc655a56690340 100644 --- a/components/usage/go.mod +++ b/components/usage/go.mod @@ -58,6 +58,8 @@ require ( github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000 github.com/go-sql-driver/mysql v1.6.0 github.com/google/uuid v1.1.2 + github.com/google/uuid v1.1.2 + github.com/jackc/pgtype v1.10.0 github.com/relvacode/iso8601 v1.1.0 github.com/robfig/cron v1.2.0 github.com/spf13/cobra v1.4.0 @@ -71,12 +73,14 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect diff --git a/components/usage/go.sum b/components/usage/go.sum index c242009539b78a..b1075b7baae3d6 100644 --- a/components/usage/go.sum +++ b/components/usage/go.sum @@ -91,6 +91,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -248,6 +249,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= diff --git a/components/usage/pkg/db/workspace_instance.go b/components/usage/pkg/db/workspace_instance.go new file mode 100644 index 00000000000000..383ef314c96c4e --- /dev/null +++ b/components/usage/pkg/db/workspace_instance.go @@ -0,0 +1,44 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package db + +import ( + "database/sql" + "github.com/google/uuid" + "gorm.io/datatypes" + "time" +) + +type WorkspaceInstance struct { + ID uuid.UUID `gorm:"primary_key;column:id;type:char;size:36;" json:"id"` + WorkspaceID string `gorm:"column:workspaceId;type:char;size:36;" json:"workspaceId"` + Configuration datatypes.JSON `gorm:"column:configuration;type:text;size:65535;" json:"configuration"` + Region string `gorm:"column:region;type:varchar;size:255;" json:"region"` + ImageBuildInfo sql.NullString `gorm:"column:imageBuildInfo;type:text;size:65535;" json:"imageBuildInfo"` + IdeURL string `gorm:"column:ideUrl;type:varchar;size:255;" json:"ideUrl"` + WorkspaceBaseImage string `gorm:"column:workspaceBaseImage;type:varchar;size:255;" json:"workspaceBaseImage"` + WorkspaceImage string `gorm:"column:workspaceImage;type:varchar;size:255;" json:"workspaceImage"` + + CreationTime VarcharTime `gorm:"column:creationTime;type:varchar;size:255;" json:"creationTime"` + StartedTime VarcharTime `gorm:"column:startedTime;type:varchar;size:255;" json:"startedTime"` + DeployedTime VarcharTime `gorm:"column:deployedTime;type:varchar;size:255;" json:"deployedTime"` + StoppedTime VarcharTime `gorm:"column:stoppedTime;type:varchar;size:255;" json:"stoppedTime"` + LastModified time.Time `gorm:"column:_lastModified;type:timestamp;default:CURRENT_TIMESTAMP(6);" json:"_lastModified"` + StoppingTime VarcharTime `gorm:"column:stoppingTime;type:varchar;size:255;" json:"stoppingTime"` + + LastHeartbeat string `gorm:"column:lastHeartbeat;type:varchar;size:255;" json:"lastHeartbeat"` + StatusOld sql.NullString `gorm:"column:status_old;type:varchar;size:255;" json:"status_old"` + Status datatypes.JSON `gorm:"column:status;type:json;" json:"status"` + Phase sql.NullString `gorm:"column:phase;type:char;size:32;" json:"phase"` + PhasePersisted string `gorm:"column:phasePersisted;type:char;size:32;" json:"phasePersisted"` + + // deleted is restricted for use by db-sync + _ bool `gorm:"column:deleted;type:tinyint;default:0;" json:"deleted"` +} + +// TableName sets the insert table name for this struct type +func (d *WorkspaceInstance) TableName() string { + return "d_b_workspace_instance" +} diff --git a/components/usage/pkg/db/workspace_instance_test.go b/components/usage/pkg/db/workspace_instance_test.go new file mode 100644 index 00000000000000..a38ded34371e2a --- /dev/null +++ b/components/usage/pkg/db/workspace_instance_test.go @@ -0,0 +1,100 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package db_test + +import ( + "fmt" + "github.com/gitpod-io/gitpod/usage/pkg/db" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + "strings" + "testing" +) + +var workspaceInstanceJSON = map[string]interface{}{ + "id": "2f9a29bb-406e-4494-b592-c99f7fa0e793", + "workspaceId": "gitpodio-ops-b8fqh3eylmr", + "region": "eu03", + "creationTime": "2022-05-31T15:22:57.192Z", + "startedTime": "2022-05-31T15:24:04.336Z", + "deployedTime": "", + "stoppedTime": "2022-05-31T15:24:50.361Z", + "lastHeartbeat": "", + "ideUrl": "https://gitpodio-ops-b8fqh3eylmr.ws-eu03.gitpod-staging.com", + "workspaceBaseImage": "", + "workspaceImage": "eu.gcr.io/gitpod-dev/workspace-images:59e2c9722f56918c0e84f2640880fee0c19ac40471e968faaddcdcaebaf0df58", + "status_old": nil, + "_lastModified": "2022-05-31 15:24:50.373117", + "status": "{\"repo\": {\"branch\": \"main\", \"latestCommit\": \"25d888e90dd21eb98be699c0b43556c153d667c9\", \"totalUntrackedFiles\": 0, \"totalUncommitedFiles\": 0, \"totalUnpushedCommits\": 0}, \"phase\": \"stopped\", \"nodeIp\": \"10.132.0.14\", \"message\": \"\", \"podName\": \"prebuild-2f9a29bb-406e-4494-b592-c99f7fa0e791\", \"timeout\": \"30m0s\", \"nodeName\": \"ws-eu03.gitpod-staging.com\", \"conditions\": {\"failed\": \"\", \"timeout\": \"\", \"deployed\": false, \"pullingImages\": false, \"stoppedByRequest\": false, \"headlessTaskFailed\": \"\"}, \"ownerToken\": \"4.MIXs2XurX1vMP7bTEryt9R9IbzGT_D\", \"exposedPorts\": []}", + "phase": "stopped", + "deleted": 0, + "phasePersisted": "stopped", + "configuration": "{\"ideImage\":\"eu.gcr.io/gitpod-core-dev/build/ide/code:commit-80d9b1ebfd826fd0db25320ba94d762b51887ada\",\"supervisorImage\":\"eu.gcr.io/gitpod-core-dev/build/supervisor:commit-1d4be665efdd1507f92b185afd112a7568d21a42\",\"ideConfig\":{\"useLatest\":false},\"featureFlags\":[\"registry_facade\",\"fixed_resources\"]}", + "stoppingTime": "2022-05-31T15:24:39.657Z", + "imageBuildInfo": nil, +} + +func TestWorkspaceInstance_ReadExistingRecords(t *testing.T) { + conn := db.ConnectForTests(t) + id := insertRawWorkspaceInstance(t, conn, workspaceInstanceJSON) + + wsi := db.WorkspaceInstance{ID: id} + tx := conn.First(&wsi) + require.NoError(t, tx.Error) + + require.Equal(t, id, wsi.ID) + + require.True(t, wsi.StoppingTime.IsSet()) + require.Equal(t, stringToVarchar(t, workspaceInstanceJSON["stoppingTime"].(string)), wsi.StoppingTime) + + require.True(t, wsi.StartedTime.IsSet()) + require.Equal(t, stringToVarchar(t, workspaceInstanceJSON["startedTime"].(string)), wsi.StartedTime) + + require.False(t, wsi.DeployedTime.IsSet()) + + require.True(t, wsi.StoppedTime.IsSet()) + require.Equal(t, stringToVarchar(t, workspaceInstanceJSON["stoppedTime"].(string)), wsi.StoppedTime) + + require.True(t, wsi.CreationTime.IsSet()) + require.Equal(t, stringToVarchar(t, workspaceInstanceJSON["creationTime"].(string)), wsi.CreationTime) + + require.Equal(t, workspaceInstanceJSON["status"], wsi.Status.String()) + require.Equal(t, workspaceInstanceJSON["configuration"], wsi.Configuration.String()) +} + +func insertRawWorkspaceInstance(t *testing.T, conn *gorm.DB, object map[string]interface{}) uuid.UUID { + columns := []string{"id", "workspaceId", "region", "creationTime", "startedTime", "deployedTime", "stoppedTime", "lastHeartbeat", "ideUrl", "workspaceBaseImage", "workspaceImage", "status_old", "_lastModified", "status", "deleted", "phasePersisted", "configuration", "stoppingTime", "imageBuildInfo"} + statement := fmt.Sprintf(`INSERT INTO d_b_workspace_instance (%s) VALUES ?;`, strings.Join(columns, ", ")) + id := uuid.MustParse(insertRawObject(t, conn, columns, statement, object)) + + t.Cleanup(func() { + tx := conn.Delete(&db.WorkspaceInstance{ID: id}) + require.NoError(t, tx.Error) + }) + + return id +} + +func insertRawObject(t *testing.T, conn *gorm.DB, columns []string, statement string, obj map[string]interface{}) string { + t.Helper() + + id := obj["id"].(string) + + var values []interface{} + for _, col := range columns { + val, ok := obj[col] + if !ok { + values = append(values, "null") + } else { + values = append(values, val) + } + } + + tx := conn.Exec(statement, values) + require.NoError(t, tx.Error) + + return id +} diff --git a/components/usage/pkg/db/workspace_test.go b/components/usage/pkg/db/workspace_test.go index 47bc02be8cd6cb..ba3a08d3bda7fd 100644 --- a/components/usage/pkg/db/workspace_test.go +++ b/components/usage/pkg/db/workspace_test.go @@ -71,28 +71,12 @@ func insertRawWorkspace(t *testing.T, conn *gorm.DB, rawWorkspace map[string]int INSERT INTO d_b_workspace ( %s ) VALUES ?;`, strings.Join(columns, ", ")) - - id := rawWorkspace["id"].(string) - - var values []interface{} - for _, col := range columns { - val, ok := rawWorkspace[col] - if !ok { - values = append(values, "null") - } else { - values = append(values, val) - } - - } - - tx := conn.Debug().Exec(statement, values) - require.NoError(t, tx.Error) + id := insertRawObject(t, conn, columns, statement, rawWorkspace) t.Cleanup(func() { tx := conn.Delete(&db.Workspace{ID: id}) require.NoError(t, tx.Error) }) - return id }