Merge pull request #1923 from owncloud/cleanup-dead-code

This commit is contained in:
Alex Unger
2021-04-16 14:47:07 +02:00
committed by GitHub
14 changed files with 6 additions and 631 deletions
@@ -0,0 +1,5 @@
Enhancement: Remove dead runtime code
When moving from the old runtime to the new one there were lots of files left behind that are essentially dead code and should be removed. The original code lives here github.com/refs/pman/ if someone finds it interesting to read.
https://github.com/owncloud/ocis/pull/1923
+1 -6
View File
@@ -5,10 +5,8 @@ import (
"log"
"net"
"net/rpc"
"os"
"github.com/owncloud/ocis/ocis/pkg/runtime/config"
"github.com/owncloud/ocis/ocis/pkg/runtime/process"
"github.com/spf13/cobra"
)
@@ -23,11 +21,8 @@ func Run(cfg *config.Config) *cobra.Command {
if err != nil {
log.Fatal("dialing:", err)
}
proc := process.NewProcEntry(args[0], os.Environ(), []string{args[0]}...)
var res int
if err := client.Call("Service.Start", proc, &res); err != nil {
if err := client.Call("Service.Start", &args[0], &res); err != nil {
log.Fatal(err)
}
-168
View File
@@ -1,168 +0,0 @@
package controller
import (
"fmt"
"os"
"os/exec"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/owncloud/ocis/ocis/pkg/runtime/config"
"github.com/owncloud/ocis/ocis/pkg/runtime/process"
"github.com/owncloud/ocis/ocis/pkg/runtime/storage"
"github.com/owncloud/ocis/ocis/pkg/runtime/watcher"
"github.com/rs/zerolog"
"github.com/olekukonko/tablewriter"
)
// Controller supervises processes.
type Controller struct {
m *sync.RWMutex
options Options
log zerolog.Logger
Config *config.Config
Store storage.Storage
// Bin is the oCIS single binary name.
Bin string
// BinPath is the oCIS single binary path withing the host machine.
// The Controller needs to know the binary location in order to spawn new extensions.
BinPath string
// Terminated facilitates communication from Watcher <-> Controller. Writes to this
// channel WILL always attempt to restart the crashed process.
Terminated chan process.ProcEntry
}
var (
once = sync.Once{}
)
// NewController initializes a new controller.
func NewController(o ...Option) Controller {
opts := &Options{}
for _, f := range o {
f(opts)
}
c := Controller{
m: &sync.RWMutex{},
options: *opts,
log: *opts.Log,
Bin: "ocis",
Terminated: make(chan process.ProcEntry),
Store: storage.NewMapStorage(),
Config: opts.Config,
}
if opts.Bin != "" {
c.Bin = opts.Bin
}
// Get binary location from $PATH lookup. If not present, it uses arg[0] as entry point.
path, err := exec.LookPath(c.Bin)
if err != nil {
c.log.Debug().Msg("oCIS binary not present in PATH, using Args[0]")
path = os.Args[0]
}
c.BinPath = path
return c
}
// Start and watches a process.
func (c *Controller) Start(pe process.ProcEntry) error {
if pid := c.Store.Load(pe.Extension); pid != 0 {
c.log.Debug().Msg(fmt.Sprintf("extension already running: %s", pe.Extension))
return nil
}
w := watcher.NewWatcher()
if err := pe.Start(c.BinPath); err != nil {
return err
}
// store the spawned child process PID.
if err := c.Store.Store(pe); err != nil {
return err
}
w.Follow(pe, c.Terminated, c.options.Config.KeepAlive)
once.Do(func() {
j := janitor{
time.Second,
c.Store,
}
go j.run()
go detach(c)
})
return nil
}
// Kill a managed process.
// Should a process managed by the runtime be allowed to be killed if the runtime is configured not to?
func (c *Controller) Kill(pe process.ProcEntry) error {
// load stored PID
pid := c.Store.Load(pe.Extension)
// find process in host by PID
p, err := os.FindProcess(pid)
if err != nil {
return err
}
if err := c.Store.Delete(pe); err != nil {
return err
}
c.log.Info().Str("package", "watcher").Msgf("terminating %v", pe.Extension)
// terminate child process
return p.Kill()
}
// Shutdown a running runtime.
func (c *Controller) Shutdown(ch chan struct{}) error {
entries := c.Store.LoadAll()
for cmd, pid := range entries {
c.log.Info().Str("package", "watcher").Msgf("gracefully terminating %v", cmd)
p, _ := os.FindProcess(pid)
if err := p.Kill(); err != nil {
return err
}
}
ch <- struct{}{}
return nil
}
// List managed processes.
func (c *Controller) List() string {
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table.SetHeader([]string{"Extension", "PID"})
entries := c.Store.LoadAll()
keys := make([]string, 0, len(entries))
for k := range entries {
keys = append(keys, k)
}
sort.Strings(keys)
for _, v := range keys {
table.Append([]string{v, strconv.Itoa(entries[v])})
}
table.Render()
return tableString.String()
}
-49
View File
@@ -1,49 +0,0 @@
package controller
import (
"os"
"os/signal"
"syscall"
"time"
"github.com/owncloud/ocis/ocis/pkg/runtime/process"
"github.com/owncloud/ocis/ocis/pkg/runtime/storage"
)
type janitor struct {
// interval at which db is cleared.
interval time.Duration
store storage.Storage
}
func (j *janitor) run() {
ticker := time.NewTicker(j.interval)
work := make(chan os.Signal, 1)
signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT)
for {
select {
case <-work:
return
case <-ticker.C:
j.cleanup()
}
}
}
// cleanup removes orphaned extension + pid that were killed via SIGKILL given the nature of is being un-catchable,
// the only way to update pman's database is by polling.
func (j *janitor) cleanup() {
for name, pid := range j.store.LoadAll() {
// On unix like systems (linux, freebsd, etc) os.FindProcess will never return an error
if p, err := os.FindProcess(pid); err == nil {
if err := p.Signal(syscall.Signal(0)); err != nil {
_ = j.store.Delete(process.ProcEntry{
Pid: pid,
Extension: name,
})
}
}
}
}
-36
View File
@@ -1,36 +0,0 @@
package controller
import (
"github.com/owncloud/ocis/ocis/pkg/runtime/config"
"github.com/rs/zerolog"
)
// Options are the configurable options for a Controller.
type Options struct {
Bin string
Restart bool
Config *config.Config
Log *zerolog.Logger
}
// Option represents an option.
type Option func(o *Options)
// NewOptions returns a new Options struct.
func NewOptions() Options {
return Options{}
}
// WithConfig sets Controller config.
func WithConfig(cfg *config.Config) Option {
return func(o *Options) {
o.Config = cfg
}
}
// WithLog sets Controller config.
func WithLog(l *zerolog.Logger) Option {
return func(o *Options) {
o.Log = l
}
}
-10
View File
@@ -1,10 +0,0 @@
package controller
// detach will try to restart processes on failures.
func detach(c *Controller) {
for proc := range c.Terminated {
if err := c.Start(proc); err != nil {
c.log.Err(err)
}
}
}
-30
View File
@@ -1,30 +0,0 @@
package log
import (
"os"
"time"
"github.com/rs/zerolog"
)
var (
// Level sets a project wide log level
Level zerolog.Level = zerolog.InfoLevel
)
// NewLogger configures a logger.
func NewLogger(options ...Option) zerolog.Logger {
zerolog.SetGlobalLevel(Level)
o := NewOptions()
for _, f := range options {
f(o)
}
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
if o.Pretty {
logger = logger.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339})
}
return logger
}
-26
View File
@@ -1,26 +0,0 @@
package log
import "github.com/rs/zerolog"
// Options are the configurable options for a Controller.
type Options struct {
Level zerolog.Level
Pretty bool
}
// Option represents an option.
type Option func(o *Options)
// NewOptions returns a new Options struct.
func NewOptions() *Options {
return &Options{
Level: zerolog.DebugLevel,
}
}
// WithPretty sets the pretty option.
func WithPretty(pretty bool) Option {
return func(o *Options) {
o.Pretty = pretty
}
}
-60
View File
@@ -1,60 +0,0 @@
// +build !windows
package process
import (
"os"
sys "golang.org/x/sys/unix"
)
// ProcEntry is an entry in the File db.
type ProcEntry struct {
Args []string
Env []string
Pid int
Extension string
}
// NewProcEntry returns a new ProcEntry.
func NewProcEntry(extension string, env []string, args ...string) ProcEntry {
return ProcEntry{
Extension: extension,
Args: args,
Env: env,
}
}
// Start a process.
func (e *ProcEntry) Start(binPath string) error {
var argv = []string{binPath}
argv = append(argv, e.Args...)
p, err := os.StartProcess(binPath, argv, &os.ProcAttr{
Files: []*os.File{
os.Stdin,
os.Stdout,
os.Stderr,
},
Env: e.Env,
Sys: &sys.SysProcAttr{
Setpgid: true,
},
})
if err != nil {
return err
}
e.Pid = p.Pid
return nil
}
// Kill the wrapped process.
func (e *ProcEntry) Kill() error {
p, err := os.FindProcess(e.Pid)
if err != nil {
return err
}
return p.Kill()
}
@@ -1,56 +0,0 @@
// +build windows
package process
import (
"os"
)
// ProcEntry is an entry in the File db.
type ProcEntry struct {
Args []string
Env []string
Pid int
Extension string
}
// NewProcEntry returns a new ProcEntry.
func NewProcEntry(extension string, env []string, args ...string) ProcEntry {
return ProcEntry{
Extension: extension,
Args: args,
Env: env,
}
}
// Start a process.
func (e *ProcEntry) Start(binPath string) error {
var argv = []string{binPath}
argv = append(argv, e.Args...)
p, err := os.StartProcess(binPath, argv, &os.ProcAttr{
Files: []*os.File{
os.Stdin,
os.Stdout,
os.Stderr,
},
Env: e.Env,
})
if err != nil {
return err
}
e.Pid = p.Pid
return nil
}
// Kill the wrapped process.
func (e *ProcEntry) Kill() error {
p, err := os.FindProcess(e.Pid)
if err != nil {
return err
}
return p.Kill()
}
-64
View File
@@ -1,64 +0,0 @@
package storage
import (
"github.com/owncloud/ocis/ocis/pkg/runtime/process"
"sync"
)
// Map synchronizes access to extension+pid tuples.
type Map struct {
c *sync.Map
}
// NewMapStorage initializes a new Storage.
func NewMapStorage() Storage {
return &Map{
c: &sync.Map{},
}
}
// Store a value on the underlying data structure.
func (m *Map) Store(e process.ProcEntry) error {
m.c.Store(e.Extension, e.Pid)
return nil
}
// Delete a value on the underlying data structure.
func (m *Map) Delete(e process.ProcEntry) error {
m.c.Delete(e.Extension)
return nil
}
// Load a single pid.
func (m *Map) Load(name string) int {
var val int
m.c.Range(func(k, v interface{}) bool {
if k.(string) == name {
val = v.(int)
return false
}
return true
})
return val
}
// LoadAll values from the underlying data structure.
func (m *Map) LoadAll() Entries {
e := make(map[string]int)
m.c.Range(func(k, v interface{}) bool {
ks, ok := k.(string)
if !ok {
return false
}
vs, ok := v.(int)
if !ok {
return false
}
e[ks] = vs
return true
})
return e
}
-50
View File
@@ -1,50 +0,0 @@
package storage
import (
"fmt"
"math/rand"
"os"
"strconv"
"testing"
"github.com/owncloud/ocis/ocis/pkg/runtime/process"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
if err := loadStore(); err != nil {
os.Exit(1)
}
os.Exit(m.Run())
}
var (
store = NewMapStorage()
)
func loadStore() error {
for i := 0; i < 20; i++ {
if err := store.Store(process.ProcEntry{
Pid: rand.Int(), //nolint:gosec
Extension: fmt.Sprintf("extension-%s", strconv.Itoa(i)),
}); err != nil {
return err
}
}
return nil
}
func TestLoadAll(t *testing.T) {
all := store.LoadAll()
assert.NotNil(t, all["extension-1"])
}
func TestDelete(t *testing.T) {
err := store.Delete(process.ProcEntry{
Extension: "extension-1",
})
assert.Nil(t, err)
all := store.LoadAll()
assert.Zero(t, all["extension-1"])
}
-21
View File
@@ -1,21 +0,0 @@
package storage
import "github.com/owncloud/ocis/ocis/pkg/runtime/process"
// Entries is a tuple of <extension:pid>
type Entries map[string]int
// Storage defines a basic persistence interface layer.
type Storage interface {
// Store a representation of a process.
Store(e process.ProcEntry) error
// Delete a representation of a process.
Delete(e process.ProcEntry) error
// Load a single entry.
Load(name string) int
// LoadAll retrieves a set of entries of running processes on the host machine.
LoadAll() Entries
}
-55
View File
@@ -1,55 +0,0 @@
package watcher
import (
golog "log"
"os"
"github.com/owncloud/ocis/ocis/pkg/runtime/log"
"github.com/owncloud/ocis/ocis/pkg/runtime/process"
"github.com/rs/zerolog"
)
// Watcher watches a process and sends messages using channels.
type Watcher struct {
log zerolog.Logger
}
// NewWatcher initializes a watcher.
func NewWatcher() Watcher {
return Watcher{
log: log.NewLogger(log.WithPretty(true)),
}
}
// Follow a process until it dies. If restart is enabled, a new fork of the original process will be automatically spawned.
func (w *Watcher) Follow(pe process.ProcEntry, followerChan chan process.ProcEntry, restart bool) {
state := make(chan *os.ProcessState, 1)
w.log.Debug().Str("package", "watcher").Msgf("watching %v", pe.Extension)
go func() {
ps, err := watch(pe.Pid)
if err != nil {
golog.Fatal(err)
}
state <- ps
}()
go func() {
status := <-state
w.log.Info().Str("package", "watcher").Msgf("%v exited with: %v", pe.Extension, status)
if restart {
followerChan <- pe
}
}()
}
// watch a process by its pid. This operation blocks.
func watch(pid int) (*os.ProcessState, error) {
p, err := os.FindProcess(pid)
if err != nil {
return nil, err
}
return p.Wait()
}