Andrey Smirnov 40a5b7c177
feat(init): expose networkd as goroutine-based server (#682)
This adds generic goroutine runner which simply wraps service as process
goroutine. It supports log redirection and basic panic handling.

DHCP-related part of the network package was slightly adjusted to run as
service with logging updates (to redirect logs to a file) and context
canceling.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2019-05-27 17:07:28 +03:00

155 lines
3.4 KiB
Go

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package process
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/talos-systems/talos/internal/app/init/pkg/system/events"
processlogger "github.com/talos-systems/talos/internal/app/init/pkg/system/log"
"github.com/talos-systems/talos/internal/app/init/pkg/system/runner"
"github.com/talos-systems/talos/internal/pkg/constants"
"github.com/talos-systems/talos/pkg/userdata"
)
// processRunner is a runner.Runner that runs a process on the host.
type processRunner struct {
data *userdata.UserData
args *runner.Args
opts *runner.Options
stop chan struct{}
stopped chan struct{}
}
// NewRunner creates runner.Runner that runs a process on the host
func NewRunner(data *userdata.UserData, args *runner.Args, setters ...runner.Option) runner.Runner {
r := &processRunner{
data: data,
args: args,
opts: runner.DefaultOptions(),
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
for _, setter := range setters {
setter(r.opts)
}
return r
}
// Open implements the Runner interface.
func (p *processRunner) Open(ctx context.Context) error {
return nil
}
// Run implements the Runner interface.
func (p *processRunner) Run(eventSink events.Recorder) error {
defer close(p.stopped)
return p.run(eventSink)
}
// Stop implements the Runner interface
func (p *processRunner) Stop() error {
close(p.stop)
<-p.stopped
p.stop = make(chan struct{})
p.stopped = make(chan struct{})
return nil
}
// Close implements the Runner interface.
func (p *processRunner) Close() error {
return nil
}
func (p *processRunner) build() (cmd *exec.Cmd, err error) {
cmd = exec.Command(p.args.ProcessArgs[0], p.args.ProcessArgs[1:]...)
// Set the environment for the service.
cmd.Env = append([]string{fmt.Sprintf("PATH=%s", constants.PATH)}, p.opts.Env...)
// Setup logging.
w, err := processlogger.New(p.args.ID, p.opts.LogPath)
if err != nil {
err = fmt.Errorf("service log handler: %v", err)
return
}
var writer io.Writer
if p.data.Debug {
writer = io.MultiWriter(w, os.Stdout)
} else {
writer = w
}
cmd.Stdout = writer
cmd.Stderr = writer
return cmd, nil
}
func (p *processRunner) run(eventSink events.Recorder) error {
cmd, err := p.build()
if err != nil {
return errors.Wrap(err, "error building command")
}
if err = cmd.Start(); err != nil {
return errors.Wrap(err, "error starting process")
}
eventSink(events.StateRunning, "Process %s started with PID %d", p, cmd.Process.Pid)
waitCh := make(chan error)
go func() {
waitCh <- cmd.Wait()
}()
select {
case err = <-waitCh:
// process exited
return err
case <-p.stop:
// graceful stop the service
eventSink(events.StateStopping, "Sending SIGTERM to %s", p)
// nolint: errcheck
_ = cmd.Process.Signal(syscall.SIGTERM)
}
select {
case <-waitCh:
// stopped process exited
return nil
case <-time.After(p.opts.GracefulShutdownTimeout):
// kill the process
eventSink(events.StateStopping, "Sending SIGKILL to %s", p)
// nolint: errcheck
_ = cmd.Process.Signal(syscall.SIGKILL)
}
// wait for process to terminate
<-waitCh
return nil
}
func (p *processRunner) String() string {
return fmt.Sprintf("Process(%q)", p.args.ProcessArgs)
}