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  }