Skip to content

Commit fba892a

Browse files
committed
cmd/telemetrygodev: validate reports during upload request
Added report validation to upload requests. Upload requests will fail if - The report is missing or invalid values for X, Config, or Week. - The report contains programs, counters, or stacks that aren't registered in the latest upload config. Change-Id: I8f4f945456f02897eda1f455c725b5a82d675f5d Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/503761 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Jamal Carvalho <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent de303cd commit fba892a

File tree

8 files changed

+179
-10
lines changed

8 files changed

+179
-10
lines changed

godev/cmd/telemetrygodev/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ cloud storage emulator and use the -gcs flag to use the Cloud Storage API.
1414

1515
### Environment Variables
1616

17-
| Name | Default | Description |
18-
| ---------------------------------- | -------------- | --------------------------------------------------------- |
19-
| GO_TELEMETRY_PROJECT_ID | go-telemetry | GCP project ID |
20-
| GO_TELEMETRY_STORAGE_EMULATOR_HOST | localhost:8081 | Host for the Cloud Storage emulator |
21-
| GO_TELEMETRY_LOCAL_STORAGE | .localstorage | Directory for storage emulator I/O or file system storage |
17+
| Name | Default | Description |
18+
| ---------------------------------- | --------------------- | --------------------------------------------------------- |
19+
| GO_TELEMETRY_PROJECT_ID | go-telemetry | GCP project ID |
20+
| GO_TELEMETRY_STORAGE_EMULATOR_HOST | localhost:8081 | Host for the Cloud Storage emulator |
21+
| GO_TELEMETRY_LOCAL_STORAGE | .localstorage | Directory for storage emulator I/O or file system storage |
22+
| GO_TELEMETRY_UPLOAD_CONFIG | ../config/config.json | Location of the upload config used for report validation |
2223

2324
## Testing
2425

godev/cmd/telemetrygodev/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ type config struct {
2626
// UploadBucket is the storage bucket for report uploads.
2727
UploadBucket string
2828

29+
// UploadConfig is the location of the upload config deployed with the server.
30+
// It's used to validate telemetry uploads.
31+
UploadConfig string
32+
2933
// UseGCS is true if the server should use the Cloud Storage API for reading and
3034
// writing storage objects.
3135
UseGCS bool
@@ -62,6 +66,7 @@ func newConfig() *config {
6266
StorageEmulatorHost: env("GO_TELEMETRY_STORAGE_EMULATOR_HOST", "localhost:8081"),
6367
LocalStorage: env("GO_TELEMETRY_LOCAL_STORAGE", ".localstorage"),
6468
UploadBucket: service + "-uploaded",
69+
UploadConfig: env("GO_TELEMETRY_UPLOAD_CONFIG", "../config/config.json"),
6570
UseGCS: *useGCS,
6671
DevMode: *devMode,
6772
}

godev/cmd/telemetrygodev/config_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func TestConfig(t *testing.T) {
1616
StorageEmulatorHost: "localhost:8081",
1717
LocalStorage: ".localstorage",
1818
UploadBucket: "local-telemetry-uploaded",
19+
UploadConfig: "../config/config.json",
1920
}
2021
if got := newConfig(); !reflect.DeepEqual(got, want) {
2122
t.Errorf("Config() = %v, want %v", got, want)

godev/cmd/telemetrygodev/main.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package main
77

88
import (
99
"context"
10+
_ "embed"
1011
"encoding/json"
1112
"flag"
1213
"fmt"
@@ -15,13 +16,16 @@ import (
1516
"net/http"
1617
"os"
1718
"path"
19+
"time"
1820

21+
"golang.org/x/mod/semver"
1922
"golang.org/x/telemetry"
2023
"golang.org/x/telemetry/godev"
2124
"golang.org/x/telemetry/godev/internal/content"
2225
"golang.org/x/telemetry/godev/internal/middleware"
2326
"golang.org/x/telemetry/godev/internal/storage"
2427
"golang.org/x/telemetry/godev/internal/unionfs"
28+
"golang.org/x/telemetry/godev/internal/upload"
2529
)
2630

2731
func main() {
@@ -32,26 +36,31 @@ func main() {
3236
if err != nil {
3337
log.Fatal(err)
3438
}
39+
ucfg, err := upload.ReadConfig(cfg.UploadConfig)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
3543
fsys := fsys(cfg.DevMode)
3644
cserv := content.Server(fsys)
3745
mux := http.NewServeMux()
3846

3947
mux.Handle("/", cserv)
40-
mux.Handle("/upload/", handleUpload(store))
48+
mux.Handle("/upload/", handleUpload(ucfg, store))
4149

4250
fmt.Printf("server listening at http://localhost:%s\n", cfg.Port)
4351
log.Fatal(http.ListenAndServe(":"+cfg.Port, middleware.Default(mux)))
4452
}
4553

46-
func handleUpload(store storage.Store) content.HandlerFunc {
54+
func handleUpload(ucfg *upload.Config, store storage.Store) content.HandlerFunc {
4755
return func(w http.ResponseWriter, r *http.Request) error {
4856
if r.Method == "POST" {
4957
var report telemetry.Report
5058
if err := json.NewDecoder(r.Body).Decode(&report); err != nil {
5159
return err
5260
}
53-
// TODO: validate the report, checking that the counters and stacks contained are things
54-
// we want to collect and file name is something reasonable.
61+
if err := validate(&report, ucfg); err != nil {
62+
return content.Error(err, http.StatusBadRequest)
63+
}
5564
// TODO: capture metrics for collisions.
5665
ctx := r.Context()
5766
name := fmt.Sprintf("%s/%g.json", report.Week, report.X)
@@ -66,12 +75,48 @@ func handleUpload(store storage.Store) content.HandlerFunc {
6675
if err := f.Close(); err != nil {
6776
return err
6877
}
69-
return content.Text(w, "ok", http.StatusOK)
78+
return content.Status(w, http.StatusOK)
7079
}
7180
return content.Status(w, http.StatusMethodNotAllowed)
7281
}
7382
}
7483

84+
// validate validates the telemetry report data against the latest config.
85+
func validate(r *telemetry.Report, cfg *upload.Config) error {
86+
// TODO: reject/drop data arrived too early or too late.
87+
if _, err := time.Parse("2006-01-02", r.Week); err != nil {
88+
return fmt.Errorf("invalid week %s", r.Week)
89+
}
90+
if !semver.IsValid(r.Config) {
91+
return fmt.Errorf("invalid config %s", r.Config)
92+
}
93+
if r.X == 0 {
94+
return fmt.Errorf("invalid X %g", r.X)
95+
}
96+
// TODO: We can probably keep known programs and counters even when a report
97+
// includes something that has been removed from the latest config.
98+
for _, p := range r.Programs {
99+
if !cfg.HasGOARCH(p.GOARCH) ||
100+
!cfg.HasGOOS(p.GOOS) ||
101+
!cfg.HasGoVersion(p.GoVersion) ||
102+
!cfg.HasProgram(p.Program) ||
103+
!cfg.HasVersion(p.Program, p.Version) {
104+
return fmt.Errorf("unknown program build %s@%s %s %s/%s", p.Program, p.Version, p.GoVersion, p.GOOS, p.GOARCH)
105+
}
106+
for c := range p.Counters {
107+
if !cfg.HasCounter(p.Program, c) {
108+
return fmt.Errorf("unknown counter %s", c)
109+
}
110+
}
111+
for s := range p.Stacks {
112+
if !cfg.HasStack(p.Program, s) {
113+
return fmt.Errorf("unknown stack %s", s)
114+
}
115+
}
116+
}
117+
return nil
118+
}
119+
75120
func fsys(fromOS bool) fs.FS {
76121
var f fs.FS = godev.FS
77122
if fromOS {

godev/cmd/telemetrygodev/main_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
_ "embed"
9+
"testing"
10+
11+
"golang.org/x/telemetry"
12+
"golang.org/x/telemetry/godev/internal/upload"
13+
)
14+
15+
func TestValidate(t *testing.T) {
16+
cfg, err := upload.ReadConfig("testdata/config.json")
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
tests := []struct {
21+
name string
22+
report *telemetry.Report
23+
wantErr bool
24+
}{
25+
{
26+
name: "empty report",
27+
report: &telemetry.Report{},
28+
wantErr: true,
29+
},
30+
{
31+
name: "valid report with no counters",
32+
report: &telemetry.Report{
33+
Week: "2023-06-15",
34+
LastWeek: "",
35+
X: 0.1,
36+
Programs: []*telemetry.ProgramReport{},
37+
Config: "v0.0.1-test",
38+
},
39+
wantErr: false,
40+
},
41+
{
42+
name: "valid report with counters",
43+
report: &telemetry.Report{
44+
Week: "2023-06-15",
45+
LastWeek: "",
46+
X: 0.1,
47+
Programs: []*telemetry.ProgramReport{
48+
{
49+
Program: "golang.org/x/tools/gopls",
50+
Version: "v0.10.1",
51+
GoVersion: "go1.20.1",
52+
GOOS: "linux",
53+
GOARCH: "arm64",
54+
Counters: map[string]int64{
55+
"editor:vim": 100,
56+
},
57+
},
58+
},
59+
Config: "v0.0.1-test",
60+
},
61+
wantErr: false,
62+
},
63+
}
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
if err := validate(tt.report, cfg); (err != nil) != tt.wantErr {
67+
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
68+
}
69+
})
70+
}
71+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"Version": "v0.0.1-test",
3+
"GOOS": [
4+
"linux",
5+
"darwin"
6+
],
7+
"GOARCH": [
8+
"amd64",
9+
"arm64"
10+
],
11+
"GoVersion": [
12+
"go1.20",
13+
"go1.20.1"
14+
],
15+
"Programs": [
16+
{
17+
"Name": "golang.org/x/tools/gopls",
18+
"Versions": [
19+
"v0.10.1",
20+
"v0.11.0"
21+
],
22+
"Counters": [
23+
{
24+
"Name": "editor:{emacs,vim,vscode,other}",
25+
"Rate": 0.01
26+
}
27+
]
28+
},
29+
{
30+
"Name": "cmd/go",
31+
"Versions": [
32+
"v0.10.1",
33+
"v0.11.0"
34+
],
35+
"Counters": [
36+
{
37+
"Name": "go/buildcache/miss:{0,0.1,0.2,0.5,1,10,100,2,20,5,50}",
38+
"Rate": 0.01
39+
}
40+
]
41+
}
42+
]
43+
}

godev/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/google/go-cmp v0.5.9
1010
github.com/yuin/goldmark v1.5.4
1111
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
12+
golang.org/x/mod v0.10.0
1213
golang.org/x/telemetry v0.0.0-00010101000000-000000000000
1314
)
1415

godev/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
263263
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
264264
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
265265
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
266+
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
267+
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
266268
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
267269
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
268270
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

0 commit comments

Comments
 (0)