diff options
Diffstat (limited to 'test')
35 files changed, 848 insertions, 144 deletions
diff --git a/test/benchmarks/base/sysbench_test.go b/test/benchmarks/base/sysbench_test.go index 6fb813640..39ced3dab 100644 --- a/test/benchmarks/base/sysbench_test.go +++ b/test/benchmarks/base/sysbench_test.go @@ -71,8 +71,15 @@ func BenchmarkSysbench(b *testing.B) { defer machine.CleanUp() for _, tc := range testCases { - b.Run(tc.name, func(b *testing.B) { - + param := tools.Parameter{ + Name: "testname", + Value: tc.name, + } + name, err := tools.ParametersToName(param) + if err != nil { + b.Fatalf("Failed to parse params: %v", err) + } + b.Run(name, func(b *testing.B) { ctx := context.Background() sysbench := machine.GetContainer(ctx, b) defer sysbench.CleanUp(ctx) diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go index 6671a4969..02e67154e 100644 --- a/test/benchmarks/database/redis_test.go +++ b/test/benchmarks/database/redis_test.go @@ -66,7 +66,15 @@ func BenchmarkRedis(b *testing.B) { ctx := context.Background() for _, operation := range operations { - b.Run(operation, func(b *testing.B) { + param := tools.Parameter{ + Name: "operation", + Value: operation, + } + name, err := tools.ParametersToName(param) + if err != nil { + b.Fatalf("Failed to parse paramaters: %v", err) + } + b.Run(name, func(b *testing.B) { server := serverMachine.GetContainer(ctx, b) defer server.CleanUp(ctx) diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go index ef1b8e4ea..56103639d 100644 --- a/test/benchmarks/fs/bazel_test.go +++ b/test/benchmarks/fs/bazel_test.go @@ -21,6 +21,7 @@ import ( "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" + "gvisor.dev/gvisor/test/benchmarks/tools" ) // Note: CleanCache versions of this test require running with root permissions. @@ -46,17 +47,36 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { // Dimensions here are clean/dirty cache (do or don't drop caches) // and if the mount on which we are compiling is a tmpfs/bind mount. benchmarks := []struct { - name string clearCache bool // clearCache drops caches before running. tmpfs bool // tmpfs will run compilation on a tmpfs. }{ - {name: "CleanCache", clearCache: true, tmpfs: false}, - {name: "DirtyCache", clearCache: false, tmpfs: false}, - {name: "CleanCacheTmpfs", clearCache: true, tmpfs: true}, - {name: "DirtyCacheTmpfs", clearCache: false, tmpfs: true}, + {clearCache: true, tmpfs: false}, + {clearCache: false, tmpfs: false}, + {clearCache: true, tmpfs: true}, + {clearCache: false, tmpfs: true}, } for _, bm := range benchmarks { - b.Run(bm.name, func(b *testing.B) { + pageCache := tools.Parameter{ + Name: "page_cache", + Value: "clean", + } + if bm.clearCache { + pageCache.Value = "dirty" + } + + filesystem := tools.Parameter{ + Name: "filesystem", + Value: "bind", + } + if bm.tmpfs { + filesystem.Value = "tmpfs" + } + name, err := tools.ParametersToName(pageCache, filesystem) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + + b.Run(name, func(b *testing.B) { // Grab a container. ctx := context.Background() container := machine.GetContainer(ctx, b) diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go index 65874ed8b..5ca191404 100644 --- a/test/benchmarks/fs/fio_test.go +++ b/test/benchmarks/fs/fio_test.go @@ -67,8 +67,19 @@ func BenchmarkFio(b *testing.B) { for _, fsType := range []mount.Type{mount.TypeBind, mount.TypeTmpfs} { for _, tc := range testCases { - testName := strings.Title(tc.Test) + strings.Title(string(fsType)) - b.Run(testName, func(b *testing.B) { + operation := tools.Parameter{ + Name: "operation", + Value: tc.Test, + } + filesystem := tools.Parameter{ + Name: "filesystem", + Value: string(fsType), + } + name, err := tools.ParametersToName(operation, filesystem) + if err != nil { + b.Fatalf("Failed to parser paramters: %v", err) + } + b.Run(name, func(b *testing.B) { ctx := context.Background() container := machine.GetContainer(ctx, b) defer container.CleanUp(ctx) diff --git a/test/benchmarks/network/httpd_test.go b/test/benchmarks/network/httpd_test.go index 369ab326e..8d7d5f750 100644 --- a/test/benchmarks/network/httpd_test.go +++ b/test/benchmarks/network/httpd_test.go @@ -14,7 +14,7 @@ package network import ( - "fmt" + "strconv" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" @@ -31,33 +31,15 @@ var httpdDocs = map[string]string{ "10Mb": "latin10240k.txt", } -// BenchmarkHttpdConcurrency iterates the concurrency argument and tests -// how well the runtime under test handles requests in parallel. -func BenchmarkHttpdConcurrency(b *testing.B) { - // The test iterates over client concurrency, so set other parameters. - concurrency := []int{1, 25, 50, 100, 1000} - - for _, c := range concurrency { - b.Run(fmt.Sprintf("%d", c), func(b *testing.B) { - hey := &tools.Hey{ - Requests: c * b.N, - Concurrency: c, - Doc: httpdDocs["10Kb"], - } - runHttpd(b, hey, false /* reverse */) - }) - } -} - -// BenchmarkHttpdDocSize iterates over different sized payloads, testing how -// well the runtime handles sending different payload sizes. -func BenchmarkHttpdDocSize(b *testing.B) { +// BenchmarkHttpd iterates over different sized payloads and concurrency, testing +// how well the runtime handles sending different payload sizes. +func BenchmarkHttpd(b *testing.B) { benchmarkHttpdDocSize(b, false /* reverse */) } -// BenchmarkReverseHttpdDocSize iterates over different sized payloads, testing +// BenchmarkReverseHttpd iterates over different sized payloads, testing // how well the runtime handles receiving different payload sizes. -func BenchmarkReverseHttpdDocSize(b *testing.B) { +func BenchmarkReverseHttpd(b *testing.B) { benchmarkHttpdDocSize(b, true /* reverse */) } @@ -65,10 +47,22 @@ func BenchmarkReverseHttpdDocSize(b *testing.B) { // for each size. func benchmarkHttpdDocSize(b *testing.B, reverse bool) { b.Helper() - for name, filename := range httpdDocs { + for size, filename := range httpdDocs { concurrency := []int{1, 25, 50, 100, 1000} for _, c := range concurrency { - b.Run(fmt.Sprintf("%s_%d", name, c), func(b *testing.B) { + fsize := tools.Parameter{ + Name: "filesize", + Value: size, + } + concurrency := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + name, err := tools.ParametersToName(fsize, concurrency) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + b.Run(name, func(b *testing.B) { hey := &tools.Hey{ Requests: c * b.N, Concurrency: c, diff --git a/test/benchmarks/network/nginx_test.go b/test/benchmarks/network/nginx_test.go index 9ec70369b..08565d0b2 100644 --- a/test/benchmarks/network/nginx_test.go +++ b/test/benchmarks/network/nginx_test.go @@ -14,7 +14,7 @@ package network import ( - "fmt" + "strconv" "testing" "gvisor.dev/gvisor/pkg/test/dockerutil" @@ -31,30 +31,6 @@ var nginxDocs = map[string]string{ "10Mb": "latin10240k.txt", } -// BenchmarkNginxConcurrency iterates the concurrency argument and tests -// how well the runtime under test handles requests in parallel. -func BenchmarkNginxConcurrency(b *testing.B) { - concurrency := []int{1, 25, 100, 1000} - for _, c := range concurrency { - for _, tmpfs := range []bool{true, false} { - fs := "Gofer" - if tmpfs { - fs = "Tmpfs" - } - name := fmt.Sprintf("%d_%s", c, fs) - b.Run(name, func(b *testing.B) { - hey := &tools.Hey{ - Requests: c * b.N, - Concurrency: c, - Doc: nginxDocs["10kb"], // see Dockerfile '//images/benchmarks/nginx' and httpd_test. - } - runNginx(b, hey, false /* reverse */, tmpfs /* tmpfs */) - }) - } - - } -} - // BenchmarkNginxDocSize iterates over different sized payloads, testing how // well the runtime handles sending different payload sizes. func BenchmarkNginxDocSize(b *testing.B) { @@ -71,15 +47,32 @@ func BenchmarkReverseNginxDocSize(b *testing.B) { // benchmarkNginxDocSize iterates through all doc sizes, running subbenchmarks // for each size. func benchmarkNginxDocSize(b *testing.B, reverse, tmpfs bool) { - for name, filename := range nginxDocs { + for size, filename := range nginxDocs { concurrency := []int{1, 25, 50, 100, 1000} for _, c := range concurrency { - fs := "Gofer" + fsize := tools.Parameter{ + Name: "filesize", + Value: size, + } + + threads := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + + fs := tools.Parameter{ + Name: "filesystem", + Value: "bind", + } if tmpfs { - fs = "Tmpfs" + fs.Value = "tmpfs" + } + name, err := tools.ParametersToName(fsize, threads, fs) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) } - benchName := fmt.Sprintf("%s_%d_%s", name, c, fs) - b.Run(benchName, func(b *testing.B) { + + b.Run(name, func(b *testing.B) { hey := &tools.Hey{ Requests: c * b.N, Concurrency: c, diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go index 0f4a205b6..254538899 100644 --- a/test/benchmarks/network/node_test.go +++ b/test/benchmarks/network/node_test.go @@ -15,7 +15,7 @@ package network import ( "context" - "fmt" + "strconv" "testing" "time" @@ -31,7 +31,15 @@ import ( func BenchmarkNode(b *testing.B) { concurrency := []int{1, 5, 10, 25} for _, c := range concurrency { - b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) { + param := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + name, err := tools.ParametersToName(param) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + b.Run(name, func(b *testing.B) { hey := &tools.Hey{ Requests: b.N * c, // Requests b.N requests per thread. Concurrency: c, diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go index 67f63f76a..0174ff3f3 100644 --- a/test/benchmarks/network/ruby_test.go +++ b/test/benchmarks/network/ruby_test.go @@ -16,6 +16,7 @@ package network import ( "context" "fmt" + "strconv" "testing" "time" @@ -31,7 +32,15 @@ import ( func BenchmarkRuby(b *testing.B) { concurrency := []int{1, 5, 10, 25} for _, c := range concurrency { - b.Run(fmt.Sprintf("Concurrency%d", c), func(b *testing.B) { + param := tools.Parameter{ + Name: "concurrency", + Value: strconv.Itoa(c), + } + name, err := tools.ParametersToName(param) + if err != nil { + b.Fatalf("Failed to parse parameters: %v", err) + } + b.Run(name, func(b *testing.B) { hey := &tools.Hey{ Requests: b.N * c, // b.N requests per thread. Concurrency: c, diff --git a/test/benchmarks/tools/BUILD b/test/benchmarks/tools/BUILD index e5734d85c..9290830d7 100644 --- a/test/benchmarks/tools/BUILD +++ b/test/benchmarks/tools/BUILD @@ -4,12 +4,14 @@ package(licenses = ["notice"]) go_library( name = "tools", + testonly = 1, srcs = [ "ab.go", "fio.go", "hey.go", "iperf.go", "meminfo.go", + "parser_util.go", "redis.go", "sysbench.go", "tools.go", diff --git a/test/benchmarks/tools/ab.go b/test/benchmarks/tools/ab.go index 4cc9c3bce..d9abf0763 100644 --- a/test/benchmarks/tools/ab.go +++ b/test/benchmarks/tools/ab.go @@ -46,18 +46,21 @@ func (a *ApacheBench) Report(b *testing.B, output string) { b.Logf("failed to parse transferrate: %v", err) } b.ReportMetric(transferRate*1024, "transfer_rate_b/s") // Convert from Kb/s to b/s. + ReportCustomMetric(b, transferRate*1024, "transfer_rate" /*metric name*/, "bytes_per_second" /*unit*/) latency, err := a.parseLatency(output) if err != nil { b.Logf("failed to parse latency: %v", err) } b.ReportMetric(latency/1000, "mean_latency_secs") // Convert from ms to s. + ReportCustomMetric(b, latency/1000, "mean_latency" /*metric name*/, "s" /*unit*/) reqPerSecond, err := a.parseRequestsPerSecond(output) if err != nil { b.Logf("failed to parse requests per second: %v", err) } b.ReportMetric(reqPerSecond, "requests_per_second") + ReportCustomMetric(b, reqPerSecond, "requests_per_second" /*metric name*/, "QPS" /*unit*/) } var transferRateRE = regexp.MustCompile(`Transfer rate:\s+(\d+\.?\d+?)\s+\[Kbytes/sec\]\s+received`) diff --git a/test/benchmarks/tools/fio.go b/test/benchmarks/tools/fio.go index 20000db16..f5f60fa84 100644 --- a/test/benchmarks/tools/fio.go +++ b/test/benchmarks/tools/fio.go @@ -56,13 +56,13 @@ func (f *Fio) Report(b *testing.B, output string) { if err != nil { b.Fatalf("failed to parse bandwidth from %s with: %v", output, err) } - b.ReportMetric(bw, "bandwidth_b/s") // in b/s. + ReportCustomMetric(b, bw, "bandwidth" /*metric name*/, "bytes_per_second" /*unit*/) iops, err := f.parseIOps(output, isRead) if err != nil { b.Fatalf("failed to parse iops from %s with: %v", output, err) } - b.ReportMetric(iops, "iops") + ReportCustomMetric(b, iops, "io_ops" /*metric name*/, "ops_per_second" /*unit*/) } // parseBandwidth reports the bandwidth in b/s. diff --git a/test/benchmarks/tools/hey.go b/test/benchmarks/tools/hey.go index b1e20e356..b8cb938fe 100644 --- a/test/benchmarks/tools/hey.go +++ b/test/benchmarks/tools/hey.go @@ -43,13 +43,13 @@ func (h *Hey) Report(b *testing.B, output string) { if err != nil { b.Fatalf("failed to parse requests per second: %v", err) } - b.ReportMetric(requests, "requests_per_second") + ReportCustomMetric(b, requests, "requests_per_second" /*metric name*/, "QPS" /*unit*/) ave, err := h.parseAverageLatency(output) if err != nil { b.Fatalf("failed to parse average latency: %v", err) } - b.ReportMetric(ave, "average_latency_secs") + ReportCustomMetric(b, ave, "average_latency" /*metric name*/, "s" /*unit*/) } var heyReqPerSecondRE = regexp.MustCompile(`Requests/sec:\s*(\d+\.?\d+?)\s+`) diff --git a/test/benchmarks/tools/iperf.go b/test/benchmarks/tools/iperf.go index df3d9349b..5c4e7125b 100644 --- a/test/benchmarks/tools/iperf.go +++ b/test/benchmarks/tools/iperf.go @@ -42,7 +42,7 @@ func (i *Iperf) Report(b *testing.B, output string) { if err != nil { b.Fatalf("failed to parse bandwitdth from %s: %v", output, err) } - b.ReportMetric(bW*1024, "bandwidth_b/s") // Convert from Kb/s to b/s. + ReportCustomMetric(b, bW*1024, "bandwidth" /*metric name*/, "bytes_per_second" /*unit*/) } // bandwidth parses the Bandwidth number from an iperf report. A sample is below. diff --git a/test/benchmarks/tools/meminfo.go b/test/benchmarks/tools/meminfo.go index 2414a96a7..b5786fe11 100644 --- a/test/benchmarks/tools/meminfo.go +++ b/test/benchmarks/tools/meminfo.go @@ -45,7 +45,7 @@ func (*Meminfo) Report(b *testing.B, before, after string) { b.Fatalf("could not parse before value %s: %v", before, err) } val := 1024 * ((beforeVal - afterVal) / float64(b.N)) - b.ReportMetric(val, "average_container_size_bytes") + ReportCustomMetric(b, val, "average_container_size" /*metric name*/, "bytes" /*units*/) } var memInfoRE = regexp.MustCompile(`MemAvailable:\s*(\d+)\skB\n`) diff --git a/test/benchmarks/tools/parser_util.go b/test/benchmarks/tools/parser_util.go new file mode 100644 index 000000000..a4555c7dd --- /dev/null +++ b/test/benchmarks/tools/parser_util.go @@ -0,0 +1,101 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tools + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "testing" +) + +// Parameter is a test parameter. +type Parameter struct { + Name string + Value string +} + +// Output is parsed and split by these values. Make them illegal in input methods. +// We are constrained on what characters these can be by 1) docker's allowable +// container names, 2) golang allowable benchmark names, and 3) golangs allowable +// charecters in b.ReportMetric calls. +var illegalChars = regexp.MustCompile(`[/\.]`) + +// ParametersToName joins parameters into a string format for parsing. +// It is meant to be used for t.Run() calls in benchmark tools. +func ParametersToName(params ...Parameter) (string, error) { + var strs []string + for _, param := range params { + if illegalChars.MatchString(param.Name) || illegalChars.MatchString(param.Value) { + return "", fmt.Errorf("params Name: %q and Value: %q cannot container '.' or '/'", param.Name, param.Value) + } + strs = append(strs, strings.Join([]string{param.Name, param.Value}, ".")) + } + return strings.Join(strs, "/"), nil +} + +// NameToParameters parses the string created by ParametersToName and returns +// it as a set of Parameters. +// Example: BenchmarkRuby/server_threads.1/doc_size.16KB-6 +// The parameter part of this benchmark is: +// "server_threads.1/doc_size.16KB" (BenchmarkRuby is the name, and 6 is GOMAXPROCS) +// This function will return a slice with two parameters -> +// {Name: server_threads, Value: 1}, {Name: doc_size, Value: 16KB} +func NameToParameters(name string) ([]*Parameter, error) { + var params []*Parameter + for _, cond := range strings.Split(name, "/") { + cs := strings.Split(cond, ".") + switch len(cs) { + case 1: + params = append(params, &Parameter{Name: cond, Value: cond}) + case 2: + params = append(params, &Parameter{Name: cs[0], Value: cs[1]}) + default: + return nil, fmt.Errorf("failed to parse param: %s", cond) + } + } + return params, nil +} + +// ReportCustomMetric reports a metric in a set format for parsing. +func ReportCustomMetric(b *testing.B, value float64, name, unit string) { + if illegalChars.MatchString(name) || illegalChars.MatchString(unit) { + b.Fatalf("name: %q and unit: %q cannot contain '/' or '.'", name, unit) + } + nameUnit := strings.Join([]string{name, unit}, ".") + b.ReportMetric(value, nameUnit) +} + +// Metric holds metric data parsed from a string based on the format +// ReportMetric. +type Metric struct { + Name string + Unit string + Sample float64 +} + +// ParseCustomMetric parses a metric reported with ReportCustomMetric. +func ParseCustomMetric(value, metric string) (*Metric, error) { + sample, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse value: %v", err) + } + nameUnit := strings.Split(metric, ".") + if len(nameUnit) != 2 { + return nil, fmt.Errorf("failed to parse metric: %s", metric) + } + return &Metric{Name: nameUnit[0], Unit: nameUnit[1], Sample: sample}, nil +} diff --git a/test/benchmarks/tools/redis.go b/test/benchmarks/tools/redis.go index c899ae0d4..e35886437 100644 --- a/test/benchmarks/tools/redis.go +++ b/test/benchmarks/tools/redis.go @@ -49,7 +49,7 @@ func (r *Redis) Report(b *testing.B, output string) { if err != nil { b.Fatalf("parsing result %s failed with err: %v", output, err) } - b.ReportMetric(result, r.Operation) // operations per second + ReportCustomMetric(b, result, r.Operation /*metric_name*/, "QPS" /*unit*/) } // parseOperation grabs the metric operations per second from redis-benchmark output. diff --git a/test/benchmarks/tools/sysbench.go b/test/benchmarks/tools/sysbench.go index 6b2f75ca2..7ccacd8ff 100644 --- a/test/benchmarks/tools/sysbench.go +++ b/test/benchmarks/tools/sysbench.go @@ -80,7 +80,7 @@ func (s *SysbenchCPU) Report(b *testing.B, output string) { if err != nil { b.Fatalf("parsing CPU events from %s failed: %v", output, err) } - b.ReportMetric(result, "cpu_events_per_second") + ReportCustomMetric(b, result, "cpu_events" /*metric name*/, "events_per_second" /*unit*/) } var cpuEventsPerSecondRE = regexp.MustCompile(`events per second:\s*(\d*.?\d*)\n`) @@ -144,7 +144,7 @@ func (s *SysbenchMemory) Report(b *testing.B, output string) { if err != nil { b.Fatalf("parsing result %s failed with err: %v", output, err) } - b.ReportMetric(result, "operations_per_second") + ReportCustomMetric(b, result, "memory_operations" /*metric name*/, "ops_per_second" /*unit*/) } var memoryOperationsRE = regexp.MustCompile(`Total\soperations:\s+\d*\s*\((\d*\.\d*)\sper\ssecond\)`) @@ -198,19 +198,19 @@ func (s *SysbenchMutex) Report(b *testing.B, output string) { if err != nil { b.Fatalf("parsing result %s failed with err: %v", output, err) } - b.ReportMetric(result, "average_execution_time_secs") + ReportCustomMetric(b, result, "average_execution_time" /*metric name*/, "s" /*unit*/) result, err = s.parseDeviation(output) if err != nil { b.Fatalf("parsing result %s failed with err: %v", output, err) } - b.ReportMetric(result, "stdev_execution_time_secs") + ReportCustomMetric(b, result, "stddev_execution_time" /*metric name*/, "s" /*unit*/) result, err = s.parseLatency(output) if err != nil { b.Fatalf("parsing result %s failed with err: %v", output, err) } - b.ReportMetric(result/1000, "average_latency_secs") + ReportCustomMetric(b, result/1000, "average_latency" /*metric name*/, "s" /*unit*/) } var executionTimeRE = regexp.MustCompile(`execution time \(avg/stddev\):\s*(\d*.?\d*)/(\d*.?\d*)`) diff --git a/test/iptables/README.md b/test/iptables/README.md index b9f44bd40..28ab195ca 100644 --- a/test/iptables/README.md +++ b/test/iptables/README.md @@ -1,6 +1,6 @@ # iptables Tests -iptables tests are run via `scripts/iptables_test.sh`. +iptables tests are run via `make iptables-tests`. iptables requires raw socket support, so you must add the `--net-raw=true` flag to `/etc/docker/daemon.json` in order to use it. diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go index 398f70ecd..834f7615f 100644 --- a/test/iptables/iptables_test.go +++ b/test/iptables/iptables_test.go @@ -48,13 +48,6 @@ func singleTest(t *testing.T, test TestCase) { } } -// TODO(gvisor.dev/issue/3549): IPv6 NAT support. -func ipv4Test(t *testing.T, test TestCase) { - t.Run("IPv4", func(t *testing.T) { - iptablesTest(t, test, false) - }) -} - func iptablesTest(t *testing.T, test TestCase, ipv6 bool) { if _, ok := Tests[test.Name()]; !ok { t.Fatalf("no test found with name %q. Has it been registered?", test.Name()) @@ -325,66 +318,66 @@ func TestFilterOutputInvertDestination(t *testing.T) { } func TestNATPreRedirectUDPPort(t *testing.T) { - ipv4Test(t, NATPreRedirectUDPPort{}) + singleTest(t, NATPreRedirectUDPPort{}) } func TestNATPreRedirectTCPPort(t *testing.T) { - ipv4Test(t, NATPreRedirectTCPPort{}) + singleTest(t, NATPreRedirectTCPPort{}) } func TestNATPreRedirectTCPOutgoing(t *testing.T) { - ipv4Test(t, NATPreRedirectTCPOutgoing{}) + singleTest(t, NATPreRedirectTCPOutgoing{}) } func TestNATOutRedirectTCPIncoming(t *testing.T) { - ipv4Test(t, NATOutRedirectTCPIncoming{}) + singleTest(t, NATOutRedirectTCPIncoming{}) } func TestNATOutRedirectUDPPort(t *testing.T) { - ipv4Test(t, NATOutRedirectUDPPort{}) + singleTest(t, NATOutRedirectUDPPort{}) } func TestNATOutRedirectTCPPort(t *testing.T) { - ipv4Test(t, NATOutRedirectTCPPort{}) + singleTest(t, NATOutRedirectTCPPort{}) } func TestNATDropUDP(t *testing.T) { - ipv4Test(t, NATDropUDP{}) + singleTest(t, NATDropUDP{}) } func TestNATAcceptAll(t *testing.T) { - ipv4Test(t, NATAcceptAll{}) + singleTest(t, NATAcceptAll{}) } func TestNATOutRedirectIP(t *testing.T) { - ipv4Test(t, NATOutRedirectIP{}) + singleTest(t, NATOutRedirectIP{}) } func TestNATOutDontRedirectIP(t *testing.T) { - ipv4Test(t, NATOutDontRedirectIP{}) + singleTest(t, NATOutDontRedirectIP{}) } func TestNATOutRedirectInvert(t *testing.T) { - ipv4Test(t, NATOutRedirectInvert{}) + singleTest(t, NATOutRedirectInvert{}) } func TestNATPreRedirectIP(t *testing.T) { - ipv4Test(t, NATPreRedirectIP{}) + singleTest(t, NATPreRedirectIP{}) } func TestNATPreDontRedirectIP(t *testing.T) { - ipv4Test(t, NATPreDontRedirectIP{}) + singleTest(t, NATPreDontRedirectIP{}) } func TestNATPreRedirectInvert(t *testing.T) { - ipv4Test(t, NATPreRedirectInvert{}) + singleTest(t, NATPreRedirectInvert{}) } func TestNATRedirectRequiresProtocol(t *testing.T) { - ipv4Test(t, NATRedirectRequiresProtocol{}) + singleTest(t, NATRedirectRequiresProtocol{}) } func TestNATLoopbackSkipsPrerouting(t *testing.T) { - ipv4Test(t, NATLoopbackSkipsPrerouting{}) + singleTest(t, NATLoopbackSkipsPrerouting{}) } func TestInputSource(t *testing.T) { @@ -421,9 +414,9 @@ func TestFilterAddrs(t *testing.T) { } func TestNATPreOriginalDst(t *testing.T) { - ipv4Test(t, NATPreOriginalDst{}) + singleTest(t, NATPreOriginalDst{}) } func TestNATOutOriginalDst(t *testing.T) { - ipv4Test(t, NATOutOriginalDst{}) + singleTest(t, NATOutOriginalDst{}) } diff --git a/test/kubernetes/gvisor-injection-admission-webhook.yaml b/test/kubernetes/gvisor-injection-admission-webhook.yaml new file mode 100644 index 000000000..691f02dda --- /dev/null +++ b/test/kubernetes/gvisor-injection-admission-webhook.yaml @@ -0,0 +1,89 @@ +# Copyright 2020 The gVisor Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +apiVersion: v1 +kind: Namespace +metadata: + name: e2e + labels: + name: e2e +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gvisor-injection-admission-webhook + namespace: e2e +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gvisor-injection-admission-webhook +rules: +- apiGroups: [ admissionregistration.k8s.io ] + resources: [ mutatingwebhookconfigurations ] + verbs: [ create ] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: gvisor-injection-admission-webhook + namespace: e2e +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gvisor-injection-admission-webhook +subjects: +- kind: ServiceAccount + name: gvisor-injection-admission-webhook + namespace: e2e +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gvisor-injection-admission-webhook + namespace: e2e + labels: + app: gvisor-injection-admission-webhook +spec: + replicas: 1 + selector: + matchLabels: + app: gvisor-injection-admission-webhook + template: + metadata: + labels: + app: gvisor-injection-admission-webhook + spec: + containers: + - name: webhook + image: gcr.io/gke-gvisor/gvisor-injection-admission-webhook:54ce9bd + args: + - --log-level=debug + ports: + - containerPort: 8443 + serviceAccountName: gvisor-injection-admission-webhook +--- +kind: Service +apiVersion: v1 +metadata: + name: gvisor-injection-admission-webhook + namespace: e2e +spec: + selector: + app: gvisor-injection-admission-webhook + ports: + - protocol: TCP + port: 443 + targetPort: 8443 diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go index 0073a1361..3c85ebbee 100644 --- a/test/packetimpact/testbench/testbench.go +++ b/test/packetimpact/testbench/testbench.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package testbench is the packetimpact test API. package testbench import ( diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 94731c64b..11db49e39 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -310,8 +310,6 @@ packetimpact_go_test( packetimpact_go_test( name = "ipv6_fragment_reassembly", srcs = ["ipv6_fragment_reassembly_test.go"], - # TODO(b/160919104): Fix netstack then remove the line below. - expect_netstack_failure = True, deps = [ "//pkg/tcpip", "//pkg/tcpip/buffer", diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go index cf881418c..7f7a768d3 100644 --- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go +++ b/test/packetimpact/tests/ipv4_id_uniqueness_test.go @@ -88,7 +88,8 @@ func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) { // this test. Once the socket option is supported, the following call // can be changed to simply assert success. ret, errno := dut.SetSockOptIntWithErrno(context.Background(), t, remoteFD, unix.IPPROTO_IP, linux.IP_MTU_DISCOVER, linux.IP_PMTUDISC_DONT) - if ret == -1 && errno != unix.ENOTSUP { + // Fuchsia will return ENOPROTOPT errno. + if ret == -1 && errno != unix.ENOPROTOOPT { t.Fatalf("failed to set IP_MTU_DISCOVER socket option to IP_PMTUDISC_DONT: %s", errno) } diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index 032ebd04e..248053dc3 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -203,7 +203,6 @@ def syscall_test( tags = platform_tags + tags, ) - # TODO(gvisor.dev/issue/1487): Enable VFS2 overlay tests. if add_overlay: _syscall_test( test = test, @@ -216,6 +215,23 @@ def syscall_test( overlay = True, ) + # TODO(gvisor.dev/issue/4407): Remove tags to enable VFS2 overlay tests. + overlay_vfs2_tags = list(vfs2_tags) + overlay_vfs2_tags.append("manual") + overlay_vfs2_tags.append("noguitar") + overlay_vfs2_tags.append("notap") + _syscall_test( + test = test, + shard_count = shard_count, + size = size, + platform = default_platform, + use_tmpfs = use_tmpfs, + add_uds_tree = add_uds_tree, + tags = platforms[default_platform] + overlay_vfs2_tags, + overlay = True, + vfs2 = True, + ) + if add_hostinet: _syscall_test( test = test, diff --git a/test/runtimes/exclude/java11.csv b/test/runtimes/exclude/java11.csv index f779df8d5..d978baca7 100644 --- a/test/runtimes/exclude/java11.csv +++ b/test/runtimes/exclude/java11.csv @@ -57,6 +57,7 @@ java/nio/channels/SocketChannel/SocketOptionTests.java,b/77965901, java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java,,Fails in Docker java/rmi/activation/Activatable/extLoadedImpl/ext.sh,, java/rmi/transport/checkLeaseInfoLeak/CheckLeaseLeak.java,, +java/security/cert/PolicyNode/GetPolicyQualifiers.java,b/170263154,Kokoro executor cert expired java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker java/text/Format/NumberFormat/CurrencyFormat.java,,Fails in Docker java/util/Calendar/JapaneseEraNameTest.java,, diff --git a/test/runtimes/exclude/nodejs12.4.0.csv b/test/runtimes/exclude/nodejs12.4.0.csv index 749fb9482..ba993814f 100644 --- a/test/runtimes/exclude/nodejs12.4.0.csv +++ b/test/runtimes/exclude/nodejs12.4.0.csv @@ -13,7 +13,7 @@ parallel/test-dns-channel-timeout.js,b/161893056, parallel/test-fs-access.js,, parallel/test-fs-watchfile.js,,Flaky - File already exists error parallel/test-fs-write-stream.js,b/166819807,Flaky -parallel/test-fs-write-stream-double-close,b/166819807,Flaky +parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky parallel/test-http-writable-true-after-close.js,,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2 parallel/test-os.js,b/63997097, diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD index f66371265..fdc6d3173 100644 --- a/test/runtimes/proctor/BUILD +++ b/test/runtimes/proctor/BUILD @@ -5,6 +5,7 @@ package(licenses = ["notice"]) go_binary( name = "proctor", srcs = ["main.go"], + pure = True, visibility = ["//test/runtimes:__pkg__"], deps = ["//test/runtimes/proctor/lib"], ) diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 96a775456..f66a9ceb4 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -286,6 +286,10 @@ syscall_test( ) syscall_test( + test = "//test/syscalls/linux:membarrier_test", +) + +syscall_test( test = "//test/syscalls/linux:memory_accounting_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index d9dbe2267..36b7f1b97 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1079,6 +1079,7 @@ cc_binary( gtest, "//test/util:test_main", "//test/util:test_util", + "//test/util:thread_util", ], ) @@ -1155,6 +1156,24 @@ cc_binary( ) cc_binary( + name = "membarrier_test", + testonly = 1, + srcs = ["membarrier.cc"], + linkstatic = 1, + deps = [ + "@com_google_absl//absl/time", + gtest, + "//test/util:cleanup", + "//test/util:logging", + "//test/util:memory_util", + "//test/util:posix_error", + "//test/util:test_main", + "//test/util:test_util", + "//test/util:thread_util", + ], +) + +cc_binary( name = "mempolicy_test", testonly = 1, srcs = ["mempolicy.cc"], diff --git a/test/syscalls/linux/ip6tables.cc b/test/syscalls/linux/ip6tables.cc index f08f2dc55..e0e146067 100644 --- a/test/syscalls/linux/ip6tables.cc +++ b/test/syscalls/linux/ip6tables.cc @@ -89,22 +89,16 @@ TEST(IP6TablesBasic, GetRevision) { ASSERT_THAT(sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW), SyscallSucceeds()); - struct xt_get_revision rev = { - .name = "REDIRECT", - .revision = 0, - }; + struct xt_get_revision rev = {}; socklen_t rev_len = sizeof(rev); - // TODO(gvisor.dev/issue/3549): IPv6 redirect support. - const int retval = - getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len); - if (IsRunningOnGvisor()) { - EXPECT_THAT(retval, SyscallFailsWithErrno(ENOPROTOOPT)); - return; - } + snprintf(rev.name, sizeof(rev.name), "REDIRECT"); + rev.revision = 0; // Revision 0 exists. - EXPECT_THAT(retval, SyscallSucceeds()); + EXPECT_THAT( + getsockopt(sock, SOL_IPV6, IP6T_SO_GET_REVISION_TARGET, &rev, &rev_len), + SyscallSucceeds()); EXPECT_EQ(rev.revision, 0); // Revisions > 0 don't exist. diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc index 7ee10bbde..22550b800 100644 --- a/test/syscalls/linux/iptables.cc +++ b/test/syscalls/linux/iptables.cc @@ -124,12 +124,12 @@ TEST(IPTablesBasic, GetRevision) { ASSERT_THAT(sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP), SyscallSucceeds()); - struct xt_get_revision rev = { - .name = "REDIRECT", - .revision = 0, - }; + struct xt_get_revision rev = {}; socklen_t rev_len = sizeof(rev); + snprintf(rev.name, sizeof(rev.name), "REDIRECT"); + rev.revision = 0; + // Revision 0 exists. EXPECT_THAT( getsockopt(sock, SOL_IP, IPT_SO_GET_REVISION_TARGET, &rev, &rev_len), diff --git a/test/syscalls/linux/kcov.cc b/test/syscalls/linux/kcov.cc index 6afcb4e75..6816c1fd0 100644 --- a/test/syscalls/linux/kcov.cc +++ b/test/syscalls/linux/kcov.cc @@ -16,39 +16,47 @@ #include <sys/ioctl.h> #include <sys/mman.h> +#include <atomic> + #include "gtest/gtest.h" #include "test/util/capability_util.h" #include "test/util/file_descriptor.h" #include "test/util/test_util.h" +#include "test/util/thread_util.h" namespace gvisor { namespace testing { namespace { -// For this test to work properly, it must be run with coverage enabled. On +// For this set of tests to run, they must be run with coverage enabled. On // native Linux, this involves compiling the kernel with kcov enabled. For -// gVisor, we need to enable the Go coverage tool, e.g. -// bazel test --collect_coverage_data --instrumentation_filter=//pkg/... <test>. +// gVisor, we need to enable the Go coverage tool, e.g. bazel test -- +// collect_coverage_data --instrumentation_filter=//pkg/... <test>. + +constexpr char kcovPath[] = "/sys/kernel/debug/kcov"; +constexpr int kSize = 4096; +constexpr int KCOV_INIT_TRACE = 0x80086301; +constexpr int KCOV_ENABLE = 0x6364; +constexpr int KCOV_DISABLE = 0x6365; + +uint64_t* KcovMmap(int fd) { + return (uint64_t*)mmap(nullptr, kSize * sizeof(uint64_t), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); +} + TEST(KcovTest, Kcov) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); - constexpr int kSize = 4096; - constexpr int KCOV_INIT_TRACE = 0x80086301; - constexpr int KCOV_ENABLE = 0x6364; - constexpr int KCOV_DISABLE = 0x6365; - int fd; - ASSERT_THAT(fd = open("/sys/kernel/debug/kcov", O_RDWR), + ASSERT_THAT(fd = open(kcovPath, O_RDWR), AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); - // Kcov not available. SKIP_IF(errno == ENOENT); + auto fd_closer = Cleanup([fd]() { close(fd); }); ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); - uint64_t* area = (uint64_t*)mmap(nullptr, kSize * sizeof(uint64_t), - PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - + uint64_t* area = KcovMmap(fd); ASSERT_TRUE(area != MAP_FAILED); ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); @@ -67,6 +75,109 @@ TEST(KcovTest, Kcov) { ASSERT_THAT(ioctl(fd, KCOV_DISABLE, 0), SyscallSucceeds()); } +TEST(KcovTest, PrematureMmap) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); + + int fd; + ASSERT_THAT(fd = open(kcovPath, O_RDWR), + AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); + // Kcov not available. + SKIP_IF(errno == ENOENT); + auto fd_closer = Cleanup([fd]() { close(fd); }); + + // Cannot mmap before KCOV_INIT_TRACE. + uint64_t* area = KcovMmap(fd); + ASSERT_TRUE(area == MAP_FAILED); +} + +// Tests that multiple kcov fds can be used simultaneously. +TEST(KcovTest, MultipleFds) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); + + int fd1; + ASSERT_THAT(fd1 = open(kcovPath, O_RDWR), + AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); + // Kcov not available. + SKIP_IF(errno == ENOENT); + + int fd2; + ASSERT_THAT(fd2 = open(kcovPath, O_RDWR), SyscallSucceeds()); + auto fd_closer = Cleanup([fd1, fd2]() { + close(fd1); + close(fd2); + }); + + auto t1 = ScopedThread([&] { + ASSERT_THAT(ioctl(fd1, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); + uint64_t* area = KcovMmap(fd1); + ASSERT_TRUE(area != MAP_FAILED); + ASSERT_THAT(ioctl(fd1, KCOV_ENABLE, 0), SyscallSucceeds()); + }); + + ASSERT_THAT(ioctl(fd2, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); + uint64_t* area = KcovMmap(fd2); + ASSERT_TRUE(area != MAP_FAILED); + ASSERT_THAT(ioctl(fd2, KCOV_ENABLE, 0), SyscallSucceeds()); +} + +// Tests behavior for two threads trying to use the same kcov fd. +TEST(KcovTest, MultipleThreads) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE)))); + + int fd; + ASSERT_THAT(fd = open(kcovPath, O_RDWR), + AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT))); + // Kcov not available. + SKIP_IF(errno == ENOENT); + auto fd_closer = Cleanup([fd]() { close(fd); }); + + // Test the behavior of multiple threads trying to use the same kcov fd + // simultaneously. + std::atomic<bool> t1_enabled(false), t1_disabled(false), t2_failed(false), + t2_exited(false); + auto t1 = ScopedThread([&] { + ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds()); + uint64_t* area = KcovMmap(fd); + ASSERT_TRUE(area != MAP_FAILED); + ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); + t1_enabled = true; + + // After t2 has made sure that enabling kcov again fails, disable it. + while (!t2_failed) { + sched_yield(); + } + ASSERT_THAT(ioctl(fd, KCOV_DISABLE, 0), SyscallSucceeds()); + t1_disabled = true; + + // Wait for t2 to enable kcov and then exit, after which we should be able + // to enable kcov again, without needing to set up a new memory mapping. + while (!t2_exited) { + sched_yield(); + } + ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); + }); + + auto t2 = ScopedThread([&] { + // Wait for t1 to enable kcov, and make sure that enabling kcov again fails. + while (!t1_enabled) { + sched_yield(); + } + ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallFailsWithErrno(EINVAL)); + t2_failed = true; + + // Wait for t1 to disable kcov, after which using fd should now succeed. + while (!t1_disabled) { + sched_yield(); + } + uint64_t* area = KcovMmap(fd); + ASSERT_TRUE(area != MAP_FAILED); + ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds()); + }); + + t2.Join(); + t2_exited = true; +} + } // namespace } // namespace testing diff --git a/test/syscalls/linux/membarrier.cc b/test/syscalls/linux/membarrier.cc new file mode 100644 index 000000000..516956a25 --- /dev/null +++ b/test/syscalls/linux/membarrier.cc @@ -0,0 +1,268 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <errno.h> +#include <signal.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <atomic> + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "test/util/cleanup.h" +#include "test/util/logging.h" +#include "test/util/memory_util.h" +#include "test/util/posix_error.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// This is the classic test case for memory fences on architectures with total +// store ordering; see e.g. Intel SDM Vol. 3A Sec. 8.2.3.4 "Loads May Be +// Reordered with Earlier Stores to Different Locations". In each iteration of +// the test, given two variables X and Y initially set to 0 +// (MembarrierTestSharedState::local_var and remote_var in the code), two +// threads execute as follows: +// +// T1 T2 +// -- -- +// +// X = 1 Y = 1 +// T1fence() T2fence() +// read Y read X +// +// On architectures where memory writes may be locally buffered by each CPU +// (essentially all architectures), if T1fence() and T2fence() are omitted or +// ineffective, it is possible for both T1 and T2 to read 0 because the memory +// write from the other CPU is not yet visible outside that CPU. T1fence() and +// T2fence() are expected to perform the necessary synchronization to restore +// sequential consistency: both threads agree on a order of memory accesses that +// is consistent with program order in each thread, such that at least one +// thread reads 1. +// +// In the NoMembarrier test, T1fence() and T2fence() are both ordinary memory +// fences establishing ordering between memory accesses before and after the +// fence (std::atomic_thread_fence). In all other test cases, T1fence() is not a +// memory fence at all, but only prevents compiler reordering of memory accesses +// (std::atomic_signal_fence); T2fence() is an invocation of the membarrier() +// syscall, which establishes ordering of memory accesses before and after the +// syscall on both threads. + +template <typename F> +int DoMembarrierTestSide(std::atomic<int>* our_var, + std::atomic<int> const& their_var, + F const& test_fence) { + our_var->store(1, std::memory_order_relaxed); + test_fence(); + return their_var.load(std::memory_order_relaxed); +} + +struct MembarrierTestSharedState { + std::atomic<int64_t> remote_iter_cur; + std::atomic<int64_t> remote_iter_done; + std::atomic<int> local_var; + std::atomic<int> remote_var; + int remote_obs_of_local_var; + + void Init() { + remote_iter_cur.store(-1, std::memory_order_relaxed); + remote_iter_done.store(-1, std::memory_order_relaxed); + } +}; + +// Special value for MembarrierTestSharedState::remote_iter_cur indicating that +// the remote thread should terminate. +constexpr int64_t kRemoteIterStop = -2; + +// Must be async-signal-safe. +template <typename F> +void RunMembarrierTestRemoteSide(MembarrierTestSharedState* state, + F const& test_fence) { + int64_t i = 0; + int64_t cur; + while (true) { + while ((cur = state->remote_iter_cur.load(std::memory_order_acquire)) < i) { + if (cur == kRemoteIterStop) { + return; + } + // spin + } + state->remote_obs_of_local_var = + DoMembarrierTestSide(&state->remote_var, state->local_var, test_fence); + state->remote_iter_done.store(i, std::memory_order_release); + i++; + } +} + +template <typename F> +void RunMembarrierTestLocalSide(MembarrierTestSharedState* state, + F const& test_fence) { + // On test completion, instruct the remote thread to terminate. + Cleanup cleanup_remote([&] { + state->remote_iter_cur.store(kRemoteIterStop, std::memory_order_relaxed); + }); + + int64_t i = 0; + absl::Time end = absl::Now() + absl::Seconds(5); // arbitrary test duration + while (absl::Now() < end) { + // Reset both vars to 0. + state->local_var.store(0, std::memory_order_relaxed); + state->remote_var.store(0, std::memory_order_relaxed); + // Instruct the remote thread to begin this iteration. + state->remote_iter_cur.store(i, std::memory_order_release); + // Perform our side of the test. + auto local_obs_of_remote_var = + DoMembarrierTestSide(&state->local_var, state->remote_var, test_fence); + // Wait for the remote thread to finish this iteration. + while (state->remote_iter_done.load(std::memory_order_acquire) < i) { + // spin + } + ASSERT_TRUE(local_obs_of_remote_var != 0 || + state->remote_obs_of_local_var != 0); + i++; + } +} + +TEST(MembarrierTest, NoMembarrier) { + MembarrierTestSharedState state; + state.Init(); + + ScopedThread remote_thread([&] { + RunMembarrierTestRemoteSide( + &state, [] { std::atomic_thread_fence(std::memory_order_seq_cst); }); + }); + RunMembarrierTestLocalSide( + &state, [] { std::atomic_thread_fence(std::memory_order_seq_cst); }); +} + +enum membarrier_cmd { + MEMBARRIER_CMD_QUERY = 0, + MEMBARRIER_CMD_GLOBAL = (1 << 0), + MEMBARRIER_CMD_GLOBAL_EXPEDITED = (1 << 1), + MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED = (1 << 2), + MEMBARRIER_CMD_PRIVATE_EXPEDITED = (1 << 3), + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED = (1 << 4), +}; + +int membarrier(membarrier_cmd cmd, int flags) { + return syscall(SYS_membarrier, cmd, flags); +} + +PosixErrorOr<int> SupportedMembarrierCommands() { + int cmds = membarrier(MEMBARRIER_CMD_QUERY, 0); + if (cmds < 0) { + if (errno == ENOSYS) { + // No commands are supported. + return 0; + } + return PosixError(errno, "membarrier(MEMBARRIER_CMD_QUERY) failed"); + } + return cmds; +} + +TEST(MembarrierTest, Global) { + SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & + MEMBARRIER_CMD_GLOBAL) == 0); + + Mapping m = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); + auto state = static_cast<MembarrierTestSharedState*>(m.ptr()); + state->Init(); + + pid_t const child_pid = fork(); + if (child_pid == 0) { + // In child process. + RunMembarrierTestRemoteSide( + state, [] { TEST_PCHECK(membarrier(MEMBARRIER_CMD_GLOBAL, 0) == 0); }); + _exit(0); + } + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + Cleanup cleanup_child([&] { + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << " status " << status; + }); + RunMembarrierTestLocalSide( + state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); +} + +TEST(MembarrierTest, GlobalExpedited) { + constexpr int kRequiredCommands = MEMBARRIER_CMD_GLOBAL_EXPEDITED | + MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED; + SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & + kRequiredCommands) != kRequiredCommands); + + ASSERT_THAT(membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0), + SyscallSucceeds()); + + Mapping m = ASSERT_NO_ERRNO_AND_VALUE( + MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED)); + auto state = static_cast<MembarrierTestSharedState*>(m.ptr()); + state->Init(); + + pid_t const child_pid = fork(); + if (child_pid == 0) { + // In child process. + RunMembarrierTestRemoteSide(state, [] { + TEST_PCHECK(membarrier(MEMBARRIER_CMD_GLOBAL_EXPEDITED, 0) == 0); + }); + _exit(0); + } + // In parent process. + ASSERT_THAT(child_pid, SyscallSucceeds()); + Cleanup cleanup_child([&] { + int status; + ASSERT_THAT(waitpid(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0) + << " status " << status; + }); + RunMembarrierTestLocalSide( + state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); +} + +TEST(MembarrierTest, PrivateExpedited) { + constexpr int kRequiredCommands = MEMBARRIER_CMD_PRIVATE_EXPEDITED | + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED; + SKIP_IF((ASSERT_NO_ERRNO_AND_VALUE(SupportedMembarrierCommands()) & + kRequiredCommands) != kRequiredCommands); + + ASSERT_THAT(membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0), + SyscallSucceeds()); + + MembarrierTestSharedState state; + state.Init(); + + ScopedThread remote_thread([&] { + RunMembarrierTestRemoteSide(&state, [] { + TEST_PCHECK(membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0) == 0); + }); + }); + RunMembarrierTestLocalSide( + &state, [] { std::atomic_signal_fence(std::memory_order_seq_cst); }); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index e0981e28a..9f522f833 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -903,6 +903,58 @@ TEST_P(SimpleTcpSocketTest, NonBlockingConnectNoListener) { EXPECT_EQ(err, ECONNREFUSED); } +TEST_P(SimpleTcpSocketTest, SelfConnectSendRecv_NoRandomSave) { + // Initialize address to the loopback one. + sockaddr_storage addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t addrlen = sizeof(addr); + + const FileDescriptor s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + ASSERT_THAT( + (bind)(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + // Get the bound port. + ASSERT_THAT( + getsockname(s.get(), reinterpret_cast<struct sockaddr*>(&addr), &addrlen), + SyscallSucceeds()); + ASSERT_THAT(RetryEINTR(connect)( + s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen), + SyscallSucceeds()); + + constexpr int kBufSz = 1 << 20; // 1 MiB + std::vector<char> writebuf(kBufSz); + + // Start reading the response in a loop. + int read_bytes = 0; + ScopedThread t([&s, &read_bytes]() { + // Too many syscalls. + const DisableSave ds; + + char readbuf[2500] = {}; + int n = -1; + while (n != 0) { + ASSERT_THAT(n = RetryEINTR(read)(s.get(), &readbuf, sizeof(readbuf)), + SyscallSucceeds()); + read_bytes += n; + } + }); + + // Try to send the whole thing. + int n; + ASSERT_THAT(n = SendFd(s.get(), writebuf.data(), kBufSz, 0), + SyscallSucceeds()); + + // We should have written the whole thing. + EXPECT_EQ(n, kBufSz); + EXPECT_THAT(shutdown(s.get(), SHUT_WR), SyscallSucceedsWithValue(0)); + t.Join(); + + // We should have read the whole thing. + EXPECT_EQ(read_bytes, kBufSz); +} + TEST_P(SimpleTcpSocketTest, NonBlockingConnect) { const FileDescriptor listener = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); diff --git a/test/util/BUILD b/test/util/BUILD index fc5fb3a8d..26c2b6a2f 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "cc_library", "cc_test", "gbenchmark", "gtest", "select_system") +load("//tools:defs.bzl", "cc_library", "cc_test", "coreutil", "gbenchmark", "gtest", "select_system") package( default_visibility = ["//:sandbox"], @@ -254,7 +254,7 @@ cc_library( ], hdrs = ["test_util.h"], defines = select_system(), - deps = [ + deps = coreutil() + [ ":fs_util", ":logging", ":posix_error", |