@@ -18,6 +18,7 @@ import (
18
18
"github.com/weaveworks/common/httpgrpc"
19
19
"github.com/weaveworks/common/user"
20
20
21
+ "github.com/cortexproject/cortex/pkg/alertmanager/merger"
21
22
"github.com/cortexproject/cortex/pkg/ring"
22
23
"github.com/cortexproject/cortex/pkg/ring/client"
23
24
"github.com/cortexproject/cortex/pkg/tenant"
@@ -67,7 +68,8 @@ func (d *Distributor) running(ctx context.Context) error {
67
68
// IsPathSupported returns true if the given route is currently supported by the Distributor.
68
69
func (d * Distributor ) IsPathSupported (p string ) bool {
69
70
// API can be found at https://petstore.swagger.io/?url=https://raw.githubusercontent.com/prometheus/alertmanager/master/api/v2/openapi.yaml.
70
- return d .isQuorumWritePath (p ) || d .isUnaryWritePath (p ) || d .isUnaryDeletePath (p ) || d .isUnaryReadPath (p )
71
+ isQuorumReadPath , _ := d .isQuorumReadPath (p )
72
+ return d .isQuorumWritePath (p ) || d .isUnaryWritePath (p ) || d .isUnaryDeletePath (p ) || d .isUnaryReadPath (p ) || isQuorumReadPath
71
73
}
72
74
73
75
func (d * Distributor ) isQuorumWritePath (p string ) bool {
@@ -82,8 +84,15 @@ func (d *Distributor) isUnaryDeletePath(p string) bool {
82
84
return strings .HasSuffix (path .Dir (p ), "/silence" )
83
85
}
84
86
87
+ func (d * Distributor ) isQuorumReadPath (p string ) (bool , merger.Merger ) {
88
+ if strings .HasSuffix (p , "/v1/alerts" ) {
89
+ return true , merger.V1Alerts {}
90
+ }
91
+ return false , nil
92
+ }
93
+
85
94
func (d * Distributor ) isUnaryReadPath (p string ) bool {
86
- return strings .HasSuffix (p , "/alerts" ) ||
95
+ return strings .HasSuffix (p , "/v2/ alerts" ) ||
87
96
strings .HasSuffix (p , "/alerts/groups" ) ||
88
97
strings .HasSuffix (p , "/silences" ) ||
89
98
strings .HasSuffix (path .Dir (p ), "/silence" ) ||
@@ -109,7 +118,7 @@ func (d *Distributor) DistributeRequest(w http.ResponseWriter, r *http.Request)
109
118
110
119
if r .Method == http .MethodPost {
111
120
if d .isQuorumWritePath (r .URL .Path ) {
112
- d .doQuorumWrite (userID , w , r , logger )
121
+ d .doQuorum (userID , w , r , logger , merger. Noop {} )
113
122
return
114
123
}
115
124
if d .isUnaryWritePath (r .URL .Path ) {
@@ -124,6 +133,10 @@ func (d *Distributor) DistributeRequest(w http.ResponseWriter, r *http.Request)
124
133
}
125
134
}
126
135
if r .Method == http .MethodGet || r .Method == http .MethodHead {
136
+ if ok , m := d .isQuorumReadPath (r .URL .Path ); ok {
137
+ d .doQuorum (userID , w , r , logger , m )
138
+ return
139
+ }
127
140
if d .isUnaryReadPath (r .URL .Path ) {
128
141
d .doUnary (userID , w , r , logger )
129
142
return
@@ -133,7 +146,7 @@ func (d *Distributor) DistributeRequest(w http.ResponseWriter, r *http.Request)
133
146
http .Error (w , "route not supported by distributor" , http .StatusNotFound )
134
147
}
135
148
136
- func (d * Distributor ) doQuorumWrite (userID string , w http.ResponseWriter , r * http.Request , logger log.Logger ) {
149
+ func (d * Distributor ) doQuorum (userID string , w http.ResponseWriter , r * http.Request , logger log.Logger , m merger. Merger ) {
137
150
var body []byte
138
151
var err error
139
152
if r .Body != nil {
@@ -149,13 +162,13 @@ func (d *Distributor) doQuorumWrite(userID string, w http.ResponseWriter, r *htt
149
162
}
150
163
}
151
164
152
- var firstSuccessfulResponse * httpgrpc.HTTPResponse
153
- var firstSuccessfulResponseMtx sync.Mutex
165
+ var responses [] * httpgrpc.HTTPResponse
166
+ var responsesMtx sync.Mutex
154
167
grpcHeaders := httpToHttpgrpcHeaders (r .Header )
155
168
err = ring .DoBatch (r .Context (), RingOp , d .alertmanagerRing , []uint32 {shardByUser (userID )}, func (am ring.InstanceDesc , _ []int ) error {
156
169
// Use a background context to make sure all alertmanagers get the request even if we return early.
157
170
localCtx := user .InjectOrgID (context .Background (), userID )
158
- sp , localCtx := opentracing .StartSpanFromContext (localCtx , "Distributor.doQuorumWrite " )
171
+ sp , localCtx := opentracing .StartSpanFromContext (localCtx , "Distributor.doQuorum " )
159
172
defer sp .Finish ()
160
173
161
174
resp , err := d .doRequest (localCtx , am , & httpgrpc.HTTPRequest {
@@ -172,11 +185,9 @@ func (d *Distributor) doQuorumWrite(userID string, w http.ResponseWriter, r *htt
172
185
return httpgrpc .ErrorFromHTTPResponse (resp )
173
186
}
174
187
175
- firstSuccessfulResponseMtx .Lock ()
176
- if firstSuccessfulResponse == nil {
177
- firstSuccessfulResponse = resp
178
- }
179
- firstSuccessfulResponseMtx .Unlock ()
188
+ responsesMtx .Lock ()
189
+ responses = append (responses , resp )
190
+ responsesMtx .Unlock ()
180
191
181
192
return nil
182
193
}, func () {})
@@ -186,12 +197,12 @@ func (d *Distributor) doQuorumWrite(userID string, w http.ResponseWriter, r *htt
186
197
return
187
198
}
188
199
189
- firstSuccessfulResponseMtx .Lock () // Another request might be ongoing after quorum.
190
- resp := firstSuccessfulResponse
191
- firstSuccessfulResponseMtx .Unlock ()
200
+ responsesMtx .Lock () // Another request might be ongoing after quorum.
201
+ resps := responses
202
+ responsesMtx .Unlock ()
192
203
193
- if resp != nil {
194
- respondFromHTTPGRPCResponse (w , resp )
204
+ if len ( resps ) > 0 {
205
+ respondFromMultipleHTTPGRPCResponses (w , logger , resps , m )
195
206
} else {
196
207
// This should not happen.
197
208
level .Error (logger ).Log ("msg" , "distributor did not receive any response from alertmanagers, but there were no errors" )
@@ -287,3 +298,29 @@ func httpToHttpgrpcHeaders(hs http.Header) []*httpgrpc.Header {
287
298
}
288
299
return result
289
300
}
301
+
302
+ func respondFromMultipleHTTPGRPCResponses (w http.ResponseWriter , logger log.Logger , responses []* httpgrpc.HTTPResponse , merger merger.Merger ) {
303
+ bodies := make ([][]byte , len (responses ))
304
+ for i , r := range responses {
305
+ bodies [i ] = r .Body
306
+ }
307
+
308
+ body , err := merger .MergeResponses (bodies )
309
+ if err != nil {
310
+ level .Error (logger ).Log ("msg" , "failed to merge responses for request" , "err" , err )
311
+ w .WriteHeader (http .StatusInternalServerError )
312
+ return
313
+ }
314
+
315
+ // It is assumed by using this function, the caller knows that the responses it receives
316
+ // have already been checked for success or failure, and that the headers will always
317
+ // match due to the nature of the request. If this is not the case, a different merge
318
+ // function should be implemented to cope with the differing responses.
319
+ response := & httpgrpc.HTTPResponse {
320
+ Code : responses [0 ].Code ,
321
+ Headers : responses [0 ].Headers ,
322
+ Body : body ,
323
+ }
324
+
325
+ respondFromHTTPGRPCResponse (w , response )
326
+ }
0 commit comments