github.phpd.cn/hashicorp/consul@v1.4.5/agent/prepared_query_endpoint.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 "strings" 8 9 cachetype "github.com/hashicorp/consul/agent/cache-types" 10 "github.com/hashicorp/consul/agent/consul" 11 "github.com/hashicorp/consul/agent/structs" 12 ) 13 14 // preparedQueryCreateResponse is used to wrap the query ID. 15 type preparedQueryCreateResponse struct { 16 ID string 17 } 18 19 // preparedQueryCreate makes a new prepared query. 20 func (s *HTTPServer) preparedQueryCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 21 args := structs.PreparedQueryRequest{ 22 Op: structs.PreparedQueryCreate, 23 } 24 s.parseDC(req, &args.Datacenter) 25 s.parseToken(req, &args.Token) 26 if err := decodeBody(req, &args.Query, nil); err != nil { 27 resp.WriteHeader(http.StatusBadRequest) 28 fmt.Fprintf(resp, "Request decode failed: %v", err) 29 return nil, nil 30 } 31 32 var reply string 33 if err := s.agent.RPC("PreparedQuery.Apply", &args, &reply); err != nil { 34 return nil, err 35 } 36 return preparedQueryCreateResponse{reply}, nil 37 } 38 39 // preparedQueryList returns all the prepared queries. 40 func (s *HTTPServer) preparedQueryList(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 41 var args structs.DCSpecificRequest 42 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 43 return nil, nil 44 } 45 46 var reply structs.IndexedPreparedQueries 47 defer setMeta(resp, &reply.QueryMeta) 48 RETRY_ONCE: 49 if err := s.agent.RPC("PreparedQuery.List", &args, &reply); err != nil { 50 return nil, err 51 } 52 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact { 53 args.AllowStale = false 54 args.MaxStaleDuration = 0 55 goto RETRY_ONCE 56 } 57 reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 58 59 // Use empty list instead of nil. 60 if reply.Queries == nil { 61 reply.Queries = make(structs.PreparedQueries, 0) 62 } 63 return reply.Queries, nil 64 } 65 66 // PreparedQueryGeneral handles all the general prepared query requests. 67 func (s *HTTPServer) PreparedQueryGeneral(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 68 switch req.Method { 69 case "POST": 70 return s.preparedQueryCreate(resp, req) 71 72 case "GET": 73 return s.preparedQueryList(resp, req) 74 75 default: 76 return nil, MethodNotAllowedError{req.Method, []string{"GET", "POST"}} 77 } 78 } 79 80 // parseLimit parses the optional limit parameter for a prepared query execution. 81 func parseLimit(req *http.Request, limit *int) error { 82 *limit = 0 83 if arg := req.URL.Query().Get("limit"); arg != "" { 84 i, err := strconv.Atoi(arg) 85 if err != nil { 86 return err 87 } 88 *limit = i 89 } 90 return nil 91 } 92 93 // preparedQueryExecute executes a prepared query. 94 func (s *HTTPServer) preparedQueryExecute(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 95 args := structs.PreparedQueryExecuteRequest{ 96 QueryIDOrName: id, 97 Agent: structs.QuerySource{ 98 Node: s.agent.config.NodeName, 99 Datacenter: s.agent.config.Datacenter, 100 Segment: s.agent.config.SegmentName, 101 }, 102 } 103 s.parseSource(req, &args.Source) 104 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 105 return nil, nil 106 } 107 if err := parseLimit(req, &args.Limit); err != nil { 108 return nil, fmt.Errorf("Bad limit: %s", err) 109 } 110 111 params := req.URL.Query() 112 if raw := params.Get("connect"); raw != "" { 113 val, err := strconv.ParseBool(raw) 114 if err != nil { 115 return nil, fmt.Errorf("Error parsing 'connect' value: %s", err) 116 } 117 118 args.Connect = val 119 } 120 121 var reply structs.PreparedQueryExecuteResponse 122 defer setMeta(resp, &reply.QueryMeta) 123 124 if args.QueryOptions.UseCache { 125 raw, m, err := s.agent.cache.Get(cachetype.PreparedQueryName, &args) 126 if err != nil { 127 // Don't return error if StaleIfError is set and we are within it and had 128 // a cached value. 129 if raw != nil && m.Hit && args.QueryOptions.StaleIfError > m.Age { 130 // Fall through to the happy path below 131 } else { 132 return nil, err 133 } 134 } 135 defer setCacheMeta(resp, &m) 136 r, ok := raw.(*structs.PreparedQueryExecuteResponse) 137 if !ok { 138 // This should never happen, but we want to protect against panics 139 return nil, fmt.Errorf("internal error: response type not correct") 140 } 141 reply = *r 142 } else { 143 RETRY_ONCE: 144 if err := s.agent.RPC("PreparedQuery.Execute", &args, &reply); err != nil { 145 // We have to check the string since the RPC sheds 146 // the specific error type. 147 if err.Error() == consul.ErrQueryNotFound.Error() { 148 resp.WriteHeader(http.StatusNotFound) 149 fmt.Fprint(resp, err.Error()) 150 return nil, nil 151 } 152 return nil, err 153 } 154 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact { 155 args.AllowStale = false 156 args.MaxStaleDuration = 0 157 goto RETRY_ONCE 158 } 159 } 160 reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 161 162 // Note that we translate using the DC that the results came from, since 163 // a query can fail over to a different DC than where the execute request 164 // was sent to. That's why we use the reply's DC and not the one from 165 // the args. 166 s.agent.TranslateAddresses(reply.Datacenter, reply.Nodes) 167 168 // Use empty list instead of nil. 169 if reply.Nodes == nil { 170 reply.Nodes = make(structs.CheckServiceNodes, 0) 171 } 172 return reply, nil 173 } 174 175 // preparedQueryExplain shows which query a name resolves to, the fully 176 // interpolated template (if it's a template), as well as additional info 177 // about the execution of a query. 178 func (s *HTTPServer) preparedQueryExplain(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 179 args := structs.PreparedQueryExecuteRequest{ 180 QueryIDOrName: id, 181 Agent: structs.QuerySource{ 182 Node: s.agent.config.NodeName, 183 Datacenter: s.agent.config.Datacenter, 184 Segment: s.agent.config.SegmentName, 185 }, 186 } 187 s.parseSource(req, &args.Source) 188 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 189 return nil, nil 190 } 191 if err := parseLimit(req, &args.Limit); err != nil { 192 return nil, fmt.Errorf("Bad limit: %s", err) 193 } 194 195 var reply structs.PreparedQueryExplainResponse 196 defer setMeta(resp, &reply.QueryMeta) 197 RETRY_ONCE: 198 if err := s.agent.RPC("PreparedQuery.Explain", &args, &reply); err != nil { 199 // We have to check the string since the RPC sheds 200 // the specific error type. 201 if err.Error() == consul.ErrQueryNotFound.Error() { 202 resp.WriteHeader(http.StatusNotFound) 203 fmt.Fprint(resp, err.Error()) 204 return nil, nil 205 } 206 return nil, err 207 } 208 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact { 209 args.AllowStale = false 210 args.MaxStaleDuration = 0 211 goto RETRY_ONCE 212 } 213 reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 214 return reply, nil 215 } 216 217 // preparedQueryGet returns a single prepared query. 218 func (s *HTTPServer) preparedQueryGet(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 219 args := structs.PreparedQuerySpecificRequest{ 220 QueryID: id, 221 } 222 if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done { 223 return nil, nil 224 } 225 226 var reply structs.IndexedPreparedQueries 227 defer setMeta(resp, &reply.QueryMeta) 228 RETRY_ONCE: 229 if err := s.agent.RPC("PreparedQuery.Get", &args, &reply); err != nil { 230 // We have to check the string since the RPC sheds 231 // the specific error type. 232 if err.Error() == consul.ErrQueryNotFound.Error() { 233 resp.WriteHeader(http.StatusNotFound) 234 fmt.Fprint(resp, err.Error()) 235 return nil, nil 236 } 237 return nil, err 238 } 239 if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact { 240 args.AllowStale = false 241 args.MaxStaleDuration = 0 242 goto RETRY_ONCE 243 } 244 reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() 245 return reply.Queries, nil 246 } 247 248 // preparedQueryUpdate updates a prepared query. 249 func (s *HTTPServer) preparedQueryUpdate(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 250 args := structs.PreparedQueryRequest{ 251 Op: structs.PreparedQueryUpdate, 252 } 253 s.parseDC(req, &args.Datacenter) 254 s.parseToken(req, &args.Token) 255 if req.ContentLength > 0 { 256 if err := decodeBody(req, &args.Query, nil); err != nil { 257 resp.WriteHeader(http.StatusBadRequest) 258 fmt.Fprintf(resp, "Request decode failed: %v", err) 259 return nil, nil 260 } 261 } 262 263 if args.Query == nil { 264 args.Query = &structs.PreparedQuery{} 265 } 266 267 // Take the ID from the URL, not the embedded one. 268 args.Query.ID = id 269 270 var reply string 271 if err := s.agent.RPC("PreparedQuery.Apply", &args, &reply); err != nil { 272 return nil, err 273 } 274 return nil, nil 275 } 276 277 // preparedQueryDelete deletes prepared query. 278 func (s *HTTPServer) preparedQueryDelete(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) { 279 args := structs.PreparedQueryRequest{ 280 Op: structs.PreparedQueryDelete, 281 Query: &structs.PreparedQuery{ 282 ID: id, 283 }, 284 } 285 s.parseDC(req, &args.Datacenter) 286 s.parseToken(req, &args.Token) 287 288 var reply string 289 if err := s.agent.RPC("PreparedQuery.Apply", &args, &reply); err != nil { 290 return nil, err 291 } 292 return nil, nil 293 } 294 295 // PreparedQuerySpecificOptions handles OPTIONS requests to prepared query endpoints. 296 func (s *HTTPServer) preparedQuerySpecificOptions(resp http.ResponseWriter, req *http.Request) interface{} { 297 path := req.URL.Path 298 switch { 299 case strings.HasSuffix(path, "/execute"): 300 resp.Header().Add("Allow", strings.Join([]string{"OPTIONS", "GET"}, ",")) 301 return resp 302 303 case strings.HasSuffix(path, "/explain"): 304 resp.Header().Add("Allow", strings.Join([]string{"OPTIONS", "GET"}, ",")) 305 return resp 306 307 default: 308 resp.Header().Add("Allow", strings.Join([]string{"OPTIONS", "GET", "PUT", "DELETE"}, ",")) 309 return resp 310 } 311 } 312 313 // PreparedQuerySpecific handles all the prepared query requests specific to a 314 // particular query. 315 func (s *HTTPServer) PreparedQuerySpecific(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 316 if req.Method == "OPTIONS" { 317 return s.preparedQuerySpecificOptions(resp, req), nil 318 } 319 320 path := req.URL.Path 321 id := strings.TrimPrefix(path, "/v1/query/") 322 323 switch { 324 case strings.HasSuffix(path, "/execute"): 325 if req.Method != "GET" { 326 return nil, MethodNotAllowedError{req.Method, []string{"GET"}} 327 } 328 id = strings.TrimSuffix(id, "/execute") 329 return s.preparedQueryExecute(id, resp, req) 330 331 case strings.HasSuffix(path, "/explain"): 332 if req.Method != "GET" { 333 return nil, MethodNotAllowedError{req.Method, []string{"GET"}} 334 } 335 id = strings.TrimSuffix(id, "/explain") 336 return s.preparedQueryExplain(id, resp, req) 337 338 default: 339 switch req.Method { 340 case "GET": 341 return s.preparedQueryGet(id, resp, req) 342 343 case "PUT": 344 return s.preparedQueryUpdate(id, resp, req) 345 346 case "DELETE": 347 return s.preparedQueryDelete(id, resp, req) 348 349 default: 350 return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}} 351 } 352 } 353 }