github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/pfsagentd/pfsagentd-init/main.go (about) 1 // Copyright (c) 2015-2021, NVIDIA CORPORATION. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package main 5 6 import ( 7 "encoding/json" 8 "io/ioutil" 9 "log" 10 "net" 11 "net/http" 12 "os" 13 "os/exec" 14 "os/signal" 15 "runtime" 16 "strconv" 17 "strings" 18 "sync" 19 "time" 20 21 "github.com/swiftstack/ProxyFS/conf" 22 "golang.org/x/sys/unix" 23 ) 24 25 const ( 26 // PFSAgentReadyAttemptLimit defines the number of times pfsagentd-init 27 // will poll for the successful launching of each child pfsagentd instance 28 // 29 PFSAgentReadyAttemptLimit = uint32(100) 30 31 // PFSAgentReadyRetryDelay is the delay between polling attempts by 32 // pfsagentd-init of each child pfsagentd instance 33 // 34 PFSAgentReadyRetryDelay = "100ms" 35 ) 36 37 type pfsagentdInstanceStruct struct { 38 confFileName string // 39 confMap conf.ConfMap // 40 httpServerHostPort string // 41 stopChan chan struct{} // Closed by main() to tell (*pfsagentdInstanceStruct).daemon() to exit 42 } 43 44 type globalsStruct struct { 45 parentEntrypoint []string // A non-existent or empty PFSAGENT_PARENT_ENTRYPOINT will result in []string{} 46 parentCmd []string // A non-existent or empty PFSAGENT_PARENT_CMD will result in []string{} 47 parentWorkingDir string // A non-existent or empty PFSAGENT_PARENT_WORKING_DIR will result in "/" 48 pfsagentdReadyRetryDelay time.Duration // 49 pfsagentdInstance []*pfsagentdInstanceStruct // 50 pfsagentdInstanceUpChan chan struct{} // Signaled by each pfsagentdInstance once they are up 51 childErrorChan chan struct{} // Signaled by an unexpectedly exiting child to 52 // indicate that the entire container should exit 53 applicationCleanExitChan chan struct{} // Signaled by applicationDaemon() on clean exit 54 applicationDaemonStopChan chan struct{} // Closed by main() to tell applicationDaemon() to exit 55 signalChan chan os.Signal // Signaled by OS or Container Framework to trigger exit 56 pfsagentdInstanceWG sync.WaitGroup // 57 applicationDaemonWG sync.WaitGroup // 58 } 59 60 var globals globalsStruct 61 62 func main() { 63 var ( 64 err error 65 fileInfo os.FileInfo 66 fileInfoName string 67 fileInfoSlice []os.FileInfo 68 parentCmdBuf string 69 parentEntrypointBuf string 70 pfsagentdInstance *pfsagentdInstanceStruct 71 pfsagentdInstanceFUSEMountPointPath string 72 pfsagentdInstanceHTTPServerIPAddr string 73 pfsagentdInstanceHTTPServerTCPPort uint16 74 pfsagentdInstanceUpCount int 75 ) 76 77 log.Printf("Launch of %s\n", os.Args[0]) 78 79 parentEntrypointBuf = os.Getenv("PFSAGENT_PARENT_ENTRYPOINT") 80 if (parentEntrypointBuf == "") || (parentEntrypointBuf == "null") { 81 globals.parentEntrypoint = make([]string, 0) 82 } else { 83 globals.parentEntrypoint = make([]string, 0) 84 err = json.Unmarshal([]byte(parentEntrypointBuf), &globals.parentEntrypoint) 85 if err != nil { 86 log.Fatalf("Couldn't parse PFSAGENT_PARENT_ENTRYPOINT: \"%s\"\n", parentEntrypointBuf) 87 } 88 } 89 90 parentCmdBuf = os.Getenv("PFSAGENT_PARENT_CMD") 91 if (parentCmdBuf == "") || (parentCmdBuf == "null") { 92 globals.parentCmd = make([]string, 0) 93 } else { 94 globals.parentCmd = make([]string, 0) 95 err = json.Unmarshal([]byte(parentCmdBuf), &globals.parentCmd) 96 if err != nil { 97 log.Fatalf("Couldn't parse PFSAGENT_PARENT_CMD: \"%s\"\n", parentCmdBuf) 98 } 99 } 100 101 globals.parentWorkingDir = os.Getenv("PFSAGENT_PARENT_WORKING_DIR") 102 if globals.parentWorkingDir == "" { 103 globals.parentWorkingDir = "/" 104 } 105 106 fileInfoSlice, err = ioutil.ReadDir(".") 107 if err != nil { 108 log.Fatalf("Couldn't read directory: %v\n", err) 109 } 110 111 globals.pfsagentdReadyRetryDelay, err = time.ParseDuration(PFSAgentReadyRetryDelay) 112 if err != nil { 113 log.Fatalf("Couldn't parse PFSAgentReadyRetryDelay\n") 114 } 115 116 globals.pfsagentdInstance = make([]*pfsagentdInstanceStruct, 0) 117 118 for _, fileInfo = range fileInfoSlice { 119 fileInfoName = fileInfo.Name() 120 if strings.HasPrefix(fileInfoName, "pfsagent.conf_") { 121 if fileInfoName != "pfsagent.conf_TEMPLATE" { 122 pfsagentdInstance = &pfsagentdInstanceStruct{ 123 confFileName: fileInfoName, 124 stopChan: make(chan struct{}), 125 } 126 globals.pfsagentdInstance = append(globals.pfsagentdInstance, pfsagentdInstance) 127 } 128 } 129 } 130 131 globals.pfsagentdInstanceUpChan = make(chan struct{}, len(globals.pfsagentdInstance)) 132 globals.childErrorChan = make(chan struct{}, len(globals.pfsagentdInstance)+1) 133 134 globals.signalChan = make(chan os.Signal, 1) 135 signal.Notify(globals.signalChan, unix.SIGINT, unix.SIGTERM, unix.SIGHUP) 136 137 globals.pfsagentdInstanceWG.Add(len(globals.pfsagentdInstance)) 138 139 for _, pfsagentdInstance = range globals.pfsagentdInstance { 140 pfsagentdInstance.confMap, err = conf.MakeConfMapFromFile(pfsagentdInstance.confFileName) 141 if err != nil { 142 log.Fatalf("Unable to parse %s: %v\n", pfsagentdInstance.confFileName, err) 143 } 144 145 pfsagentdInstanceFUSEMountPointPath, err = pfsagentdInstance.confMap.FetchOptionValueString("Agent", "FUSEMountPointPath") 146 if err != nil { 147 log.Fatalf("In %s, parsing [Agent]FUSEMountPointPath failed: %v\n", pfsagentdInstance.confFileName, err) 148 } 149 150 err = os.MkdirAll(pfsagentdInstanceFUSEMountPointPath, 0777) 151 if err != nil { 152 log.Fatalf("For %s, could not create directory path %s: %v\n", pfsagentdInstance.confFileName, pfsagentdInstanceFUSEMountPointPath, err) 153 } 154 155 pfsagentdInstanceHTTPServerIPAddr, err = pfsagentdInstance.confMap.FetchOptionValueString("Agent", "HTTPServerIPAddr") 156 if err != nil { 157 log.Fatalf("In %s, parsing [Agent]HTTPServerIPAddr failed: %v\n", pfsagentdInstance.confFileName, err) 158 } 159 if pfsagentdInstanceHTTPServerIPAddr == "0.0.0.0" { 160 pfsagentdInstanceHTTPServerIPAddr = "127.0.0.1" 161 } else if pfsagentdInstanceHTTPServerIPAddr == "::" { 162 pfsagentdInstanceHTTPServerIPAddr = "::1" 163 } 164 165 pfsagentdInstanceHTTPServerTCPPort, err = pfsagentdInstance.confMap.FetchOptionValueUint16("Agent", "HTTPServerTCPPort") 166 if err != nil { 167 log.Fatalf("In %s, parsing [Agent]HTTPServerTCPPort failed: %v\n", pfsagentdInstance.confFileName, err) 168 } 169 170 pfsagentdInstance.httpServerHostPort = net.JoinHostPort(pfsagentdInstanceHTTPServerIPAddr, strconv.Itoa(int(pfsagentdInstanceHTTPServerTCPPort))) 171 172 go pfsagentdInstance.daemon() 173 } 174 175 if len(globals.pfsagentdInstance) > 0 { 176 pfsagentdInstanceUpCount = 0 177 178 for { 179 select { 180 case _ = <-globals.pfsagentdInstanceUpChan: 181 pfsagentdInstanceUpCount++ 182 if pfsagentdInstanceUpCount == len(globals.pfsagentdInstance) { 183 goto pfsagentdInstanceUpComplete 184 } 185 case _ = <-globals.childErrorChan: 186 for _, pfsagentdInstance = range globals.pfsagentdInstance { 187 close(pfsagentdInstance.stopChan) 188 } 189 190 globals.pfsagentdInstanceWG.Wait() 191 192 log.Fatalf("Abnormal exit during PFSAgent Instances creation\n") 193 case _ = <-globals.signalChan: 194 for _, pfsagentdInstance = range globals.pfsagentdInstance { 195 close(pfsagentdInstance.stopChan) 196 } 197 198 globals.pfsagentdInstanceWG.Wait() 199 200 log.Printf("Signaled exit during PFSAgent Instances creation\n") 201 os.Exit(0) 202 } 203 } 204 205 pfsagentdInstanceUpComplete: 206 } 207 208 globals.applicationCleanExitChan = make(chan struct{}, 1) 209 globals.applicationDaemonStopChan = make(chan struct{}) 210 211 globals.applicationDaemonWG.Add(1) 212 213 go applicationDaemon() 214 215 for { 216 select { 217 case _ = <-globals.childErrorChan: 218 close(globals.applicationDaemonStopChan) 219 220 globals.applicationDaemonWG.Wait() 221 222 for _, pfsagentdInstance = range globals.pfsagentdInstance { 223 close(pfsagentdInstance.stopChan) 224 } 225 226 globals.pfsagentdInstanceWG.Wait() 227 228 log.Fatalf("Abnormal exit after PFSAgent Instances creation\n") 229 case _ = <-globals.applicationCleanExitChan: 230 globals.applicationDaemonWG.Wait() 231 232 for _, pfsagentdInstance = range globals.pfsagentdInstance { 233 close(pfsagentdInstance.stopChan) 234 } 235 236 globals.pfsagentdInstanceWG.Wait() 237 238 log.Printf("Normal exit do to application clean exit\n") 239 os.Exit(0) 240 case _ = <-globals.signalChan: 241 close(globals.applicationDaemonStopChan) 242 243 globals.applicationDaemonWG.Wait() 244 245 for _, pfsagentdInstance = range globals.pfsagentdInstance { 246 close(pfsagentdInstance.stopChan) 247 } 248 249 globals.pfsagentdInstanceWG.Wait() 250 251 log.Printf("Signaled exit after PFSAgent Instances creation\n") 252 os.Exit(0) 253 } 254 } 255 } 256 257 func (pfsagentdInstance *pfsagentdInstanceStruct) daemon() { 258 var ( 259 cmd *exec.Cmd 260 cmdExitChan chan struct{} 261 err error 262 getResponse *http.Response 263 readyAttempt uint32 264 ) 265 266 cmd = exec.Command("pfsagentd", pfsagentdInstance.confFileName) 267 268 err = cmd.Start() 269 if err != nil { 270 globals.childErrorChan <- struct{}{} 271 globals.pfsagentdInstanceWG.Done() 272 runtime.Goexit() 273 } 274 275 readyAttempt = 1 276 277 for { 278 getResponse, err = http.Get("http://" + pfsagentdInstance.httpServerHostPort + "/version") 279 if err == nil { 280 _, err = ioutil.ReadAll(getResponse.Body) 281 if err != nil { 282 log.Fatalf("Failure to read GET Response Body: %v\n", err) 283 } 284 err = getResponse.Body.Close() 285 if err != nil { 286 log.Fatalf("Failure to close GET Response Body: %v\n", err) 287 } 288 289 if getResponse.StatusCode == http.StatusOK { 290 break 291 } 292 } 293 294 readyAttempt++ 295 296 if readyAttempt > PFSAgentReadyAttemptLimit { 297 log.Fatalf("Ready attempt limit exceeded for pfsagentd instance %s\n", pfsagentdInstance.confFileName) 298 } 299 300 time.Sleep(globals.pfsagentdReadyRetryDelay) 301 } 302 303 globals.pfsagentdInstanceUpChan <- struct{}{} 304 305 cmdExitChan = make(chan struct{}, 1) 306 go watchCmdDaemon(cmd, cmdExitChan) 307 308 for { 309 select { 310 case _ = <-cmdExitChan: 311 globals.childErrorChan <- struct{}{} 312 globals.pfsagentdInstanceWG.Done() 313 runtime.Goexit() 314 case _, _ = <-pfsagentdInstance.stopChan: 315 err = cmd.Process.Signal(unix.SIGTERM) 316 if err == nil { 317 _ = cmd.Wait() 318 } 319 globals.pfsagentdInstanceWG.Done() 320 runtime.Goexit() 321 } 322 } 323 } 324 325 func applicationDaemon() { 326 var ( 327 cmd *exec.Cmd 328 cmdArgs []string 329 cmdExitChan chan struct{} 330 cmdPath string 331 err error 332 ) 333 334 cmdArgs = make([]string, 0) 335 336 if len(globals.parentEntrypoint) > 0 { 337 cmdPath = globals.parentEntrypoint[0] 338 cmdArgs = globals.parentEntrypoint[1:] 339 340 if len(os.Args) > 1 { 341 cmdArgs = append(cmdArgs, os.Args[1:]...) 342 } else { 343 cmdArgs = append(cmdArgs, globals.parentCmd...) 344 } 345 } else { // len(globals.parentEntrypoint) == 0 346 if len(os.Args) > 1 { 347 cmdPath = os.Args[1] 348 cmdArgs = os.Args[2:] 349 } else { 350 if len(globals.parentCmd) > 0 { 351 cmdPath = globals.parentCmd[0] 352 cmdArgs = globals.parentCmd[1:] 353 } else { 354 cmdPath = "/bin/sh" 355 cmdArgs = make([]string, 0) 356 } 357 } 358 } 359 360 cmd = exec.Command(cmdPath, cmdArgs...) 361 362 cmd.Stdin = os.Stdin 363 cmd.Stdout = os.Stdout 364 cmd.Stderr = os.Stderr 365 366 err = cmd.Start() 367 if err != nil { 368 globals.childErrorChan <- struct{}{} 369 globals.applicationDaemonWG.Done() 370 runtime.Goexit() 371 } 372 373 cmdExitChan = make(chan struct{}, 1) 374 go watchCmdDaemon(cmd, cmdExitChan) 375 376 for { 377 select { 378 case _ = <-cmdExitChan: 379 if cmd.ProcessState.ExitCode() == 0 { 380 globals.applicationCleanExitChan <- struct{}{} 381 } else { 382 globals.childErrorChan <- struct{}{} 383 } 384 globals.applicationDaemonWG.Done() 385 runtime.Goexit() 386 case _, _ = <-globals.applicationDaemonStopChan: 387 globals.applicationDaemonWG.Done() 388 runtime.Goexit() 389 } 390 } 391 } 392 393 func watchCmdDaemon(cmd *exec.Cmd, cmdExitChan chan struct{}) { 394 _ = cmd.Wait() 395 396 cmdExitChan <- struct{}{} 397 }