blob: 84ecaf7bf16d142a563897d637bcd9f7bf7ab39b [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001// Copyright 2016 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package runtime
16
17import (
18 "errors"
19 "fmt"
20 "io"
21 "net"
22 "net/http"
23 "sort"
24 "strconv"
25 "strings"
26)
27
28type httpHandler struct{}
29
30func serve(host string) error {
31 ln, err := net.Listen("tcp", host)
32 if err != nil {
33 return err
34 }
35 go http.Serve(ln, &httpHandler{})
36 return nil
37}
38
39func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
40 // Ensures the server(runtime) doesn't panic due to the execution of
41 // panic failpoints during processing of the HTTP request, as the
42 // sender of the HTTP request should not be affected by the execution
43 // of the panic failpoints and crash as a side effect
44 panicMu.Lock()
45 defer panicMu.Unlock()
46
47 // flush before unlocking so a panic failpoint won't
48 // take down the http server before it sends the response
49 defer flush(w)
50
51 key := r.RequestURI
52 if len(key) == 0 || key[0] != '/' {
53 http.Error(w, "malformed request URI", http.StatusBadRequest)
54 return
55 }
56 key = key[1:]
57
58 switch {
59 // sets the failpoint
60 case r.Method == "PUT":
61 v, err := io.ReadAll(r.Body)
62 if err != nil {
63 http.Error(w, "failed ReadAll in PUT", http.StatusBadRequest)
64 return
65 }
66
67 fpMap := map[string]string{key: string(v)}
68 if strings.EqualFold(key, "failpoints") {
69 fpMap, err = parseFailpoints(string(v))
70 if err != nil {
71 http.Error(w, fmt.Sprintf("fail to parse failpoint: %v", err), http.StatusBadRequest)
72 return
73 }
74 }
75
76 for k, v := range fpMap {
77 if err := Enable(k, v); err != nil {
78 http.Error(w, fmt.Sprintf("fail to set failpoint: %v", err), http.StatusBadRequest)
79 return
80 }
81 }
82 w.WriteHeader(http.StatusNoContent)
83
84 // gets status of the failpoint
85 case r.Method == "GET":
86 if len(key) == 0 {
87 fps := list()
88 sort.Strings(fps)
89 lines := make([]string, len(fps))
90 for i := range lines {
91 s, _, _ := Status(fps[i])
92 lines[i] = fps[i] + "=" + s
93 }
94 w.Write([]byte(strings.Join(lines, "\n") + "\n"))
95 } else if strings.HasSuffix(key, "/count") {
96 fp := key[:len(key)-len("/count")]
97 _, count, err := Status(fp)
98 if err != nil {
99 if errors.Is(err, ErrNoExist) {
100 http.Error(w, "failed to GET: "+err.Error(), http.StatusNotFound)
101 } else {
102 http.Error(w, "failed to GET: "+err.Error(), http.StatusInternalServerError)
103 }
104 return
105 }
106 w.Write([]byte(strconv.Itoa(count)))
107 } else {
108 status, _, err := Status(key)
109 if err != nil {
110 http.Error(w, "failed to GET: "+err.Error(), http.StatusNotFound)
111 }
112 w.Write([]byte(status + "\n"))
113 }
114
115 // deactivates a failpoint
116 case r.Method == "DELETE":
117 if err := Disable(key); err != nil {
118 http.Error(w, "failed to delete failpoint "+err.Error(), http.StatusBadRequest)
119 return
120 }
121 w.WriteHeader(http.StatusNoContent)
122 default:
123 w.Header().Add("Allow", "DELETE")
124 w.Header().Add("Allow", "GET")
125 w.Header().Set("Allow", "PUT")
126 http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
127 }
128}
129
130func flush(w http.ResponseWriter) {
131 if f, ok := w.(http.Flusher); ok {
132 f.Flush()
133 }
134}