bcf2cc2de3
* Default gRPC port; Start on some basic tests for argument and ENV handling; Make Exec test less platform-dependent. * Allow hot-restarts * Remove debug
169 lines
4.1 KiB
Go
169 lines
4.1 KiB
Go
// +build linux darwin
|
|
|
|
package envoy
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func isHotRestartOption(s string) bool {
|
|
restartOpts := []string{
|
|
"--restart-epoch",
|
|
"--hot-restart-version",
|
|
"--drain-time-s",
|
|
"--parent-shutdown-time-s",
|
|
}
|
|
for _, opt := range restartOpts {
|
|
if s == opt {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(s, opt+"=") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasHotRestartOption(argSets ...[]string) bool {
|
|
for _, args := range argSets {
|
|
for _, opt := range args {
|
|
if isHotRestartOption(opt) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func execEnvoy(binary string, prefixArgs, suffixArgs []string, bootstrapJson []byte) error {
|
|
// Write the Envoy bootstrap config file out to disk in a pocket universe
|
|
// visible only to the current process (and exec'd future selves).
|
|
fd, err := writeEphemeralEnvoyTempFile(bootstrapJson)
|
|
if err != nil {
|
|
return errors.New("Could not write envoy bootstrap config to a temp file: " + err.Error())
|
|
}
|
|
|
|
// On unix systems after exec the file descriptors that we should see:
|
|
//
|
|
// 0: stdin
|
|
// 1: stdout
|
|
// 2: stderr
|
|
// ... any open file descriptors from the parent without CLOEXEC set
|
|
//
|
|
// Above we explicitly disabled CLOEXEC for our temp file, so assuming
|
|
// FD numbers survive across execs, it should just be the value of
|
|
// `fd`. This is accessible as a file itself (trippy!) under
|
|
// /dev/fd/$FDNUMBER.
|
|
magicPath := filepath.Join("/dev/fd", strconv.Itoa(int(fd)))
|
|
|
|
// We default to disabling hot restart because it makes it easier to run
|
|
// multiple envoys locally for testing without them trying to share memory and
|
|
// unix sockets and complain about being different IDs. But if user is
|
|
// actually configuring hot-restart explicitly with the --restart-epoch option
|
|
// then don't disable it!
|
|
disableHotRestart := !hasHotRestartOption(prefixArgs, suffixArgs)
|
|
|
|
// First argument needs to be the executable name.
|
|
envoyArgs := []string{binary}
|
|
envoyArgs = append(envoyArgs, prefixArgs...)
|
|
envoyArgs = append(envoyArgs, "--v2-config-only",
|
|
"--config-path",
|
|
magicPath,
|
|
)
|
|
if disableHotRestart {
|
|
envoyArgs = append(envoyArgs, "--disable-hot-restart")
|
|
}
|
|
envoyArgs = append(envoyArgs, suffixArgs...)
|
|
|
|
// Exec
|
|
if err = unix.Exec(binary, envoyArgs, os.Environ()); err != nil {
|
|
return errors.New("Failed to exec envoy: " + err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeEphemeralEnvoyTempFile(b []byte) (uintptr, error) {
|
|
f, err := ioutil.TempFile("", "envoy-ephemeral-config")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
errFn := func(err error) (uintptr, error) {
|
|
_ = f.Close()
|
|
return 0, err
|
|
}
|
|
|
|
// TempFile already does this, but it's cheap to reinforce that we
|
|
// WANT the default behavior.
|
|
if err := f.Chmod(0600); err != nil {
|
|
return errFn(err)
|
|
}
|
|
|
|
// Immediately unlink the file as we are going to just pass the
|
|
// file descriptor, not the path.
|
|
if err = os.Remove(f.Name()); err != nil {
|
|
return errFn(err)
|
|
}
|
|
if _, err = f.Write(b); err != nil {
|
|
return errFn(err)
|
|
}
|
|
// Rewind the file descriptor so Envoy can read it.
|
|
if _, err = f.Seek(0, io.SeekStart); err != nil {
|
|
return errFn(err)
|
|
}
|
|
|
|
// Disable CLOEXEC so that this file descriptor is available
|
|
// to the exec'd Envoy.
|
|
if err := setCloseOnExec(f.Fd(), false); err != nil {
|
|
return errFn(err)
|
|
}
|
|
|
|
return f.Fd(), nil
|
|
}
|
|
|
|
// isCloseOnExec checks the provided file descriptor to see if the CLOEXEC flag
|
|
// is set.
|
|
func isCloseOnExec(fd uintptr) (bool, error) {
|
|
flags, err := getFdFlags(fd)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return flags&unix.FD_CLOEXEC != 0, nil
|
|
}
|
|
|
|
// setCloseOnExec sets or unsets the CLOEXEC flag on the provided file descriptor
|
|
// depending upon the value of the enabled arg.
|
|
func setCloseOnExec(fd uintptr, enabled bool) error {
|
|
flags, err := getFdFlags(fd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newFlags := flags
|
|
if enabled {
|
|
newFlags |= unix.FD_CLOEXEC
|
|
} else {
|
|
newFlags &= ^unix.FD_CLOEXEC
|
|
}
|
|
|
|
if newFlags == flags {
|
|
return nil // noop
|
|
}
|
|
|
|
_, err = unix.FcntlInt(fd, unix.F_SETFD, newFlags)
|
|
return err
|
|
}
|
|
|
|
func getFdFlags(fd uintptr) (int, error) {
|
|
return unix.FcntlInt(fd, unix.F_GETFD, 0)
|
|
}
|