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