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  }