volcano.sh/volcano@v1.9.0/pkg/util/socket.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package util 18 19 import ( 20 "context" 21 "fmt" 22 "html" 23 "net" 24 "net/http" 25 "os" 26 "path/filepath" 27 "strconv" 28 "sync" 29 "time" 30 31 "golang.org/x/sys/unix" 32 "k8s.io/apimachinery/pkg/util/runtime" 33 "k8s.io/klog/v2" 34 ) 35 36 const ( 37 DefaultSocketDir = "/tmp/klog-socks" // Default directory storing socket files 38 SocketSuffix = "-klog.sock" 39 SocketDirEnvName = "DEBUG_SOCKET_DIR" 40 // The HTTP request patterns 41 setLogLevelPath = "/setlevel" 42 getLogLevelPath = "/getlevel" 43 exampleSocketCli = "\"Failed to change klog log level, because got wrong value from level argument\\n\"+\n\t\t\t\t\"example: curl --unix-socket /tmp/klog-socks/componentName-klog.sock \\\"http://localhost/setlevel?level=8&duration=60s\\\"\\n\"+\n\t\t\t\t\"level=8 means changing klog log level to 8\\n\"+\n\t\t\t\t\"duration=60s means maintaining level=8 for 60 seconds[60m -> 60 minutes; 60h -> 60 hours]\"" 44 ) 45 46 var ( 47 // When users frequently make request to change klog log level, the previously registered timer may not expire. 48 // To improve performance, cancel the previous timer. prevCtx, prevCtxCancelFunc is used to achieve this target. 49 prevCtx context.Context 50 prevCtxCancelFunc context.CancelFunc 51 52 // currentLogLevel stores current log level 53 currentLogLevel string 54 // startupLogLevel stores start-up log level 55 startupLogLevel string 56 // mutex is used to avoid data race about prevCtx, prevCtxCancelFunc and currentLogLevel 57 mutex sync.RWMutex 58 ) 59 60 // responseOk returns a statusOK response to client 61 func responseOk(w *http.ResponseWriter, okMsg string) { 62 (*w).Header().Set("Content-Type", "text/plain; charset=utf-8") 63 (*w).Header().Set("X-Content-Type-Options", "nosniff") 64 _, err := fmt.Fprint(*w, okMsg) 65 if err != nil { 66 klog.Error(err) 67 return 68 } 69 } 70 71 // responseError returns an error response containing specific httpCode and errMsg to client 72 func responseError(w *http.ResponseWriter, errMsg string, httpCode int) { 73 http.Error(*w, errMsg, httpCode) 74 } 75 76 // modifyLoglevel will try to change current klog's log level to newLogLevel and assign it to currentLogLevel. 77 // After prevCtxCancelFunc function corresponding to last timer executed, prevCtx and prevCtxCancelFunc will be reassigned 78 // in order to represent brand-new timer. 79 func modifyLoglevel(newLogLevel string) error { 80 mutex.Lock() 81 defer mutex.Unlock() 82 83 // Change klog log level to new value 84 var loglevel klog.Level 85 if err := loglevel.Set(newLogLevel); err != nil { 86 return err 87 } 88 currentLogLevel = newLogLevel 89 90 // Cancel the previous timer. 91 if prevCtxCancelFunc != nil { 92 prevCtxCancelFunc() 93 } 94 prevCtx, prevCtxCancelFunc = context.WithCancel(context.Background()) 95 return nil 96 } 97 98 // reset creates a timer to make klog recover to start-up log level. 99 func reset(ctx context.Context, duration time.Duration) { 100 defer runtime.HandleCrash() 101 select { 102 // Create a timer 103 case <-time.After(duration): 104 var loglevel klog.Level 105 mutex.Lock() 106 defer mutex.Unlock() 107 if err := loglevel.Set(startupLogLevel); err != nil { 108 klog.Error(err) 109 return 110 } 111 currentLogLevel = startupLogLevel 112 klog.InfoS("Klog recover to start-up log level successfully", "startupLogLevel", startupLogLevel) 113 // Cancel previous timer 114 case <-ctx.Done(): 115 klog.InfoS("Cancel previous timer successfully") 116 } 117 } 118 119 // installKlogLogLevelHandler registers the HTTP request patterns that can set/get current klog log level 120 func installKlogLogLevelHandler(mux *http.ServeMux, startup string) { 121 currentLogLevel, startupLogLevel = startup, startup 122 // Register the HTTP request patterns that can change klog log level 123 mux.Handle(setLogLevelPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 124 values := r.URL.Query() 125 rawLevel := values.Get("level") 126 rawDuration := values.Get("duration") 127 // Escape the data that needs to be output to the log, Prevent Reflected cross-site scripting 128 rawLevel = html.EscapeString(rawLevel) 129 rawDuration = html.EscapeString(rawDuration) 130 var duration time.Duration 131 var err error 132 // Validate argument in request 133 if level, err := strconv.ParseInt(rawLevel, 10, 64); err != nil || level <= 0 { 134 responseError(&w, exampleSocketCli, http.StatusBadRequest) 135 return 136 } 137 if duration, err = time.ParseDuration(rawDuration); err != nil || duration.Milliseconds() <= 0 { 138 responseError(&w, exampleSocketCli, http.StatusBadRequest) 139 return 140 } 141 142 if err := modifyLoglevel(rawLevel); err != nil { 143 responseError(&w, fmt.Sprintf("Failed to change klog log level. Error: %v\n", err.Error()), http.StatusInternalServerError) 144 return 145 } 146 147 mutex.RLock() 148 // Create a timer to make klog recover to start-up log level. 149 // There will be more than one timer using same prevCtx variable under extreme conditions. 150 // Therefore, put reset function in mutex range. 151 go reset(prevCtx, duration) 152 responseOk(&w, fmt.Sprintf("Change klog log level to %s successfully and for %v\n", currentLogLevel, duration)) 153 mutex.RUnlock() 154 })) 155 156 // Register the HTTP request patterns that can get current klog log level 157 mux.Handle(getLogLevelPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 158 mutex.RLock() 159 responseOk(&w, fmt.Sprintf("Current klog log level: %s\n", currentLogLevel)) 160 mutex.RUnlock() 161 })) 162 } 163 164 // listenUnix does net.Listen for a unix socket 165 func listenUnix(componentName string, socketDir string) (net.Listener, error) { 166 // Use default directory to store socket files 167 if len(socketDir) == 0 { 168 socketDir = DefaultSocketDir 169 } 170 171 // Check whether KlogLogLevelSocketDir exists 172 if _, err := os.Stat(socketDir); os.IsNotExist(err) { 173 if err = os.MkdirAll(socketDir, 0750); err != nil { 174 return nil, fmt.Errorf("error creating klog log level socket dir: %v", err) 175 } 176 } 177 178 // Specify socket file full path 179 socketFileFullName := componentName + SocketSuffix 180 socketFileFullPath := filepath.Join(socketDir, socketFileFullName) 181 182 // Remove any socket, stale or not, but fall through for other files 183 fi, err := os.Stat(socketFileFullPath) 184 if err == nil && (fi.Mode()&os.ModeSocket) != 0 { 185 err := os.Remove(socketFileFullPath) 186 if err != nil { 187 klog.ErrorS(err, "failed to remote socket file", "file", socketFileFullPath) 188 return nil, err 189 } 190 } 191 192 // Default to only user accessible socket, caller can open up later if desired 193 // Result perm: 777 - 077 = 700 194 oldmask := unix.Umask(0077) 195 l, err := net.Listen("unix", socketFileFullPath) 196 unix.Umask(oldmask) 197 198 return l, err 199 } 200 201 // serveOnListener starts the server using given listener, loops forever. 202 func serveOnListener(l net.Listener, m *http.ServeMux) error { 203 server := http.Server{ 204 Handler: m, 205 } 206 return server.Serve(l) 207 } 208 209 // ListenAndServeKlogLogLevel registers a server on specific component to handle the HTTP request which set/get klog log level 210 func ListenAndServeKlogLogLevel(componentName string, startupLogLevel string, socketDir string) { 211 var err error 212 defer runtime.HandleCrash() 213 214 mux := http.NewServeMux() 215 installKlogLogLevelHandler(mux, startupLogLevel) 216 217 var listener net.Listener 218 listener, err = listenUnix(componentName, socketDir) 219 if err != nil { 220 return 221 } 222 223 if err = serveOnListener(listener, mux); err != nil { 224 return 225 } 226 }