Skip to content

Commit

Permalink
Merge #96990
Browse files Browse the repository at this point in the history
96990: roachtest: support running steps in the background in `mixedversion` r=srosenberg a=renatolabs

This adds a `BackgroundFunc` API to the `mixedversion` package in
roachtest, allowing test writers to run tasks in the background during
an upgrade test. The most common use-case for this functionality is
running a workload while the cluster upgrades (other similar use-cases
exist in a variety of tests); for this reason, a `Workload`
convenience function is added that allows tests to add a workload to a
mixed-version test with one function call.

Currently, each test needs to devise their own mechanism to: spawn the
background task; monitor its execution; and terminate the test on
error.

The current API aims to reduce copying and pasting of such logic,
making for a more declarative test. In the future, the test planner
itself could decide to run some steps in the background and it should
be able to leverage the mechanisms introduced in this commit.

Epic: [CRDB-19321](https://cockroachlabs.atlassian.net/browse/CRDB-19321)

Release note: None

Co-authored-by: Renato Costa <[email protected]>
  • Loading branch information
craig[bot] and renatolabs committed Mar 9, 2023
2 parents 721dcb2 + b16ffe1 commit dcd687c
Show file tree
Hide file tree
Showing 10 changed files with 860 additions and 160 deletions.
2 changes: 2 additions & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ ALL_TESTS = [
"//pkg/cmd/roachprod-microbench:roachprod-microbench_test",
"//pkg/cmd/roachtest/clusterstats:clusterstats_test",
"//pkg/cmd/roachtest/roachtestutil/mixedversion:mixedversion_test",
"//pkg/cmd/roachtest/roachtestutil:roachtestutil_test",
"//pkg/cmd/roachtest/tests:tests_test",
"//pkg/cmd/roachtest:roachtest_test",
"//pkg/cmd/teamcity-trigger:teamcity-trigger_test",
Expand Down Expand Up @@ -1043,6 +1044,7 @@ GO_TARGETS = [
"//pkg/cmd/roachtest/roachtestutil/mixedversion:mixedversion",
"//pkg/cmd/roachtest/roachtestutil/mixedversion:mixedversion_test",
"//pkg/cmd/roachtest/roachtestutil:roachtestutil",
"//pkg/cmd/roachtest/roachtestutil:roachtestutil_test",
"//pkg/cmd/roachtest/spec:spec",
"//pkg/cmd/roachtest/test:test",
"//pkg/cmd/roachtest/tests:tests",
Expand Down
11 changes: 10 additions & 1 deletion pkg/cmd/roachtest/roachtestutil/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data")
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "roachtestutil",
srcs = [
"commandbuilder.go",
"jaeger.go",
"validation_check.go",
],
Expand All @@ -17,4 +18,12 @@ go_library(
],
)

go_test(
name = "roachtestutil_test",
srcs = ["commandbuilder_test.go"],
args = ["-test.timeout=295s"],
embed = [":roachtestutil"],
deps = ["@com_github_stretchr_testify//require"],
)

get_x_data(name = "get_x_data")
131 changes: 131 additions & 0 deletions pkg/cmd/roachtest/roachtestutil/commandbuilder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package roachtestutil

import (
"fmt"
"sort"
"strings"
)

// Command wraps a command to be run in a cluster. It allows users to
// manipulate a command without having to perform string-based
// operations.
type Command struct {
Binary string
Arguments []string
Flags map[string]*string
}

// NewCommand builds a command. The format parameter can take
// `fmt.Print` verbs.
//
// Examples:
//
// NewCommand("./cockroach version")
// NewCommand("%s version", binaryPath)
func NewCommand(format string, args ...interface{}) *Command {
cmd := fmt.Sprintf(format, args...)
parts := strings.Fields(cmd)
return &Command{
Binary: parts[0],
Arguments: parts[1:],
Flags: make(map[string]*string),
}
}

func (c *Command) Arg(format string, args ...interface{}) *Command {
c.Arguments = append(c.Arguments, fmt.Sprintf(format, args...))
return c
}

func (c *Command) HasFlag(name string) bool {
_, ok := c.Flags[name]
return ok
}

func (c *Command) Flag(name string, val interface{}) *Command {
c.Flags[name] = stringP(fmt.Sprint(val))
return c
}

// MaybeFlag is a thin wrapper around Flag for the caller's
// convenience. The flag is added only if the `condition` parameter is
// true.
func (c *Command) MaybeFlag(condition bool, name string, val interface{}) *Command {
if condition {
return c.Flag(name, val)
}

return c
}

// Option adds a flag that doesn't have an associated value
func (c *Command) Option(name string) *Command {
c.Flags[name] = nil
return c
}

func (c *Command) MaybeOption(condition bool, name string) *Command {
if condition {
return c.Option(name)
}

return c
}

// ITEFlag (if-then-else flag) adds a flag where the value depends on
// the `condition` parameter. `trueVal` is used if `condition` is
// true; `falseVal` is used otherwise.
func (c *Command) ITEFlag(condition bool, name string, trueVal, falseVal interface{}) *Command {
if condition {
return c.Flag(name, trueVal)
}

return c.Flag(name, falseVal)
}

// String returns a canonical string representation of the command
// which can be passed to `cluster.Run`.
func (c *Command) String() string {
flags := make([]string, 0, len(c.Flags))
names := make([]string, 0, len(c.Flags))
for name := range c.Flags {
names = append(names, name)
}
sort.Strings(names)

for _, name := range names {
val := c.Flags[name]
prefix := "-"
if len(name) > 1 {
prefix = "--"
}

prefixedName := prefix + name
parts := []string{prefixedName}
if val != nil {
parts = append(parts, *val)
}
flags = append(flags, strings.Join(parts, " "))
}

cmd := append(
[]string{c.Binary},
append(c.Arguments, flags...)...,
)

return strings.Join(cmd, " ")
}

func stringP(s string) *string {
return &s
}
77 changes: 77 additions & 0 deletions pkg/cmd/roachtest/roachtestutil/commandbuilder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package roachtestutil

import (
"testing"
"time"

"github.com/stretchr/testify/require"
)

func TestCommand(t *testing.T) {
c := NewCommand("./cockroach")
require.Equal(t, "./cockroach", c.String())

c = NewCommand("./cockroach workload init")
require.Equal(t, "./cockroach workload init", c.String())

c = NewCommand("./cockroach").Arg("workload").Arg("init")
require.Equal(t, "./cockroach workload init", c.String())

baseCommand := NewCommand("./cockroach workload run bank").Arg("{pgurl:%d}", 1)

c = clone(baseCommand)
c.Flag("max-ops", 10).Flag("path", "/some/path")
require.Equal(t, "./cockroach workload run bank {pgurl:1} --max-ops 10 --path /some/path", c.String())

c = clone(baseCommand)
c.MaybeFlag(true, "max-ops", 10) // included
c.MaybeFlag(false, "concurrency", 8) // not included
require.True(t, c.HasFlag("max-ops"))
require.False(t, c.HasFlag("concurrency"))
require.Equal(t, "./cockroach workload run bank {pgurl:1} --max-ops 10", c.String())

c = clone(baseCommand)
c.ITEFlag(true, "max-ops", 10, 20)
c.ITEFlag(false, "duration", 2*time.Hour, 10*time.Minute)
require.Equal(t, "./cockroach workload run bank {pgurl:1} --duration 10m0s --max-ops 10", c.String())

c = clone(baseCommand)
c.Option("local")
c.MaybeOption(true, "background") // included
c.MaybeOption(false, "dry-run") // not included
require.True(t, c.HasFlag("local"))
require.True(t, c.HasFlag("background"))
require.False(t, c.HasFlag("dry-run"))
require.Equal(t, "./cockroach workload run bank {pgurl:1} --background --local", c.String())

c = clone(baseCommand)
c.Flag("c", 10)
c.MaybeFlag(true, "n", "8") // included
c.MaybeFlag(false, "j", "yes") // not included
c.Option("x")
require.True(t, c.HasFlag("c"))
require.Equal(t, "./cockroach workload run bank {pgurl:1} -c 10 -n 8 -x", c.String())
}

func clone(cmd *Command) *Command {
flags := make(map[string]*string)
for k, v := range cmd.Flags {
flags[k] = v
}

return &Command{
Binary: cmd.Binary,
Arguments: append([]string{}, cmd.Arguments...),
Flags: flags,
}
}
5 changes: 4 additions & 1 deletion pkg/cmd/roachtest/roachtestutil/mixedversion/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ go_library(
deps = [
"//pkg/cmd/roachtest/cluster",
"//pkg/cmd/roachtest/option",
"//pkg/cmd/roachtest/roachtestutil",
"//pkg/cmd/roachtest/roachtestutil/clusterupgrade",
"//pkg/cmd/roachtest/test",
"//pkg/roachpb",
"//pkg/roachprod/logger",
"//pkg/util/ctxgroup",
"//pkg/util/randutil",
"//pkg/util/timeutil",
"//pkg/util/version",
"@org_golang_x_sync//errgroup",
],
)

Expand All @@ -30,7 +31,9 @@ go_test(
args = ["-test.timeout=295s"],
embed = [":mixedversion"],
deps = [
"//pkg/cmd/roachtest/cluster",
"//pkg/cmd/roachtest/option",
"//pkg/cmd/roachtest/roachtestutil",
"//pkg/cmd/roachtest/roachtestutil/clusterupgrade",
"//pkg/roachprod/logger",
"//pkg/util/version",
Expand Down
Loading

0 comments on commit dcd687c

Please sign in to comment.