1// +build linux darwin 2 3package envoy 4 5import ( 6 "errors" 7 "fmt" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strings" 12 "syscall" 13 "time" 14 15 "golang.org/x/sys/unix" 16) 17 18// testSelfExecOverride is a way for the tests to no fork-bomb themselves by 19// self-executing the whole test suite for each case recursively. It's gross but 20// the least gross option I could think of. 21var testSelfExecOverride string 22 23func isHotRestartOption(s string) bool { 24 restartOpts := []string{ 25 "--restart-epoch", 26 "--hot-restart-version", 27 "--drain-time-s", 28 "--parent-shutdown-time-s", 29 } 30 for _, opt := range restartOpts { 31 if s == opt { 32 return true 33 } 34 if strings.HasPrefix(s, opt+"=") { 35 return true 36 } 37 } 38 return false 39} 40 41func hasHotRestartOption(argSets ...[]string) bool { 42 for _, args := range argSets { 43 for _, opt := range args { 44 if isHotRestartOption(opt) { 45 return true 46 } 47 } 48 } 49 return false 50} 51 52func hasMaxObjNameLenOption(argSets ...[]string) bool { 53 for _, args := range argSets { 54 for _, opt := range args { 55 if opt == "--max-obj-name-len" { 56 return true 57 } 58 } 59 } 60 return false 61} 62 63func makeBootstrapPipe(bootstrapJSON []byte) (string, error) { 64 pipeFile := filepath.Join(os.TempDir(), 65 fmt.Sprintf("envoy-%x-bootstrap.json", time.Now().UnixNano()+int64(os.Getpid()))) 66 67 err := syscall.Mkfifo(pipeFile, 0600) 68 if err != nil { 69 return pipeFile, err 70 } 71 72 // Get our own executable path. 73 execPath, err := os.Executable() 74 if err != nil { 75 return pipeFile, err 76 } 77 78 if testSelfExecOverride != "" { 79 execPath = testSelfExecOverride 80 } else if strings.HasSuffix(execPath, "/envoy.test") { 81 return pipeFile, fmt.Errorf("I seem to be running in a test binary without " + 82 "overriding the self-executable. Not doing that - it will make you sad. " + 83 "See testSelfExecOverride.") 84 } 85 86 // Exec the pipe-bootstrap internal sub-command which will write the bootstrap 87 // from STDIN to the named pipe (once Envoy opens it) and then clean up the 88 // file for us. 89 cmd := exec.Command(execPath, "connect", "envoy", "pipe-bootstrap", pipeFile) 90 stdin, err := cmd.StdinPipe() 91 if err != nil { 92 return pipeFile, err 93 } 94 95 // Write the config 96 n, err := stdin.Write(bootstrapJSON) 97 // Close STDIN whether it was successful or not 98 stdin.Close() 99 if err != nil { 100 return pipeFile, err 101 } 102 if n < len(bootstrapJSON) { 103 return pipeFile, fmt.Errorf("failed writing boostrap to child STDIN: %s", err) 104 } 105 106 err = cmd.Start() 107 if err != nil { 108 return pipeFile, err 109 } 110 111 // We can't wait for the process since we need to exec into Envoy before it 112 // will be able to complete so it will be remain as a zombie until Envoy is 113 // killed then will be reaped by the init process (pid 0). This is all a bit 114 // gross but the cleanest workaround I can think of for Envoy 1.10 not 115 // supporting /dev/fd/<fd> config paths any more. So we are done and leaving 116 // the child to run it's course without reaping it. 117 return pipeFile, nil 118} 119 120func execEnvoy(binary string, prefixArgs, suffixArgs []string, bootstrapJSON []byte) error { 121 pipeFile, err := makeBootstrapPipe(bootstrapJSON) 122 if err != nil { 123 os.RemoveAll(pipeFile) 124 return err 125 } 126 // We don't defer a cleanup since we are about to Exec into Envoy which means 127 // defer will never fire. The child process cleans up for us in the happy 128 // path. 129 130 // We default to disabling hot restart because it makes it easier to run 131 // multiple envoys locally for testing without them trying to share memory and 132 // unix sockets and complain about being different IDs. But if user is 133 // actually configuring hot-restart explicitly with the --restart-epoch option 134 // then don't disable it! 135 disableHotRestart := !hasHotRestartOption(prefixArgs, suffixArgs) 136 137 // First argument needs to be the executable name. 138 envoyArgs := []string{binary} 139 envoyArgs = append(envoyArgs, prefixArgs...) 140 envoyArgs = append(envoyArgs, "--config-path", pipeFile) 141 if disableHotRestart { 142 envoyArgs = append(envoyArgs, "--disable-hot-restart") 143 } 144 if !hasMaxObjNameLenOption(prefixArgs, suffixArgs) { 145 envoyArgs = append(envoyArgs, "--max-obj-name-len", "256") 146 } 147 envoyArgs = append(envoyArgs, suffixArgs...) 148 149 // Exec 150 if err = unix.Exec(binary, envoyArgs, os.Environ()); err != nil { 151 return errors.New("Failed to exec envoy: " + err.Error()) 152 } 153 154 return nil 155} 156