diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 47121007a0..4939dac5b9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -64,8 +64,6 @@ jobs: run: "go test -tags ci ./..." e2e: - # Explicitly disabling e2e testing - if: "false" name: "E2E" runs-on: "ubuntu-latest" steps: diff --git a/e2e/cockroach/cockroach.go b/e2e/cockroach/cockroach.go index 08d790852e..b705f1bf34 100644 --- a/e2e/cockroach/cockroach.go +++ b/e2e/cockroach/cockroach.go @@ -73,6 +73,30 @@ func (c *Node) Conn() *pgx.Conn { return c.conn } +// NodeID returns the cockroach-internal node id for this connection. This is +// the value that is referenced by other crdb metadata to identify range leader, +// follower nodes, etc. +func (c *Node) NodeID(ctx context.Context) (int, error) { + rows, err := c.conn.Query(ctx, "SHOW node_id") + defer rows.Close() + if err != nil { + return -1, err + } + // despite being an int, crdb returns node id as a string + var nodeID string + for rows.Next() { + if err := rows.Scan(&nodeID); err != nil { + return -1, err + } + break + } + i, err := strconv.Atoi(nodeID) + if err != nil { + return -1, err + } + return i, nil +} + // Cluster represents a set of Node nodes configured to talk to // each other. type Cluster []*Node diff --git a/e2e/generator/names.go b/e2e/generator/names.go new file mode 100644 index 0000000000..a81230f4d1 --- /dev/null +++ b/e2e/generator/names.go @@ -0,0 +1,25 @@ +package generator + +import "github.com/brianvoe/gofakeit/v6" + +type UniqueGenerator struct { + seen map[string]struct{} + regex string +} + +func NewUniqueGenerator(regex string) *UniqueGenerator { + return &UniqueGenerator{ + seen: make(map[string]struct{}, 0), + regex: regex, + } +} + +func (g *UniqueGenerator) Next() string { + for { + val := gofakeit.Regex(g.regex) + if _, ok := g.seen[val]; !ok { + g.seen[val] = struct{}{} + return val + } + } +} diff --git a/e2e/go.mod b/e2e/go.mod index 4428ddf021..5fae598be7 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -6,17 +6,23 @@ require ( github.com/authzed/authzed-go v0.3.1-0.20211220220442-a36f72252b43 github.com/authzed/grpcutil v0.0.0-20211020204402-aba1876830e6 github.com/authzed/spicedb v0.0.0 + github.com/brianvoe/gofakeit/v6 v6.10.0 + github.com/ecordell/optgen v0.0.5-0.20211217170453-18cdce036e35 github.com/jackc/pgtype v1.9.1 github.com/jackc/pgx/v4 v4.14.1 github.com/stretchr/testify v1.7.0 + golang.org/x/tools v0.1.8 google.golang.org/grpc v1.42.0 + mvdan.cc/gofumpt v0.2.1 ) require ( github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect + github.com/dave/jennifer v1.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.6 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -25,14 +31,15 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.2.0 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/mod v0.5.1 // indirect golang.org/x/net v0.0.0-20211104170005-ce137452f963 // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/e2e/go.sum b/e2e/go.sum index 80d8907429..d277524f56 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -81,6 +81,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/brianvoe/gofakeit/v6 v6.10.0 h1:0lZpqKzY2xVfjmCQBn9g9+SHIGg58SX+vu/ejuSVGMc= +github.com/brianvoe/gofakeit/v6 v6.10.0/go.mod h1:palrJUk4Fyw38zIFB/uBZqsgzW5VsNllhHKKwAebzew= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= @@ -123,6 +125,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw= github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -137,6 +140,7 @@ github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05b github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/ecordell/optgen v0.0.5-0.20211217170453-18cdce036e35 h1:BWkgCFLPOJXWYvUCfQa7ArWzX22Jiw0erbnzCMZI2rc= github.com/ecordell/optgen v0.0.5-0.20211217170453-18cdce036e35/go.mod h1:bAPkLVWcBlTX5EkXW0UTPRj3+yjq2I6VLgH8OasuQEM= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -158,6 +162,8 @@ github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0C github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -389,6 +395,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -464,6 +472,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -498,6 +508,9 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -678,6 +691,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -842,6 +856,7 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -928,6 +943,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1082,6 +1098,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1110,6 +1127,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mvdan.cc/gofumpt v0.2.1 h1:7jakRGkQcLAJdT+C8Bwc9d0BANkVPSkHZkzNv07pJAs= +mvdan.cc/gofumpt v0.2.1/go.mod h1:a/rvZPhsNaedOJBzqRD9omnwVwHZsBdJirXHa9Gh9Ig= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/e2e/newenemy/newenemy_test.go b/e2e/newenemy/newenemy_test.go index a1841e1cfd..958072f227 100644 --- a/e2e/newenemy/newenemy_test.go +++ b/e2e/newenemy/newenemy_test.go @@ -2,34 +2,57 @@ package newenemy import ( "context" + "database/sql" + "flag" "fmt" - "io" + "log" "math" "math/rand" "os" + "strings" "testing" + "text/template" "time" v0 "github.com/authzed/authzed-go/proto/authzed/api/v0" "github.com/authzed/authzed-go/proto/authzed/api/v1alpha1" "github.com/authzed/spicedb/pkg/zookie" + "github.com/jackc/pgtype" + "github.com/jackc/pgx/v4" "github.com/stretchr/testify/require" "github.com/authzed/spicedb/e2e" "github.com/authzed/spicedb/e2e/cockroach" + "github.com/authzed/spicedb/e2e/generator" "github.com/authzed/spicedb/e2e/spice" ) -const schema = ` -definition user {} -definition resource { - relation direct: user - relation excluded: user +type SchemaData struct { + Prefixes []string +} + +const schemaText = ` +{{ range .Prefixes }} +definition {{.}}/user {} +definition {{.}}/resource { + relation direct: {{.}}/user + relation excluded: {{.}}/user permission allowed = direct - excluded } +{{ end }} ` -var testCtx context.Context +const ( + objIDRegex = "[a-zA-Z0-9_][a-zA-Z0-9/_-]{0,127}" + namespacePrefixRegex = "[a-z][a-z0-9_]{2,62}[a-z0-9]" +) + +var ( + maxIterations = flag.Int("max-iterations", 1000, "iteration cap for statistic-based tests (0 for no limit)") + + schemaTpl = template.Must(template.New("schema").Parse(schemaText)) + testCtx context.Context +) func TestMain(m *testing.M) { var cancel context.CancelFunc @@ -45,41 +68,47 @@ const ( dbName = "spicedbnetest" ) -func startCluster(ctx context.Context, t testing.TB) cockroach.Cluster { +func initializeTestCRDBCluster(ctx context.Context, t testing.TB) cockroach.Cluster { require := require.New(t) - fmt.Println("starting cockroach...") + t.Log("starting cockroach...") crdbCluster := cockroach.NewCluster(3) for _, c := range crdbCluster { require.NoError(c.Start(ctx)) } - fmt.Println("initializing crdb...") - crdbCluster.Init(ctx, os.Stdout, os.Stdout) - require.NoError(crdbCluster.SQL(ctx, os.Stdout, os.Stdout, + t.Log("initializing crdb...") + tlog := e2e.NewTLog(t) + crdbCluster.Init(ctx, tlog, tlog) + require.NoError(crdbCluster.SQL(ctx, tlog, tlog, fmt.Sprintf(createDb, dbName), )) - fmt.Println("migrating...") - require.NoError(spice.MigrateHead(ctx, "cockroachdb", crdbCluster[0].ConnectionString(dbName))) + t.Log("migrating...") + require.NoError(spice.MigrateHead(ctx, tlog, "cockroachdb", crdbCluster[0].ConnectionString(dbName))) - fmt.Println("attempting to connect...") - require.NoError(crdbCluster[2].Connect(ctx, os.Stdout, dbName)) + t.Log("attempting to connect...") + require.NoError(crdbCluster[2].Connect(ctx, tlog, dbName)) + require.NoError(crdbCluster[1].Connect(ctx, tlog, dbName)) + require.NoError(crdbCluster[0].Connect(ctx, tlog, dbName)) return crdbCluster } func TestNoNewEnemy(t *testing.T) { require := require.New(t) + rand.Seed(time.Now().UnixNano()) ctx, cancel := context.WithCancel(testCtx) defer cancel() - crdb := startCluster(ctx, t) + crdb := initializeTestCRDBCluster(ctx, t) + + tlog := e2e.NewTLog(t) t.Log("starting vulnerable spicedb...") vulnerableSpiceDb := spice.NewClusterFromCockroachCluster(crdb, spice.WithDbName(dbName)) - require.NoError(vulnerableSpiceDb.Start(ctx, os.Stdout, "vulnerable", + require.NoError(vulnerableSpiceDb.Start(ctx, tlog, "vulnerable", "--datastore-tx-overlap-strategy=insecure")) - require.NoError(vulnerableSpiceDb.Connect(ctx, os.Stdout)) + require.NoError(vulnerableSpiceDb.Connect(ctx, tlog)) t.Log("start protected spicedb cluster") protectedSpiceDb := spice.NewClusterFromCockroachCluster(crdb, @@ -89,36 +118,40 @@ func TestNoNewEnemy(t *testing.T) { spice.WithMetricsPort(9100), spice.WithDashboardPort(8100), spice.WithDbName(dbName)) - require.NoError(protectedSpiceDb.Start(ctx, os.Stdout, "protected")) - require.NoError(protectedSpiceDb.Connect(ctx, os.Stdout)) + require.NoError(protectedSpiceDb.Start(ctx, tlog, "protected")) + require.NoError(protectedSpiceDb.Connect(ctx, tlog)) t.Log("configure small ranges, single replicas, short ttl") - require.NoError(crdb.SQL(ctx, os.Stdout, os.Stdout, + require.NoError(crdb.SQL(ctx, tlog, tlog, fmt.Sprintf(setSmallRanges, dbName), )) - t.Log("modifying time") - timeDelay := 100 * time.Millisecond - require.NoError(crdb.TimeDelay(ctx, e2e.MustFile(ctx, t, "timeattack.log"), 1, -1*timeDelay)) + t.Log("fill with schemas to span multiple ranges") + // 4000 is larger than we need to span all three nodes, but a higher number + // seems to make the test converge faster + schemaData := generateSchemaData(4000, 500) + require.NoError(fillSchema(t, schemaData, vulnerableSpiceDb[1].Client().V1Alpha1().Schema())) - t.Log("modifying network") - networkDelay := timeDelay / 2 - require.NoError(crdb.NetworkDelay(ctx, e2e.MustFile(ctx, t, "netattack.log"), 1, networkDelay)) + t.Log("determining a prefix with a leader on the slow node") + slowNodeId, err := crdb[1].NodeID(testCtx) + require.NoError(err) + slowPrefix := prefixForNode(ctx, crdb[1].Conn(), schemaData, slowNodeId) - t.Log("create initial test schema") - require.NoError(initSchema(vulnerableSpiceDb[0].Client().V1Alpha1().Schema())) + t.Logf("using prefix %s for slow node %d", slowPrefix, slowNodeId) t.Log("filling with data to span multiple ranges") - rand.Seed(time.Now().UnixNano()) - fill(require, vulnerableSpiceDb[0].Client().V0().ACL(), 4000, 100) + fill(t, vulnerableSpiceDb[0].Client().V0().ACL(), slowPrefix, 4000, 1000) + + t.Log("modifying time") + require.NoError(crdb.TimeDelay(ctx, e2e.MustFile(ctx, t, "timeattack.log"), 1, -200*time.Millisecond)) const sampleSize = 5 samples := make([]int, sampleSize) for i := 0; i < sampleSize; i++ { t.Log(i, "check vulnerability with mitigations disabled") - checkCtx, checkCancel := context.WithTimeout(ctx, 5*time.Minute) - protected, attempts := checkNoNewEnemy(checkCtx, t, vulnerableSpiceDb, -1) + checkCtx, checkCancel := context.WithTimeout(ctx, 30*time.Minute) + protected, attempts := checkNoNewEnemy(checkCtx, t, schemaData, slowNodeId, crdb, vulnerableSpiceDb, -1) require.NotNil(protected, "unable to determine if spicedb displays newenemy when mitigations are disabled within the time limit") require.False(*protected) checkCancel() @@ -139,22 +172,28 @@ func TestNoNewEnemy(t *testing.T) { samplestddev := stddev / math.Sqrt(float64(sampleSize)) // how many iterations do we need to get > 3sigma from the mean? - // cap max_iterations to control test runtime - const max_iterations = 100 - iterations := int(math.Min(max_iterations, math.Ceil(3*stddev*samplestddev+mean))) + // cap maxIterations to control test runtime. + iterations := int(math.Ceil(3*stddev*samplestddev + mean)) + if *maxIterations != 0 && *maxIterations < iterations { + iterations = *maxIterations + } t.Logf("check spicedb is protected after %d attempts", iterations) - protected, _ := checkNoNewEnemy(ctx, t, protectedSpiceDb, iterations) + protected, _ := checkNoNewEnemy(ctx, t, schemaData, slowNodeId, crdb, protectedSpiceDb, iterations) require.NotNil(protected, "unable to determine if spicedb is protected within the time limit") require.True(*protected, "protection is enabled, but newenemy detected") } // checkNoNewEnemy returns true if the service is protected, false if it is vulnerable, and nil if we couldn't determine -func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, candidateCount int) (*bool, int) { +func checkNoNewEnemy(ctx context.Context, t testing.TB, schemaData []SchemaData, slowNodeId int, crdb cockroach.Cluster, spicedb spice.Cluster, candidateCount int) (*bool, int) { var attempts int + + prefix := prefixForNode(ctx, crdb[1].Conn(), schemaData, slowNodeId) + objIdGenerator := generator.NewUniqueGenerator(objIDRegex) + for { attempts++ - directs, excludes := generateTuples(1) + directs, excludes := generateTuples(prefix, 1, objIdGenerator) // write to node 1 r1, err := spicedb[0].Client().V0().ACL().Write(testCtx, &v0.WriteRequest{ @@ -165,6 +204,11 @@ func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, c continue } + // the first write has to read the namespaces from the second node, + // which will resync the timestamps. sleeping allows the clocks to get + // back out of sync + time.Sleep(100 * time.Millisecond) + // write to node 2 (clock is behind) r2, err := spicedb[1].Client().V0().ACL().Write(testCtx, &v0.WriteRequest{ Updates: []*v0.RelationTupleUpdate{directs[0]}, @@ -174,10 +218,10 @@ func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, c continue } - canHas, err := spicedb[2].Client().V0().ACL().Check(context.Background(), &v0.CheckRequest{ + canHas, err := spicedb[1].Client().V0().ACL().Check(context.Background(), &v0.CheckRequest{ TestUserset: &v0.ObjectAndRelation{ - Namespace: "resource", - ObjectId: "thegoods", + Namespace: directs[0].Tuple.ObjectAndRelation.Namespace, + ObjectId: directs[0].Tuple.ObjectAndRelation.ObjectId, Relation: "allowed", }, User: directs[0].Tuple.GetUser(), @@ -191,7 +235,21 @@ func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, c t.Log("service is subject to the new enemy problem") } - analyzeCalls(os.Stdout, r1.GetRevision(), r2.GetRevision()) + r1leader, r2leader := getLeaderNode(testCtx, crdb[1].Conn(), excludes[0].Tuple), getLeaderNode(testCtx, crdb[1].Conn(), directs[0].Tuple) + ns1Leader := getLeaderNodeForNamespace(testCtx, crdb[1].Conn(), excludes[0].Tuple.ObjectAndRelation.Namespace) + ns2Leader := getLeaderNodeForNamespace(testCtx, crdb[1].Conn(), excludes[0].Tuple.User.GetUserset().Namespace) + z1, _ := zookie.DecodeRevision(r1.GetRevision()) + z2, _ := zookie.DecodeRevision(r2.GetRevision()) + t.Log(z1, z2, z1.Sub(z2).String(), r1leader, r2leader, ns1Leader, ns2Leader) + + if ns1Leader != slowNodeId || ns2Leader != slowNodeId { + t.Log("namespace leader changed, pick new prefix and fill") + prefix = prefixForNode(ctx, crdb[1].Conn(), schemaData, slowNodeId) + // need to fill new prefix + t.Log("filling with data to span multiple ranges for prefix ", prefix) + fill(t, spicedb[0].Client().V0().ACL(), prefix, 4000, 1000) + continue + } if canHas.IsMember { t.Log("service is subject to the new enemy problem") @@ -199,6 +257,11 @@ func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, c return &protected, attempts } + if !canHas.IsMember && z1.Sub(z2).IsPositive() { + t.Log("error in test, object id has been re-used.") + continue + } + // if we find causal reversals, but no newenemy, assume we're protected if candidateCount > 0 && attempts >= candidateCount { t.Log(candidateCount, "(would be) causal reversals with no new enemy detected") @@ -206,59 +269,75 @@ func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, c return &protected, attempts } + if attempts > 1000 { + t.Log("trying with a new prefix") + attempts = 0 + prefix = prefixForNode(ctx, crdb[1].Conn(), schemaData, slowNodeId) + t.Log("filling with data to span multiple ranges for prefix ", prefix) + fill(t, spicedb[0].Client().V0().ACL(), prefix, 4000, 1000) + continue + } + select { case <-ctx.Done(): return nil, attempts default: continue } + + // allow clocks to desync + time.Sleep(100 * time.Millisecond) } } func BenchmarkBatchWrites(b *testing.B) { ctx, cancel := context.WithCancel(testCtx) defer cancel() - crdb := startCluster(ctx, b) + crdb := initializeTestCRDBCluster(ctx, b) spicedb := spice.NewClusterFromCockroachCluster(crdb, spice.WithDbName(dbName)) - require.NoError(b, spicedb.Start(ctx, os.Stdout, "")) - require.NoError(b, spicedb.Connect(ctx, os.Stdout)) + tlog := e2e.NewTLog(b) + require.NoError(b, spicedb.Start(ctx, tlog, "")) + require.NoError(b, spicedb.Connect(ctx, tlog)) - exludes, directs := generateTuples(b.N * 20) + directs, excludes := generateTuples("", b.N*20, generator.NewUniqueGenerator(objIDRegex)) b.ResetTimer() _, err := spicedb[0].Client().V0().ACL().Write(testCtx, &v0.WriteRequest{ - Updates: exludes, + Updates: excludes, }) if err != nil { - fmt.Println(err) + b.Log(err) } _, err = spicedb[0].Client().V0().ACL().Write(testCtx, &v0.WriteRequest{ Updates: directs, }) if err != nil { - fmt.Println(err) + b.Log(err) } } func BenchmarkConflictingTupleWrites(b *testing.B) { ctx, cancel := context.WithCancel(testCtx) defer cancel() - crdb := startCluster(ctx, b) + crdb := initializeTestCRDBCluster(ctx, b) spicedb := spice.NewClusterFromCockroachCluster(crdb, spice.WithDbName(dbName)) - require.NoError(b, spicedb.Start(ctx, os.Stdout, "")) - require.NoError(b, spicedb.Connect(ctx, os.Stdout)) + tlog := e2e.NewTLog(b) + require.NoError(b, spicedb.Start(ctx, tlog, "")) + require.NoError(b, spicedb.Connect(ctx, tlog)) // fill with tuples to ensure we span multiple ranges - fill(require.New(b), spicedb[0].Client().V0().ACL(), 2000, 100) + fill(b, spicedb[0].Client().V0().ACL(), "", 2000, 100) b.ResetTimer() - checkNoNewEnemy(ctx, b, spicedb, b.N) + checkNoNewEnemy(ctx, b, generateSchemaData(1, 1), 1, crdb, spicedb, b.N) } -func fill(require *require.Assertions, client v0.ACLServiceClient, fillerCount, batchSize int) { - directs, excludes := generateTuples(fillerCount) +func fill(t testing.TB, client v0.ACLServiceClient, prefix string, fillerCount, batchSize int) { + t.Log("filling prefix", prefix) + require := require.New(t) + directs, excludes := generateTuples(prefix, fillerCount, generator.NewUniqueGenerator(objIDRegex)) for i := 0; i < fillerCount/batchSize; i++ { - fmt.Println("filling ", i*batchSize, "to", (i+1)*batchSize) + t.Log("filling", i*batchSize, "to", (i+1)*batchSize) _, err := client.Write(testCtx, &v0.WriteRequest{ Updates: excludes[i*batchSize : (i+1)*batchSize], }) @@ -270,29 +349,77 @@ func fill(require *require.Assertions, client v0.ACLServiceClient, fillerCount, } } -func initSchema(schemaClient v1alpha1.SchemaServiceClient) error { - _, err := schemaClient.WriteSchema(context.Background(), &v1alpha1.WriteSchemaRequest{ - Schema: schema, - }) - return err +func fillSchema(t testing.TB, data []SchemaData, schemaClient v1alpha1.SchemaServiceClient) error { + var b strings.Builder + batchSize := len(data[0].Prefixes) + for i, d := range data { + t.Logf("filling %d to %d", i*batchSize, (i+1)*batchSize) + b.Reset() + err := schemaTpl.Execute(&b, d) + if err != nil { + return err + } + _, err = schemaClient.WriteSchema(context.Background(), &v1alpha1.WriteSchemaRequest{ + Schema: b.String(), + }) + if err != nil { + return err + } + } + return nil +} + +// prefixForNode finds a prefix with namespace leaders on the node id +func prefixForNode(ctx context.Context, conn *pgx.Conn, data []SchemaData, node int) string { + for { + // get a random prefix + d := data[rand.Intn(len(data))] + candidate := d.Prefixes[rand.Intn(len(d.Prefixes))] + ns1 := candidate + "/user" + ns2 := candidate + "/resource" + leader1 := getLeaderNodeForNamespace(ctx, conn, ns1) + leader2 := getLeaderNodeForNamespace(ctx, conn, ns2) + if leader1 == leader2 && leader1 == node { + return candidate + } + select { + case <-ctx.Done(): + return "" + default: + continue + } + } +} + +func generateSchemaData(n int, batchSize int) (data []SchemaData) { + data = make([]SchemaData, 0, n/batchSize) + prefixGenerator := generator.NewUniqueGenerator(namespacePrefixRegex) + for i := 0; i < n/batchSize; i++ { + schema := SchemaData{Prefixes: make([]string, 0, batchSize)} + for j := i * batchSize; j < (i+1)*batchSize; j++ { + schema.Prefixes = append(schema.Prefixes, prefixGenerator.Next()) + } + data = append(data, schema) + } + return } -func generateTuples(n int) (directs []*v0.RelationTupleUpdate, excludes []*v0.RelationTupleUpdate) { +func generateTuples(prefix string, n int, objIdGenerator *generator.UniqueGenerator) (directs []*v0.RelationTupleUpdate, excludes []*v0.RelationTupleUpdate) { directs = make([]*v0.RelationTupleUpdate, 0, n) excludes = make([]*v0.RelationTupleUpdate, 0, n) for i := 0; i < n; i++ { user := &v0.User{ UserOneof: &v0.User_Userset{ Userset: &v0.ObjectAndRelation{ - Namespace: "user", - ObjectId: randSeq(16), + Namespace: prefix + "/user", + ObjectId: objIdGenerator.Next(), Relation: "...", }, }, } tupleExclude := &v0.RelationTuple{ ObjectAndRelation: &v0.ObjectAndRelation{ - Namespace: "resource", + Namespace: prefix + "/resource", ObjectId: "thegoods", Relation: "excluded", }, @@ -300,7 +427,7 @@ func generateTuples(n int) (directs []*v0.RelationTupleUpdate, excludes []*v0.Re } tupleDirect := &v0.RelationTuple{ ObjectAndRelation: &v0.ObjectAndRelation{ - Namespace: "resource", + Namespace: prefix + "/resource", ObjectId: "thegoods", Relation: "direct", }, @@ -318,26 +445,52 @@ func generateTuples(n int) (directs []*v0.RelationTupleUpdate, excludes []*v0.Re return } -// after we've checked, analyze the previous calls -func analyzeCalls(out io.Writer, r1, r2 *v0.Zookie) { - z1, _ := zookie.DecodeRevision(r1) - z2, _ := zookie.DecodeRevision(r2) - - // the best we can do when mitigations are enabled is guess that timestamps - // with the same nanosecond timestamp were protected - if z2.GreaterThan(z1) && z2.IntPart() == z1.IntPart() { - fmt.Fprintln(out, "candidate found") +// getLeaderNode returns the node with the lease leader for the range containing the tuple +func getLeaderNode(ctx context.Context, conn *pgx.Conn, tuple *v0.RelationTuple) int { + t := tuple + rows, err := conn.Query(ctx, "SHOW RANGE FROM TABLE relation_tuple FOR ROW ($1::text,$2::text,$3::text,$4::text,$5::text,$6::text)", + t.ObjectAndRelation.Namespace, + t.ObjectAndRelation.ObjectId, + t.ObjectAndRelation.Relation, + t.User.GetUserset().Namespace, + t.User.GetUserset().ObjectId, + t.User.GetUserset().Relation, + ) + defer rows.Close() + if err != nil { + log.Fatalf("failed to exec: %v", err) } - - fmt.Fprintln(out, z1, z2, z1.Sub(z2).String()) + return leaderFromRangeRow(rows) } -var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") +// getLeaderNodeForNamespace returns the node with the lease leader for the range containing the namespace +func getLeaderNodeForNamespace(ctx context.Context, conn *pgx.Conn, namespace string) int { + rows, err := conn.Query(ctx, "SHOW RANGE FROM TABLE namespace_config FOR ROW ($1::text)", + namespace, + ) + defer rows.Close() + if err != nil { + log.Fatalf("failed to exec: %v", err) + } + return leaderFromRangeRow(rows) +} -func randSeq(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] +func leaderFromRangeRow(rows pgx.Rows) int { + var ( + startKey sql.NullString + endKey sql.NullString + rangeID int + leaseHolder int + leaseHoldeLocality sql.NullString + replicas pgtype.Int8Array + replicaLocalities pgtype.TextArray + ) + + for rows.Next() { + if err := rows.Scan(&startKey, &endKey, &rangeID, &leaseHolder, &leaseHoldeLocality, &replicas, &replicaLocalities); err != nil { + panic(err) + } + break } - return string(b) + return leaseHolder } diff --git a/e2e/spice/spicedb.go b/e2e/spice/spicedb.go index 5fbf11966c..3eeeadf61f 100644 --- a/e2e/spice/spicedb.go +++ b/e2e/spice/spicedb.go @@ -5,38 +5,38 @@ import ( "fmt" "io" "net" - "os" "strconv" "time" "github.com/authzed/grpcutil" - "github.com/authzed/spicedb/e2e" "google.golang.org/grpc" + "github.com/authzed/spicedb/e2e" "github.com/authzed/spicedb/e2e/cockroach" ) -//go:generate go run github.com/ecordell/optgen -output spicedb_options.go . SpiceDb +//go:generate go run github.com/ecordell/optgen -output spicedb_options.go . Node // Node represents a single instance of spicedb started via exec type Node struct { - ID string - PresharedKey string - Datastore string - DbName string - URI string - GrpcPort int - HttpPort int - DispatchPort int - MetricsPort int - DashboardPort int - Pid int - Cancel context.CancelFunc - client e2e.Client + ID string + PresharedKey string + Datastore string + DbName string + URI string + GrpcPort int + HttpPort int + DispatchPort int + MetricsPort int + DashboardPort int + HedgingEnabled bool + Pid int + Cancel context.CancelFunc + client e2e.Client } // WithTestDefaults sets the default values for Node -func WithTestDefaults(opts ...SpiceDbOption) SpiceDbOption { +func WithTestDefaults(opts ...NodeOption) NodeOption { return func(s *Node) { for _, o := range opts { o(s) @@ -76,6 +76,7 @@ func (s *Node) Start(ctx context.Context, logprefix string, args ...string) erro "./spicedb", "serve", "--log-level=debug", + fmt.Sprintf("--datastore-request-hedging=%t", s.HedgingEnabled), "--grpc-preshared-key=" + s.PresharedKey, "--datastore-engine=" + s.Datastore, "--datastore-conn-uri=" + s.URI, @@ -132,11 +133,11 @@ type Cluster []*Node // NewClusterFromCockroachCluster creates a spicedb instance for every // cockroach instance, with each spicedb configured to talk to the corresponding // cockraoch node. -func NewClusterFromCockroachCluster(c cockroach.Cluster, opts ...SpiceDbOption) Cluster { +func NewClusterFromCockroachCluster(c cockroach.Cluster, opts ...NodeOption) Cluster { ss := make([]*Node, 0, len(c)) // the prototypical node will be used to generate a set of nodes - proto := NewSpiceDbWithOptions(WithTestDefaults(opts...)) + proto := NewNodeWithOptions(WithTestDefaults(opts...)) for i := 0; i < len(c); i++ { ss = append(ss, &Node{ @@ -188,9 +189,9 @@ func (c *Cluster) Connect(ctx context.Context, out io.Writer) error { } // MigrateHead migrates a Datastore to the latest revision defined in spicedb -func MigrateHead(ctx context.Context, datastore, uri string) error { +func MigrateHead(ctx context.Context, out io.Writer, datastore, uri string) error { for i := 0; i < 5; i++ { - if err := e2e.Run(ctx, os.Stdout, os.Stderr, + if err := e2e.Run(ctx, out, out, "./spicedb", "migrate", "head", "--datastore-engine="+datastore, "--datastore-conn-uri="+uri, diff --git a/e2e/spice/spicedb_options.go b/e2e/spice/spicedb_options.go index 4d980cced5..367daf214c 100644 --- a/e2e/spice/spicedb_options.go +++ b/e2e/spice/spicedb_options.go @@ -3,105 +3,112 @@ package spice import "context" -type SpiceDbOption func(s *Node) +type NodeOption func(n *Node) -// NewSpiceDbWithOptions creates a new Node with the passed in options set -func NewSpiceDbWithOptions(opts ...SpiceDbOption) *Node { - s := &Node{} +// NewNodeWithOptions creates a new Node with the passed in options set +func NewNodeWithOptions(opts ...NodeOption) *Node { + n := &Node{} for _, o := range opts { - o(s) + o(n) } - return s + return n } -// SpiceDbWithOptions configures an existing Node with the passed in options set -func SpiceDbWithOptions(s *Node, opts ...SpiceDbOption) *Node { +// NodeWithOptions configures an existing Node with the passed in options set +func NodeWithOptions(n *Node, opts ...NodeOption) *Node { for _, o := range opts { - o(s) + o(n) } - return s + return n } -// WithId returns an option that can set ID on a Node -func WithId(id string) SpiceDbOption { - return func(s *Node) { - s.ID = id +// WithID returns an option that can set ID on a Node +func WithID(iD string) NodeOption { + return func(n *Node) { + n.ID = iD } } // WithPresharedKey returns an option that can set PresharedKey on a Node -func WithPresharedKey(presharedKey string) SpiceDbOption { - return func(s *Node) { - s.PresharedKey = presharedKey +func WithPresharedKey(presharedKey string) NodeOption { + return func(n *Node) { + n.PresharedKey = presharedKey } } // WithDatastore returns an option that can set Datastore on a Node -func WithDatastore(datastore string) SpiceDbOption { - return func(s *Node) { - s.Datastore = datastore +func WithDatastore(datastore string) NodeOption { + return func(n *Node) { + n.Datastore = datastore } } // WithDbName returns an option that can set DbName on a Node -func WithDbName(dbName string) SpiceDbOption { - return func(s *Node) { - s.DbName = dbName +func WithDbName(dbName string) NodeOption { + return func(n *Node) { + n.DbName = dbName } } -// WithUri returns an option that can set URI on a Node -func WithUri(uri string) SpiceDbOption { - return func(s *Node) { - s.URI = uri +// WithURI returns an option that can set URI on a Node +func WithURI(uRI string) NodeOption { + return func(n *Node) { + n.URI = uRI } } // WithGrpcPort returns an option that can set GrpcPort on a Node -func WithGrpcPort(grpcPort int) SpiceDbOption { - return func(s *Node) { - s.GrpcPort = grpcPort +func WithGrpcPort(grpcPort int) NodeOption { + return func(n *Node) { + n.GrpcPort = grpcPort } } -// WithDispatchPort returns an option that can set DispatchPort on a Node -func WithDispatchPort(dispatchPort int) SpiceDbOption { - return func(s *Node) { - s.DispatchPort = dispatchPort +// WithHttpPort returns an option that can set HttpPort on a Node +func WithHttpPort(httpPort int) NodeOption { + return func(n *Node) { + n.HttpPort = httpPort } } -// WithHttpPort returns an option that can set HttpPort on a Node -func WithHttpPort(httpPort int) SpiceDbOption { - return func(s *Node) { - s.HttpPort = httpPort +// WithDispatchPort returns an option that can set DispatchPort on a Node +func WithDispatchPort(dispatchPort int) NodeOption { + return func(n *Node) { + n.DispatchPort = dispatchPort } } // WithMetricsPort returns an option that can set MetricsPort on a Node -func WithMetricsPort(metricsPort int) SpiceDbOption { - return func(s *Node) { - s.MetricsPort = metricsPort +func WithMetricsPort(metricsPort int) NodeOption { + return func(n *Node) { + n.MetricsPort = metricsPort } } // WithDashboardPort returns an option that can set DashboardPort on a Node -func WithDashboardPort(dashboardPort int) SpiceDbOption { - return func(s *Node) { - s.DashboardPort = dashboardPort +func WithDashboardPort(dashboardPort int) NodeOption { + return func(n *Node) { + n.DashboardPort = dashboardPort + } +} + +// WithHedgingEnabled returns an option that can set HedgingEnabled on a Node +func WithHedgingEnabled(hedgingEnabled bool) NodeOption { + return func(n *Node) { + n.HedgingEnabled = hedgingEnabled } } // WithPid returns an option that can set Pid on a Node -func WithPid(pid int) SpiceDbOption { - return func(s *Node) { - s.Pid = pid +func WithPid(pid int) NodeOption { + return func(n *Node) { + n.Pid = pid } } // WithCancel returns an option that can set Cancel on a Node -func WithCancel(cancel context.CancelFunc) SpiceDbOption { - return func(s *Node) { - s.Cancel = cancel +func WithCancel(cancel context.CancelFunc) NodeOption { + return func(n *Node) { + n.Cancel = cancel } } diff --git a/e2e/tools.go b/e2e/tools.go new file mode 100644 index 0000000000..7248233d04 --- /dev/null +++ b/e2e/tools.go @@ -0,0 +1,10 @@ +//go:build tools +// +build tools + +package tools + +import ( + _ "github.com/ecordell/optgen" + _ "golang.org/x/tools/cmd/stringer" + _ "mvdan.cc/gofumpt" +) diff --git a/e2e/util.go b/e2e/util.go index 6397f01ead..9ecc0d458a 100644 --- a/e2e/util.go +++ b/e2e/util.go @@ -8,15 +8,18 @@ import ( // TLogger wraps a testing.TB and makes it conform to io.Writer type TLogger struct { testing.TB + skip int } // Write satisfied io.Writer func (t *TLogger) Write(p []byte) (int, error) { + t.Helper() t.Log(string(p)) return len(p), nil } -// TLog returns a TLogger -func TLog(t testing.TB) io.Writer { +// NewTLog returns a TLogger +func NewTLog(t testing.TB) io.Writer { + t.Helper() return &TLogger{TB: t} } diff --git a/internal/namespace/manager.go b/internal/namespace/manager.go index b9e9d12227..235144abda 100644 --- a/internal/namespace/manager.go +++ b/internal/namespace/manager.go @@ -29,7 +29,7 @@ type Manager interface { // ReadNamespaceAndTypes reads a namespace definition, version, and type system and returns it if found. ReadNamespaceAndTypes(ctx context.Context, nsName string, revision decimal.Decimal) (*v0.NamespaceDefinition, *NamespaceTypeSystem, error) - // Closes the namespace manager, disposing of any resources. + // Close closes the namespace manager, disposing of any resources. // // NOTE: Should *not* call Close on the datastore. Close() error