@@ -12,6 +12,7 @@ import (
12
12
"cdr.dev/slog"
13
13
agentproto "github.com/coder/coder/v2/agent/proto"
14
14
"github.com/coder/coder/v2/coderd/database"
15
+ "github.com/coder/coder/v2/coderd/database/dbtime"
15
16
"github.com/coder/coder/v2/coderd/database/pubsub"
16
17
)
17
18
@@ -20,14 +21,26 @@ type MetadataAPI struct {
20
21
Database database.Store
21
22
Pubsub pubsub.Pubsub
22
23
Log slog.Logger
24
+
25
+ TimeNowFn func () time.Time // defaults to dbtime.Now()
26
+ }
27
+
28
+ func (a * MetadataAPI ) now () time.Time {
29
+ if a .TimeNowFn != nil {
30
+ return a .TimeNowFn ()
31
+ }
32
+ return dbtime .Now ()
23
33
}
24
34
25
35
func (a * MetadataAPI ) BatchUpdateMetadata (ctx context.Context , req * agentproto.BatchUpdateMetadataRequest ) (* agentproto.BatchUpdateMetadataResponse , error ) {
26
36
const (
27
- // maxValueLen is set to 2048 to stay under the 8000 byte Postgres
28
- // NOTIFY limit. Since both value and error can be set, the real payload
29
- // limit is 2 * 2048 * 4/3 <base64 expansion> = 5461 bytes + a few
30
- // hundred bytes for JSON syntax, key names, and metadata.
37
+ // maxAllKeysLen is the maximum length of all metadata keys. This is
38
+ // 6144 to stay below the Postgres NOTIFY limit of 8000 bytes, with some
39
+ // headway for the timestamp and JSON encoding. Any values that would
40
+ // exceed this limit are discarded (the rest are still inserted) and an
41
+ // error is returned.
42
+ maxAllKeysLen = 6144 // 1024 * 6
43
+
31
44
maxValueLen = 2048
32
45
maxErrorLen = maxValueLen
33
46
)
@@ -37,18 +50,36 @@ func (a *MetadataAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.B
37
50
return nil , err
38
51
}
39
52
40
- collectedAt := time .Now ()
41
- dbUpdate := database.UpdateWorkspaceAgentMetadataParams {
42
- WorkspaceAgentID : workspaceAgent .ID ,
43
- Key : make ([]string , 0 , len (req .Metadata )),
44
- Value : make ([]string , 0 , len (req .Metadata )),
45
- Error : make ([]string , 0 , len (req .Metadata )),
46
- CollectedAt : make ([]time.Time , 0 , len (req .Metadata )),
47
- }
48
-
53
+ var (
54
+ collectedAt = a .now ()
55
+ allKeysLen = 0
56
+ dbUpdate = database.UpdateWorkspaceAgentMetadataParams {
57
+ WorkspaceAgentID : workspaceAgent .ID ,
58
+ // These need to be `make(x, 0, len(req.Metadata))` instead of
59
+ // `make(x, len(req.Metadata))` because we may not insert all
60
+ // metadata if the keys are large.
61
+ Key : make ([]string , 0 , len (req .Metadata )),
62
+ Value : make ([]string , 0 , len (req .Metadata )),
63
+ Error : make ([]string , 0 , len (req .Metadata )),
64
+ CollectedAt : make ([]time.Time , 0 , len (req .Metadata )),
65
+ }
66
+ )
49
67
for _ , md := range req .Metadata {
50
68
metadataError := md .Result .Error
51
69
70
+ allKeysLen += len (md .Key )
71
+ if allKeysLen > maxAllKeysLen {
72
+ // We still insert the rest of the metadata, and we return an error
73
+ // after the insert.
74
+ a .Log .Warn (
75
+ ctx , "discarded extra agent metadata due to excessive key length" ,
76
+ slog .F ("collected_at" , collectedAt ),
77
+ slog .F ("all_keys_len" , allKeysLen ),
78
+ slog .F ("max_all_keys_len" , maxAllKeysLen ),
79
+ )
80
+ break
81
+ }
82
+
52
83
// We overwrite the error if the provided payload is too long.
53
84
if len (md .Result .Value ) > maxValueLen {
54
85
metadataError = fmt .Sprintf ("value of %d bytes exceeded %d bytes" , len (md .Result .Value ), maxValueLen )
@@ -71,30 +102,34 @@ func (a *MetadataAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.B
71
102
a .Log .Debug (
72
103
ctx , "accepted metadata report" ,
73
104
slog .F ("collected_at" , collectedAt ),
74
- slog .F ("original_collected_at" , collectedAt ),
75
105
slog .F ("key" , md .Key ),
76
106
slog .F ("value" , ellipse (md .Result .Value , 16 )),
77
107
)
78
108
}
79
109
110
+ err = a .Database .UpdateWorkspaceAgentMetadata (ctx , dbUpdate )
111
+ if err != nil {
112
+ return nil , xerrors .Errorf ("update workspace agent metadata in database: %w" , err )
113
+ }
114
+
80
115
payload , err := json .Marshal (WorkspaceAgentMetadataChannelPayload {
81
116
CollectedAt : collectedAt ,
82
117
Keys : dbUpdate .Key ,
83
118
})
84
119
if err != nil {
85
120
return nil , xerrors .Errorf ("marshal workspace agent metadata channel payload: %w" , err )
86
121
}
87
-
88
- err = a .Database .UpdateWorkspaceAgentMetadata (ctx , dbUpdate )
89
- if err != nil {
90
- return nil , xerrors .Errorf ("update workspace agent metadata in database: %w" , err )
91
- }
92
-
93
122
err = a .Pubsub .Publish (WatchWorkspaceAgentMetadataChannel (workspaceAgent .ID ), payload )
94
123
if err != nil {
95
124
return nil , xerrors .Errorf ("publish workspace agent metadata: %w" , err )
96
125
}
97
126
127
+ // If the metadata keys were too large, we return an error so the agent can
128
+ // log it.
129
+ if allKeysLen > maxAllKeysLen {
130
+ return nil , xerrors .Errorf ("metadata keys of %d bytes exceeded %d bytes" , allKeysLen , maxAllKeysLen )
131
+ }
132
+
98
133
return & agentproto.BatchUpdateMetadataResponse {}, nil
99
134
}
100
135
0 commit comments