@@ -2,22 +2,44 @@ package push
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
5
6
"net/http"
7
+ "strconv"
8
+ "strings"
6
9
7
10
"github.com/go-kit/log/level"
11
+ "github.com/prometheus/prometheus/config"
12
+ "github.com/prometheus/prometheus/storage/remote"
8
13
"github.com/weaveworks/common/httpgrpc"
9
14
"github.com/weaveworks/common/middleware"
10
15
11
16
"github.com/cortexproject/cortex/pkg/cortexpb"
17
+ "github.com/cortexproject/cortex/pkg/cortexpbv2"
12
18
"github.com/cortexproject/cortex/pkg/util"
13
19
"github.com/cortexproject/cortex/pkg/util/log"
14
20
)
15
21
22
+ const (
23
+ remoteWriteVersionHeader = "X-Prometheus-Remote-Write-Version"
24
+ remoteWriteVersion1HeaderValue = "0.1.0"
25
+ remoteWriteVersion20HeaderValue = "2.0.0"
26
+ appProtoContentType = "application/x-protobuf"
27
+ appProtoV1ContentType = "application/x-protobuf;proto=prometheus.WriteRequest"
28
+ appProtoV2ContentType = "application/x-protobuf;proto=io.prometheus.write.v2.Request"
29
+
30
+ rw20WrittenSamplesHeader = "X-Prometheus-Remote-Write-Samples-Written"
31
+ rw20WrittenHistogramsHeader = "X-Prometheus-Remote-Write-Histograms-Written"
32
+ rw20WrittenExemplarsHeader = "X-Prometheus-Remote-Write-Exemplars-Written"
33
+ )
34
+
16
35
// Func defines the type of the push. It is similar to http.HandlerFunc.
17
36
type Func func (context.Context , * cortexpb.WriteRequest ) (* cortexpb.WriteResponse , error )
18
37
38
+ // FuncV2 defines the type of the pushV2. It is similar to http.HandlerFunc.
39
+ type FuncV2 func (ctx context.Context , request * cortexpbv2.WriteRequest ) (* cortexpbv2.WriteResponse , error )
40
+
19
41
// Handler is a http.Handler which accepts WriteRequests.
20
- func Handler (maxRecvMsgSize int , sourceIPs * middleware.SourceIPExtractor , push Func ) http.Handler {
42
+ func Handler (maxRecvMsgSize int , sourceIPs * middleware.SourceIPExtractor , push Func , pushV2 FuncV2 ) http.Handler {
21
43
return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
22
44
ctx := r .Context ()
23
45
logger := log .WithContext (ctx , log .Logger )
@@ -28,31 +50,123 @@ func Handler(maxRecvMsgSize int, sourceIPs *middleware.SourceIPExtractor, push F
28
50
logger = log .WithSourceIPs (source , logger )
29
51
}
30
52
}
31
- var req cortexpb.PreallocWriteRequest
32
- err := util .ParseProtoReader (ctx , r .Body , int (r .ContentLength ), maxRecvMsgSize , & req , util .RawSnappy )
53
+
54
+ contentType := r .Header .Get ("Content-Type" )
55
+ if contentType == "" {
56
+ contentType = appProtoContentType
57
+ }
58
+
59
+ msgType , err := parseProtoMsg (contentType )
33
60
if err != nil {
34
- level .Error (logger ).Log ("err" , err . Error () )
35
- http .Error (w , err .Error (), http .StatusBadRequest )
61
+ level .Error (logger ).Log ("Error decoding remote write request" , " err" , err )
62
+ http .Error (w , err .Error (), http .StatusUnsupportedMediaType )
36
63
return
37
64
}
38
65
39
- req .SkipLabelNameValidation = false
40
- if req .Source == 0 {
41
- req .Source = cortexpb .API
66
+ if msgType != config .RemoteWriteProtoMsgV1 && msgType != config .RemoteWriteProtoMsgV2 {
67
+ level .Error (logger ).Log ("Error decoding remote write request" , "err" , err )
68
+ http .Error (w , err .Error (), http .StatusUnsupportedMediaType )
69
+ return
70
+ }
71
+
72
+ enc := r .Header .Get ("Content-Encoding" )
73
+ if enc == "" {
74
+ } else if enc != string (remote .SnappyBlockCompression ) {
75
+ err := fmt .Errorf ("%v encoding (compression) is not accepted by this server; only %v is acceptable" , enc , remote .SnappyBlockCompression )
76
+ level .Error (logger ).Log ("Error decoding remote write request" , "err" , err )
77
+ http .Error (w , err .Error (), http .StatusUnsupportedMediaType )
78
+ return
42
79
}
43
80
44
- if _ , err := push (ctx , & req .WriteRequest ); err != nil {
45
- resp , ok := httpgrpc .HTTPResponseFromError (err )
46
- if ! ok {
47
- http .Error (w , err .Error (), http .StatusInternalServerError )
81
+ switch msgType {
82
+ case config .RemoteWriteProtoMsgV1 :
83
+ var req cortexpb.PreallocWriteRequest
84
+ err := util .ParseProtoReader (ctx , r .Body , int (r .ContentLength ), maxRecvMsgSize , & req , util .RawSnappy )
85
+ if err != nil {
86
+ level .Error (logger ).Log ("err" , err .Error ())
87
+ http .Error (w , err .Error (), http .StatusBadRequest )
88
+ return
89
+ }
90
+
91
+ req .SkipLabelNameValidation = false
92
+ if req .Source == 0 {
93
+ req .Source = cortexpb .API
94
+ }
95
+
96
+ if _ , err := push (ctx , & req .WriteRequest ); err != nil {
97
+ resp , ok := httpgrpc .HTTPResponseFromError (err )
98
+ if ! ok {
99
+ http .Error (w , err .Error (), http .StatusInternalServerError )
100
+ return
101
+ }
102
+ if resp .GetCode ()/ 100 == 5 {
103
+ level .Error (logger ).Log ("msg" , "push error" , "err" , err )
104
+ } else if resp .GetCode () != http .StatusAccepted && resp .GetCode () != http .StatusTooManyRequests {
105
+ level .Warn (logger ).Log ("msg" , "push refused" , "err" , err )
106
+ }
107
+ http .Error (w , string (resp .Body ), int (resp .Code ))
108
+ }
109
+ case config .RemoteWriteProtoMsgV2 :
110
+ var req cortexpbv2.WriteRequest
111
+ err := util .ParseProtoReader (ctx , r .Body , int (r .ContentLength ), maxRecvMsgSize , & req , util .RawSnappy )
112
+ if err != nil {
113
+ fmt .Println ("err" , err )
114
+ level .Error (logger ).Log ("err" , err .Error ())
115
+ http .Error (w , err .Error (), http .StatusBadRequest )
48
116
return
49
117
}
50
- if resp .GetCode ()/ 100 == 5 {
51
- level .Error (logger ).Log ("msg" , "push error" , "err" , err )
52
- } else if resp .GetCode () != http .StatusAccepted && resp .GetCode () != http .StatusTooManyRequests {
53
- level .Warn (logger ).Log ("msg" , "push refused" , "err" , err )
118
+
119
+ req .SkipLabelNameValidation = false
120
+ if req .Source == 0 {
121
+ req .Source = cortexpbv2 .API
122
+ }
123
+
124
+ if resp , err := pushV2 (ctx , & req ); err != nil {
125
+ resp , ok := httpgrpc .HTTPResponseFromError (err )
126
+ w .Header ().Set (rw20WrittenSamplesHeader , "0" )
127
+ w .Header ().Set (rw20WrittenHistogramsHeader , "0" )
128
+ w .Header ().Set (rw20WrittenExemplarsHeader , "0" )
129
+ if ! ok {
130
+ http .Error (w , err .Error (), http .StatusInternalServerError )
131
+ return
132
+ }
133
+ if resp .GetCode ()/ 100 == 5 {
134
+ level .Error (logger ).Log ("msg" , "push error" , "err" , err )
135
+ } else if resp .GetCode () != http .StatusAccepted && resp .GetCode () != http .StatusTooManyRequests {
136
+ level .Warn (logger ).Log ("msg" , "push refused" , "err" , err )
137
+ }
138
+ http .Error (w , string (resp .Body ), int (resp .Code ))
139
+ } else {
140
+ w .Header ().Set (rw20WrittenSamplesHeader , strconv .FormatInt (resp .Samples , 10 ))
141
+ w .Header ().Set (rw20WrittenHistogramsHeader , strconv .FormatInt (resp .Histograms , 10 ))
142
+ w .Header ().Set (rw20WrittenExemplarsHeader , strconv .FormatInt (resp .Exemplars , 10 ))
54
143
}
55
- http .Error (w , string (resp .Body ), int (resp .Code ))
56
144
}
57
145
})
58
146
}
147
+
148
+ // Refer to parseProtoMsg in https://github.com/prometheus/prometheus/blob/main/storage/remote/write_handler.go
149
+ func parseProtoMsg (contentType string ) (config.RemoteWriteProtoMsg , error ) {
150
+ contentType = strings .TrimSpace (contentType )
151
+
152
+ parts := strings .Split (contentType , ";" )
153
+ if parts [0 ] != appProtoContentType {
154
+ return "" , fmt .Errorf ("expected %v as the first (media) part, got %v content-type" , appProtoContentType , contentType )
155
+ }
156
+ // Parse potential https://www.rfc-editor.org/rfc/rfc9110#parameter
157
+ for _ , p := range parts [1 :] {
158
+ pair := strings .Split (p , "=" )
159
+ if len (pair ) != 2 {
160
+ return "" , fmt .Errorf ("as per https://www.rfc-editor.org/rfc/rfc9110#parameter expected parameters to be key-values, got %v in %v content-type" , p , contentType )
161
+ }
162
+ if pair [0 ] == "proto" {
163
+ ret := config .RemoteWriteProtoMsg (pair [1 ])
164
+ if err := ret .Validate (); err != nil {
165
+ return "" , fmt .Errorf ("got %v content type; %w" , contentType , err )
166
+ }
167
+ return ret , nil
168
+ }
169
+ }
170
+ // No "proto=" parameter, assuming v1.
171
+ return config .RemoteWriteProtoMsgV1 , nil
172
+ }
0 commit comments