Skip to content

Commit b2253b1

Browse files
authored
Durable async cleanup to speed up tests (#109)
* Simplify cleanup using centralized tf/destroy, return cleanup closures on error, and apply review fixes for robust cleanup * Hardcode cleanup dir * Address David's comments * Fix nit in main.tf
1 parent 6024346 commit b2253b1

37 files changed

Lines changed: 330 additions & 143 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ terraform*
1919

2020
go.work
2121
go.work.sum
22+
23+
# Ignore nested jj workspace
24+
concurrent_task/

cloudbuild-cleanup.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
steps:
2+
- name: $_TEST_RUNNER_IMAGE
3+
id: cleanup
4+
dir: /
5+
timeout: 600s
6+
args:
7+
- cleanup
8+
- --environment=$_E2E_ENVIRONMENT
9+
- --project-id=$PROJECT_ID
10+
- --test-run-id=$_TEST_RUN_ID
11+
12+
substitutions:
13+
_TEST_RUN_ID: $BUILD_ID

cloudbuild-e2e-cloud-functions-gen2.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,19 @@ steps:
6161
id: run-tests-cloudfunctions
6262
dir: /
6363
timeout: 1800s
64-
env: ["PROJECT_ID=$PROJECT_ID"]
64+
env: ["PROJECT_ID=$PROJECT_ID", "TEST_RUN_ID=$BUILD_ID"]
6565
args:
6666
- cloud-functions-gen2
6767
- --runtime=go125
6868
- --functionsource=/workspace/opentelemetry-operations-go/e2e-test-server/cloud_functions/function-source.zip
6969
- --entrypoint=HandleCloudFunction
70+
- --skip-cleanup
7071

7172
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
7273
substitutions:
7374
_TEST_RUNNER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-e2e-testing:${SHORT_SHA}
7475
_TEST_SERVER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-go-e2e-test-server:${SHORT_SHA}
76+
77+
options:
78+
# Notify for Cloud Build async cleanup trigger
79+
pubsubTopic: projects/opentelemetry-ops-e2e/topics/e2e-cleanup

cloudbuild-e2e-cloud-run.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ steps:
3939
id: run-tests-cloudrun
4040
dir: /
4141
timeout: 10m
42-
env: ["PROJECT_ID=$PROJECT_ID"]
42+
env: ["PROJECT_ID=$PROJECT_ID", "TEST_RUN_ID=$BUILD_ID"]
4343
args:
4444
- cloud-run
4545
- --image=$_TEST_SERVER_IMAGE
46+
- --skip-cleanup
4647

4748
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
4849
substitutions:
4950
_TEST_RUNNER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-e2e-testing:${SHORT_SHA}
5051
_TEST_SERVER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-go-e2e-test-server:${SHORT_SHA}
52+
53+
options:
54+
# Notify for Cloud Build async cleanup trigger
55+
pubsubTopic: projects/opentelemetry-ops-e2e/topics/e2e-cleanup

cloudbuild-e2e-gae-standard.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,18 @@ steps:
6161
id: run-tests-gae-standard
6262
dir: /
6363
timeout: 10m
64-
env: ["PROJECT_ID=$PROJECT_ID"]
64+
env: ["PROJECT_ID=$PROJECT_ID", "TEST_RUN_ID=$BUILD_ID"]
6565
args:
6666
- gae-standard
6767
- --runtime=go125
6868
- --appsource=/workspace/opentelemetry-operations-go/e2e-test-server/appsource.zip
69+
- --skip-cleanup
6970

7071
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
7172
substitutions:
7273
_TEST_RUNNER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-e2e-testing:${SHORT_SHA}
7374
_TEST_SERVER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-go-e2e-test-server:${SHORT_SHA}
75+
76+
options:
77+
# Notify for Cloud Build async cleanup trigger
78+
pubsubTopic: projects/opentelemetry-ops-e2e/topics/e2e-cleanup

cloudbuild-e2e-gae.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,18 @@ steps:
3939
id: run-tests-gae
4040
dir: /
4141
timeout: 10m
42-
env: ["PROJECT_ID=$PROJECT_ID"]
42+
env: ["PROJECT_ID=$PROJECT_ID", "TEST_RUN_ID=$BUILD_ID"]
4343
args:
4444
- gae
4545
- --image=$_TEST_SERVER_IMAGE
4646
- --runtime=go125
47+
- --skip-cleanup
4748

4849
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
4950
substitutions:
5051
_TEST_RUNNER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-e2e-testing:${SHORT_SHA}
5152
_TEST_SERVER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-go-e2e-test-server:${SHORT_SHA}
53+
54+
options:
55+
# Notify for Cloud Build async cleanup trigger
56+
pubsubTopic: projects/opentelemetry-ops-e2e/topics/e2e-cleanup

cloudbuild-e2e-gce.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ steps:
3939
id: run-tests-gce
4040
dir: /
4141
timeout: 10m
42-
env: ["PROJECT_ID=$PROJECT_ID"]
42+
env: ["PROJECT_ID=$PROJECT_ID", "TEST_RUN_ID=$BUILD_ID"]
4343
args:
4444
- gce
4545
- --image=$_TEST_SERVER_IMAGE
46+
- --skip-cleanup
4647

4748
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
4849
substitutions:
4950
_TEST_RUNNER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-e2e-testing:${SHORT_SHA}
5051
_TEST_SERVER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-go-e2e-test-server:${SHORT_SHA}
52+
53+
options:
54+
# Notify for Cloud Build async cleanup trigger
55+
pubsubTopic: projects/opentelemetry-ops-e2e/topics/e2e-cleanup

cloudbuild-e2e-gke.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ steps:
3939
id: run-tests-gke
4040
dir: /
4141
timeout: 10m
42-
env: ["PROJECT_ID=$PROJECT_ID"]
42+
env: ["PROJECT_ID=$PROJECT_ID", "TEST_RUN_ID=$BUILD_ID"]
4343
args:
4444
- gke
4545
- --image=$_TEST_SERVER_IMAGE
46+
- --skip-cleanup
4647

4748
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
4849
substitutions:
4950
_TEST_RUNNER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-e2e-testing:${SHORT_SHA}
5051
_TEST_SERVER_IMAGE: us-central1-docker.pkg.dev/${PROJECT_ID}/e2e-testing/opentelemetry-operations-go-e2e-test-server:${SHORT_SHA}
52+
53+
options:
54+
# Notify for Cloud Build async cleanup trigger
55+
pubsubTopic: projects/opentelemetry-ops-e2e/topics/e2e-cleanup

e2etesting/e2e_testing.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strings"
2424
"time"
2525

26+
"github.com/GoogleCloudPlatform/opentelemetry-operations-e2e-testing/e2etesting/setuptf"
2627
"github.com/GoogleCloudPlatform/opentelemetry-operations-e2e-testing/e2etestrunner/testclient"
2728
"github.com/alexflint/go-arg"
2829
)
@@ -106,12 +107,16 @@ type CloudFunctionsGen2Cmd struct {
106107
FunctionSource string `arg:"required" help:"The full path of the zip file that contains the code source that needs to run within the CloudFunction"`
107108
}
108109

110+
type CleanupCmd struct{}
111+
109112
type Args struct {
110113
// This subcommand is a special case, it doesn't run any tests. It just
111114
// applies the persistent resources which are used across tests. See
112115
// tf/persistent/README.md for details on what is in there.
113116
ApplyPersistent *ApplyPersistent `arg:"subcommand:apply-persistent" help:"Terraform apply the resources in tf/persistent and exit (does not run tests)."`
114117

118+
Cleanup *CleanupCmd `arg:"subcommand:cleanup" help:"Clean up resources for a test run"`
119+
115120
Local *LocalCmd `arg:"subcommand:local" help:"Deploy the test server locally with docker and execute tests"`
116121
Gke *GkeCmd `arg:"subcommand:gke" help:"Deploy the test server on GKE and execute tests"`
117122
Gce *GceCmd `arg:"subcommand:gce" help:"Deploy the test server on GCE and execute tests"`
@@ -135,6 +140,7 @@ type Args struct {
135140
// resources created for debugging. If not provided, we generate a hex
136141
// string.
137142
TestRunID string `arg:"--test-run-id,env:TEST_RUN_ID" help:"Optional test run id to use to partition terraform resources"`
143+
SkipCleanup bool `arg:"--skip-cleanup" help:"Skip automatic cleanup after tests (default: false). If enabled, you must run the 'cleanup' subcommand manually or via trigger."`
138144
}
139145

140146
type Cleanup func()
@@ -164,6 +170,15 @@ func InitTestMain(args *Args, applyPersistent ApplyPersistentFunc) (*log.Logger,
164170
logger := log.New(os.Stdout, "TestMain: ", log.LstdFlags|log.Lshortfile)
165171
ctx := context.Background()
166172

173+
// Handle cleanup subcommand
174+
if args.Cleanup != nil {
175+
err := setuptf.CleanupTf(ctx, args.ProjectID, args.TestRunID, logger)
176+
if err != nil {
177+
logger.Panic(err)
178+
}
179+
return nil, nil, true
180+
}
181+
167182
// Handle special case of just creating persistent resources
168183
if args.ApplyPersistent != nil {
169184
err := applyPersistent(ctx, args.ProjectID, args.ApplyPersistent.AutoApprove, logger)

e2etesting/setuptf/setuptf.go

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,8 @@ func initCommand(ctx context.Context, projectID string) *exec.Cmd {
6969
)
7070
}
7171

72-
// Runs the sequence of terraform commands most environemnts need, returns the
73-
// output bytes of `terraform output -json` and a cleanup function to teardown
74-
// the created resources.
72+
// Runs the sequence of terraform commands most environments need, returns the
73+
// parsed PubsubInfo from `terraform output -json`.
7574
//
7675
// 1. Run terraform init
7776
// 2. Create a new terraform workspace for the test run ID
@@ -86,35 +85,17 @@ func SetupTf(
8685
tfDir string, // the Dir to set when running terraform commands in e.g. tf/gke
8786
tfVars map[string]string, // key-values for terraform input vars to send to terraform
8887
logger *log.Logger,
89-
) (*PubsubInfo, func(), error) {
88+
) (*PubsubInfo, error) {
9089
tfVarArgs := tfVarMapToArgs(projectID, tfVars)
9190
cmd := initCommand(ctx, projectID)
9291
cmd.Args = append(cmd.Args, tfVarArgs...)
9392
cmd.Dir = tfDir
9493
if err := runWithOutput(cmd, logger); err != nil {
95-
return nil, func() {}, err
94+
return nil, err
9695
}
9796

9897
logger.Printf("Running %s with image: %s\n", tfDir, tfVars["image"])
9998

100-
cleanup := func() {
101-
defer deleteWorkspace(ctx, testRunID, tfDir, logger)
102-
103-
// Run terraform destroy
104-
cmd = exec.CommandContext(
105-
ctx,
106-
"terraform",
107-
"destroy",
108-
"-input=false",
109-
"-auto-approve",
110-
)
111-
cmd.Args = append(cmd.Args, tfVarArgs...)
112-
cmd.Dir = tfDir
113-
if err := runWithOutput(cmd, logger); err != nil {
114-
logger.Panic(err)
115-
}
116-
}
117-
11899
// Create new terraform workspace
119100
cmd = exec.CommandContext(ctx, "terraform", "workspace", "new", testRunID)
120101
cmd.Dir = tfDir
@@ -124,7 +105,7 @@ func SetupTf(
124105
cmd.Dir = tfDir
125106

126107
if err := runWithOutput(cmd, logger); err != nil {
127-
return nil, cleanup, err
108+
return nil, err
128109
}
129110
}
130111

@@ -139,7 +120,7 @@ func SetupTf(
139120
cmd.Args = append(cmd.Args, tfVarArgs...)
140121
cmd.Dir = tfDir
141122
if err := runWithOutput(cmd, logger); err != nil {
142-
return nil, cleanup, err
123+
return nil, err
143124
}
144125

145126
// Run terraform output
@@ -148,14 +129,14 @@ func SetupTf(
148129
out, err := cmd.Output()
149130
if err != nil {
150131
logger.Println(err)
151-
return nil, cleanup, err
132+
return nil, err
152133
}
153134

154135
tfOutput := &tfOutput{}
155136
if err := json.Unmarshal(out, tfOutput); err != nil {
156-
return nil, cleanup, err
137+
return nil, err
157138
}
158-
return &tfOutput.PubsubInfoWrapper.Value, cleanup, nil
139+
return &tfOutput.PubsubInfoWrapper.Value, nil
159140
}
160141

161142
func ApplyPersistent(
@@ -255,3 +236,47 @@ func tfVarMapToArgs(
255236
}
256237
return tfVarArgs
257238
}
239+
240+
// CleanupTf runs terraform destroy and deletes the workspace.
241+
func CleanupTf(
242+
ctx context.Context,
243+
projectID string,
244+
testRunID string,
245+
logger *log.Logger,
246+
) error {
247+
const tfDir = "tf/destroy"
248+
tfVarArgs := []string{fmt.Sprintf("-var=project_id=%s", projectID)}
249+
cmd := initCommand(ctx, projectID)
250+
cmd.Dir = tfDir
251+
if err := runWithOutput(cmd, logger); err != nil {
252+
logger.Printf("error cleaning up terraform (init) in %s: %v", tfDir, err)
253+
return err
254+
}
255+
256+
// Switch to target workspace
257+
cmd = exec.CommandContext(ctx, "terraform", "workspace", "select", testRunID)
258+
cmd.Dir = tfDir
259+
if err := runWithOutput(cmd, logger); err != nil {
260+
logger.Printf("error cleaning up terraform (workspace select %s) in %s: %v", testRunID, tfDir, err)
261+
return err
262+
}
263+
264+
// Run terraform destroy
265+
cmd = exec.CommandContext(
266+
ctx,
267+
"terraform",
268+
"destroy",
269+
"-input=false",
270+
"-auto-approve",
271+
)
272+
cmd.Args = append(cmd.Args, tfVarArgs...)
273+
cmd.Dir = tfDir
274+
if err := runWithOutput(cmd, logger); err != nil {
275+
logger.Printf("error cleaning up terraform (destroy) in %s: %v", tfDir, err)
276+
return err
277+
}
278+
279+
deleteWorkspace(ctx, testRunID, tfDir, logger)
280+
281+
return nil
282+
}

0 commit comments

Comments
 (0)