mirror of
https://github.com/iLoveElysia/openbilibili.git
synced 2026-03-14 05:46:26 -05:00
209 lines
6.3 KiB
Go
209 lines
6.3 KiB
Go
/*
|
|
Copyright 2018 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package entrypoint
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strconv"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
// InternalErrorCode is what we write to the marker file to
|
|
// indicate that we failed to start the wrapped command
|
|
InternalErrorCode = 127
|
|
// AbortedErrorCode is what we write to the marker file to
|
|
// indicate that we were terminated via a signal.
|
|
AbortedErrorCode = 130
|
|
|
|
// DefaultTimeout is the default timeout for the test
|
|
// process before SIGINT is sent
|
|
DefaultTimeout = 120 * time.Minute
|
|
|
|
// DefaultGracePeriod is the default timeout for the test
|
|
// process after SIGINT is sent before SIGKILL is sent
|
|
DefaultGracePeriod = 15 * time.Second
|
|
)
|
|
|
|
var (
|
|
// errTimedOut is used as the command's error when the command
|
|
// is terminated after the timeout is reached
|
|
errTimedOut = errors.New("process timed out")
|
|
// errAborted is used as the command's error when the command
|
|
// is shut down by an external signal
|
|
errAborted = errors.New("process aborted")
|
|
)
|
|
|
|
// Run executes the test process then writes the exit code to the marker file.
|
|
// This function returns the status code that should be passed to os.Exit().
|
|
func (o Options) Run() int {
|
|
code, err := o.ExecuteProcess()
|
|
if err != nil {
|
|
logrus.WithError(err).Error("Error executing test process")
|
|
}
|
|
if err := o.mark(code); err != nil {
|
|
logrus.WithError(err).Error("Error writing exit code to marker file")
|
|
return InternalErrorCode
|
|
}
|
|
return code
|
|
}
|
|
|
|
// ExecuteProcess creates the artifact directory then executes the process as
|
|
// configured, writing the output to the process log.
|
|
func (o Options) ExecuteProcess() (int, error) {
|
|
if o.ArtifactDir != "" {
|
|
if err := os.MkdirAll(o.ArtifactDir, os.ModePerm); err != nil {
|
|
return InternalErrorCode, fmt.Errorf("could not create artifact directory(%s): %v", o.ArtifactDir, err)
|
|
}
|
|
}
|
|
processLogFile, err := os.Create(o.ProcessLog)
|
|
if err != nil {
|
|
return InternalErrorCode, fmt.Errorf("could not create process logfile(%s): %v", o.ProcessLog, err)
|
|
}
|
|
defer processLogFile.Close()
|
|
|
|
output := io.MultiWriter(os.Stdout, processLogFile)
|
|
logrus.SetOutput(output)
|
|
defer logrus.SetOutput(os.Stdout)
|
|
|
|
executable := o.Args[0]
|
|
var arguments []string
|
|
if len(o.Args) > 1 {
|
|
arguments = o.Args[1:]
|
|
}
|
|
command := exec.Command(executable, arguments...)
|
|
command.Stderr = output
|
|
command.Stdout = output
|
|
if err := command.Start(); err != nil {
|
|
return InternalErrorCode, fmt.Errorf("could not start the process: %v", err)
|
|
}
|
|
|
|
// if we get asked to terminate we need to forward
|
|
// that to the wrapped process as if it timed out
|
|
interrupt := make(chan os.Signal, 1)
|
|
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
|
|
|
timeout := optionOrDefault(o.Timeout, DefaultTimeout)
|
|
gracePeriod := optionOrDefault(o.GracePeriod, DefaultGracePeriod)
|
|
var commandErr error
|
|
cancelled, aborted := false, false
|
|
done := make(chan error)
|
|
go func() {
|
|
done <- command.Wait()
|
|
}()
|
|
select {
|
|
case err := <-done:
|
|
commandErr = err
|
|
case <-time.After(timeout):
|
|
logrus.Errorf("Process did not finish before %s timeout", timeout)
|
|
cancelled = true
|
|
gracefullyTerminate(command, done, gracePeriod)
|
|
case s := <-interrupt:
|
|
logrus.Errorf("Entrypoint received interrupt: %v", s)
|
|
cancelled = true
|
|
aborted = true
|
|
gracefullyTerminate(command, done, gracePeriod)
|
|
}
|
|
|
|
var returnCode int
|
|
if cancelled {
|
|
if aborted {
|
|
commandErr = errAborted
|
|
returnCode = AbortedErrorCode
|
|
} else {
|
|
commandErr = errTimedOut
|
|
returnCode = InternalErrorCode
|
|
}
|
|
} else {
|
|
if status, ok := command.ProcessState.Sys().(syscall.WaitStatus); ok {
|
|
returnCode = status.ExitStatus()
|
|
} else if commandErr == nil {
|
|
returnCode = 0
|
|
} else {
|
|
returnCode = 1
|
|
}
|
|
|
|
if returnCode != 0 {
|
|
commandErr = fmt.Errorf("wrapped process failed: %v", commandErr)
|
|
}
|
|
}
|
|
return returnCode, commandErr
|
|
}
|
|
|
|
func (o *Options) mark(exitCode int) error {
|
|
content := []byte(strconv.Itoa(exitCode))
|
|
|
|
// create temp file in the same directory as the desired marker file
|
|
dir := filepath.Dir(o.MarkerFile)
|
|
tempFile, err := ioutil.TempFile(dir, "temp-marker")
|
|
if err != nil {
|
|
return fmt.Errorf("could not create temp marker file in %s: %v", dir, err)
|
|
}
|
|
// write the exit code to the tempfile, sync to disk and close
|
|
if _, err = tempFile.Write(content); err != nil {
|
|
return fmt.Errorf("could not write to temp marker file (%s): %v", tempFile.Name(), err)
|
|
}
|
|
if err = tempFile.Sync(); err != nil {
|
|
return fmt.Errorf("could not sync temp marker file (%s): %v", tempFile.Name(), err)
|
|
}
|
|
tempFile.Close()
|
|
// set desired permission bits, then rename to the desired file name
|
|
if err = os.Chmod(tempFile.Name(), os.ModePerm); err != nil {
|
|
return fmt.Errorf("could not chmod (%x) temp marker file (%s): %v", os.ModePerm, tempFile.Name(), err)
|
|
}
|
|
if err := os.Rename(tempFile.Name(), o.MarkerFile); err != nil {
|
|
return fmt.Errorf("could not move marker file to destination path (%s): %v", o.MarkerFile, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// optionOrDefault defaults to a value if option
|
|
// is the zero value
|
|
func optionOrDefault(option, defaultValue time.Duration) time.Duration {
|
|
if option == 0 {
|
|
return defaultValue
|
|
}
|
|
|
|
return option
|
|
}
|
|
|
|
func gracefullyTerminate(command *exec.Cmd, done <-chan error, gracePeriod time.Duration) {
|
|
if err := command.Process.Signal(os.Interrupt); err != nil {
|
|
logrus.WithError(err).Error("Could not interrupt process after timeout")
|
|
}
|
|
select {
|
|
case <-done:
|
|
logrus.Errorf("Process gracefully exited before %s grace period", gracePeriod)
|
|
// but we ignore the output error as we will want errTimedOut
|
|
case <-time.After(gracePeriod):
|
|
logrus.Errorf("Process did not exit before %s grace period", gracePeriod)
|
|
if err := command.Process.Kill(); err != nil {
|
|
logrus.WithError(err).Error("Could not kill process after grace period")
|
|
}
|
|
}
|
|
}
|