From 03824d5c4c255c88fb6fb59a079cdf10d254c112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 1 May 2020 23:15:15 +1200 Subject: [PATCH 01/17] Appserver simplifications and improvements. * Combined the appserver.Server and appserver.ProcManager concepts. * Have one rpc.Server per proc. * Various refactoring and renamings. --- cmd/skywire-cli/commands/visor/gen-config.go | 2 +- pkg/app/appcommon/config.go | 16 -- pkg/app/appcommon/doc.go | 3 +- pkg/app/appcommon/env.go | 10 - pkg/app/appcommon/key.go | 14 -- pkg/app/appcommon/proc_config.go | 101 ++++++++++ pkg/app/appcommon/procid.go | 6 - pkg/app/appdisc/factory.go | 8 +- pkg/app/appserver/mock_proc_manager.go | 6 +- pkg/app/appserver/proc.go | 96 +++++---- pkg/app/appserver/proc_manager.go | 194 +++++++++++++++---- pkg/app/appserver/proc_manager_test.go | 6 +- pkg/app/appserver/server.go | 92 --------- pkg/app/appserver/server_test.go | 8 +- pkg/app/client.go | 38 ++-- pkg/app/client_test.go | 24 +-- pkg/app/conn_test.go | 4 +- pkg/app/rpc_client.go | 6 +- pkg/visor/config.go | 4 +- pkg/visor/gateway.go | 14 +- pkg/visor/rpc_test.go | 14 +- pkg/visor/visor.go | 59 +++--- pkg/visor/visor_test.go | 40 ++-- 23 files changed, 432 insertions(+), 333 deletions(-) delete mode 100644 pkg/app/appcommon/config.go delete mode 100644 pkg/app/appcommon/env.go delete mode 100644 pkg/app/appcommon/key.go create mode 100644 pkg/app/appcommon/proc_config.go delete mode 100644 pkg/app/appcommon/procid.go delete mode 100644 pkg/app/appserver/server.go diff --git a/cmd/skywire-cli/commands/visor/gen-config.go b/cmd/skywire-cli/commands/visor/gen-config.go index eeef0a864..f7b7a1901 100644 --- a/cmd/skywire-cli/commands/visor/gen-config.go +++ b/cmd/skywire-cli/commands/visor/gen-config.go @@ -155,7 +155,7 @@ func defaultConfig() *visor.Config { RPCAddress: "localhost:3435", } - conf.AppServerAddr = appcommon.DefaultServerAddr + conf.AppServerAddr = appcommon.DefaultAppSrvAddr conf.RestartCheckDelay = restart.DefaultCheckDelay.String() if testenv { diff --git a/pkg/app/appcommon/config.go b/pkg/app/appcommon/config.go deleted file mode 100644 index 93c518b57..000000000 --- a/pkg/app/appcommon/config.go +++ /dev/null @@ -1,16 +0,0 @@ -package appcommon - -import "github.com/SkycoinProject/skywire-mainnet/pkg/routing" - -// DefaultServerAddr is a default address to run the app server at. -const DefaultServerAddr = "localhost:5505" - -// Config defines configuration parameters for `Proc`. -type Config struct { - Name string `json:"name"` - ServerAddr string `json:"server_addr"` // TODO(evanlinjin): Maybe rename to AppSrvAddr or related. - VisorPK string `json:"visor_pk"` - RoutingPort routing.Port `json:"routing_port"` - BinaryDir string `json:"binary_dir"` - WorkDir string `json:"work_dir"` -} diff --git a/pkg/app/appcommon/doc.go b/pkg/app/appcommon/doc.go index 52dc9d630..28b3b77d6 100644 --- a/pkg/app/appcommon/doc.go +++ b/pkg/app/appcommon/doc.go @@ -1,3 +1,2 @@ -// Package appcommon contains common facilities -// implementing skywire apps. +// Package appcommon contains common facilities implementing skywire apps. package appcommon diff --git a/pkg/app/appcommon/env.go b/pkg/app/appcommon/env.go deleted file mode 100644 index 2eada4cd8..000000000 --- a/pkg/app/appcommon/env.go +++ /dev/null @@ -1,10 +0,0 @@ -package appcommon - -const ( - // EnvAppKey is a name for env arg containing skywire application key. - EnvAppKey = "APP_KEY" - // EnvServerAddr is a name for env arg containing app server address. - EnvServerAddr = "APP_SERVER_ADDR" - // EnvVisorPK is a name for env arg containing public key of visor. - EnvVisorPK = "VISOR_PK" -) diff --git a/pkg/app/appcommon/key.go b/pkg/app/appcommon/key.go deleted file mode 100644 index 897d13bce..000000000 --- a/pkg/app/appcommon/key.go +++ /dev/null @@ -1,14 +0,0 @@ -package appcommon - -import ( - "github.com/google/uuid" -) - -// Key is an app key to authenticate within the -// app server. -type Key string - -// GenerateAppKey generates new app key. -func GenerateAppKey() Key { - return Key(uuid.New().String()) -} diff --git a/pkg/app/appcommon/proc_config.go b/pkg/app/appcommon/proc_config.go new file mode 100644 index 000000000..2422ae939 --- /dev/null +++ b/pkg/app/appcommon/proc_config.go @@ -0,0 +1,101 @@ +package appcommon + +import ( + "encoding/hex" + "errors" + "fmt" + "path/filepath" + + "github.com/google/uuid" + + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" +) + +// DefaultAppSrvAddr is the default address to run the app server at. +const DefaultAppSrvAddr = "localhost:5505" + +const ( + // EnvProcKey is a name for env arg containing skywire application key. + EnvProcKey = "PROC_KEY" + // EnvAppSrvAddr is a name for env arg containing app server address. + EnvAppSrvAddr = "APP_SERVER_ADDR" + // EnvVisorPK is a name for env arg containing public key of visor. + EnvVisorPK = "VISOR_PK" +) + +// ProcKey is a unique key to authenticate a proc within the app server. +type ProcKey [16]byte + +// RandProcKey generates new proc key. +func RandProcKey() ProcKey { + return ProcKey(uuid.New()) +} + +// String implements io.Stringer +func (k ProcKey) String() string { + return hex.EncodeToString(k[:]) +} + +// Null returns true if ProcKey is null. +func (k ProcKey) Null() bool { + return k == (ProcKey{}) +} + +// MarshalText implements encoding.TextMarshaller +func (k ProcKey) MarshalText() ([]byte, error) { + return []byte(k.String()), nil +} + +// UnmarshalText implements encoding.TextUnmarshaller +func (k *ProcKey) UnmarshalText(data []byte) error { + if len(data) == 0 { + *k = ProcKey{} + return nil + } + n, err := hex.Decode(k[:], data) + if err != nil { + return err + } + if n != len(k) { + return errors.New("invalid proc key length") + } + return nil +} + +// ProcID identifies the current instance of an app (an app process). +// The visor is responsible for starting apps, and the started process should be provided with a ProcID. +type ProcID uint16 + +// ProcConfig defines configuration parameters for `Proc`. +type ProcConfig struct { + AppName string `json:"app_name"` + AppSrvAddr string `json:"app_server_addr"` + ProcKey ProcKey `json:"proc_key"` + ProcArgs []string `json:"proc_args"` + VisorPK string `json:"visor_pk"` + RoutingPort routing.Port `json:"routing_port"` + BinaryDir string `json:"binary_dir"` + WorkDir string `json:"work_dir"` +} + +// EnsureKey ensures that a proc key is provided in the ProcConfig. +func (c *ProcConfig) EnsureKey() { + if c.ProcKey.Null() { + c.ProcKey = RandProcKey() + } +} + +// BinaryLoc returns the binary path using the associated fields of the ProcConfig. +func (c *ProcConfig) BinaryLoc() string { + return filepath.Join(c.BinaryDir, c.AppName) +} + +// Envs returns the env variables that are passed to the associated proc. +func (c *ProcConfig) Envs() []string { + const format = "%s=%s" + return []string{ + fmt.Sprintf(format, EnvProcKey, c.ProcKey), + fmt.Sprintf(format, EnvAppSrvAddr, c.AppSrvAddr), + fmt.Sprintf(format, EnvVisorPK, c.VisorPK), + } +} diff --git a/pkg/app/appcommon/procid.go b/pkg/app/appcommon/procid.go deleted file mode 100644 index 9a5858bae..000000000 --- a/pkg/app/appcommon/procid.go +++ /dev/null @@ -1,6 +0,0 @@ -package appcommon - -// ProcID identifies the current instance of an app (an app process). -// The visor is responsible for starting apps, and the started process -// should be provided with a ProcID. -type ProcID uint16 diff --git a/pkg/app/appdisc/factory.go b/pkg/app/appdisc/factory.go index 0e656ce69..a21beab82 100644 --- a/pkg/app/appdisc/factory.go +++ b/pkg/app/appdisc/factory.go @@ -35,20 +35,20 @@ func (f *Factory) setDefaults() { } // Updater obtains an updater based on the app name and configuration. -func (f *Factory) Updater(conf appcommon.Config, args []string) (Updater, bool) { +func (f *Factory) Updater(conf appcommon.ProcConfig) (Updater, bool) { // Always return empty updater if keys are not set. if f.setDefaults(); f.PK.Null() || f.SK.Null() { return &emptyUpdater{}, false } - log := f.Log.WithField("appName", conf.Name) + log := f.Log.WithField("appName", conf.AppName) - switch conf.Name { + switch conf.AppName { case "skysocks": // Do not update in proxy discovery if passcode-protected. - if containsFlag(args, "passcode") { + if containsFlag(conf.ProcArgs, "passcode") { return &emptyUpdater{}, false } diff --git a/pkg/app/appserver/mock_proc_manager.go b/pkg/app/appserver/mock_proc_manager.go index 483cde0e9..8df0be593 100644 --- a/pkg/app/appserver/mock_proc_manager.go +++ b/pkg/app/appserver/mock_proc_manager.go @@ -37,18 +37,18 @@ func (_m *MockProcManager) Range(next func(string, *Proc) bool) { } // Start provides a mock function with given fields: log, c, args, stdout, stderr -func (_m *MockProcManager) Start(log *logging.Logger, c appcommon.Config, args []string, stdout io.Writer, stderr io.Writer) (appcommon.ProcID, error) { +func (_m *MockProcManager) Start(log *logging.Logger, c appcommon.ProcConfig, args []string, stdout io.Writer, stderr io.Writer) (appcommon.ProcID, error) { ret := _m.Called(log, c, args, stdout, stderr) var r0 appcommon.ProcID - if rf, ok := ret.Get(0).(func(*logging.Logger, appcommon.Config, []string, io.Writer, io.Writer) appcommon.ProcID); ok { + if rf, ok := ret.Get(0).(func(*logging.Logger, appcommon.ProcConfig, []string, io.Writer, io.Writer) appcommon.ProcID); ok { r0 = rf(log, c, args, stdout, stderr) } else { r0 = ret.Get(0).(appcommon.ProcID) } var r1 error - if rf, ok := ret.Get(1).(func(*logging.Logger, appcommon.Config, []string, io.Writer, io.Writer) error); ok { + if rf, ok := ret.Get(1).(func(*logging.Logger, appcommon.ProcConfig, []string, io.Writer, io.Writer) error); ok { r1 = rf(log, c, args, stdout, stderr) } else { r1 = ret.Error(1) diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 4e68b7e7f..037deb4af 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -1,12 +1,13 @@ package appserver import ( + "context" "errors" - "fmt" "io" + "net" + "net/rpc" "os" "os/exec" - "path/filepath" "sync" "sync/atomic" @@ -25,69 +26,83 @@ var ( // the running process itself and the RPC server for // app/visor communication. type Proc struct { - disc appdisc.Updater // App discovery client. - key appcommon.Key - config appcommon.Config - log *logging.Logger + disc appdisc.Updater // App discovery client. + conf appcommon.ProcConfig + log *logging.Logger + cmd *exec.Cmd isRunning int32 waitMx sync.Mutex waitErr error + + connCh chan net.Conn } // NewProc constructs `Proc`. -func NewProc(log *logging.Logger, disc appdisc.Updater, c appcommon.Config, args []string, stdout, stderr io.Writer) (*Proc, error) { - key := appcommon.GenerateAppKey() - - binaryPath := getBinaryPath(c.BinaryDir, c.Name) - - const ( - appKeyEnvFormat = appcommon.EnvAppKey + "=%s" - serverAddrEnvFormat = appcommon.EnvServerAddr + "=%s" - visorPKEnvFormat = appcommon.EnvVisorPK + "=%s" - ) - - env := make([]string, 0, 4) - env = append(env, fmt.Sprintf(appKeyEnvFormat, key)) - env = append(env, fmt.Sprintf(serverAddrEnvFormat, c.ServerAddr)) - env = append(env, fmt.Sprintf(visorPKEnvFormat, c.VisorPK)) - - cmd := exec.Command(binaryPath, args...) // nolint:gosec - - cmd.Env = env - cmd.Dir = c.WorkDir - +func NewProc(log *logging.Logger, conf appcommon.ProcConfig, disc appdisc.Updater, stdout, stderr io.Writer) *Proc { + cmd := exec.Command(conf.BinaryLoc(), conf.ProcArgs...) // nolint:gosec + cmd.Env = conf.Envs() + cmd.Dir = conf.WorkDir cmd.Stdout = stdout cmd.Stderr = stderr return &Proc{ disc: disc, - key: key, - config: c, + conf: conf, log: log, cmd: cmd, - }, nil + connCh: make(chan net.Conn, 1), + } +} + +// InjectConn introduces the connection to the Proc after it is started. +func (p *Proc) InjectConn(conn net.Conn) bool { + select { + case p.connCh <- conn: + return true + default: + return false + } +} + +func (p *Proc) awaitConn(ctx context.Context) error { + select { + case conn := <-p.connCh: + rpcS := rpc.NewServer() + if err := rpcS.RegisterName(p.conf.ProcKey.String(), NewRPCGateway(p.log)); err != nil { + panic(err) + } + go rpcS.ServeConn(conn) + return nil + case <-ctx.Done(): + return ctx.Err() + } } // Start starts the application. -func (p *Proc) Start() error { +func (p *Proc) Start(ctx context.Context) error { if !atomic.CompareAndSwapInt32(&p.isRunning, 0, 1) { return errProcAlreadyRunning } + // acquire lock immediately + p.waitMx.Lock() + if err := p.cmd.Start(); err != nil { + p.waitMx.Unlock() + return err + } + if err := p.awaitConn(ctx); err != nil { + _ = p.cmd.Process.Kill() //nolint:errcheck + p.waitMx.Unlock() return err } - p.disc.Start() - // acquire lock immediately - p.waitMx.Lock() go func() { - defer func() { - p.disc.Stop() - p.waitMx.Unlock() - }() + p.disc.Start() p.waitErr = p.cmd.Wait() + p.disc.Stop() + p.waitMx.Unlock() }() return nil @@ -128,8 +143,3 @@ func (p *Proc) Wait() error { func (p *Proc) IsRunning() bool { return atomic.LoadInt32(&p.isRunning) == 1 } - -// getBinaryPath formats binary path using app dir, name and version. -func getBinaryPath(dir, name string) string { - return filepath.Join(dir, name) -} diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index f2062c937..226ab6e91 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -1,12 +1,15 @@ package appserver import ( + "context" "errors" "fmt" "io" + "net" "os/exec" "strings" "sync" + "time" "github.com/SkycoinProject/skycoin/src/util/logging" @@ -16,73 +19,166 @@ import ( //go:generate mockery -name ProcManager -case underscore -inpkg +const ( + // ProcStartTimeout represents the duration in which a proc should have started and connected with the app server. + ProcStartTimeout = time.Second * 5 +) + var ( // ErrAppAlreadyStarted is returned when trying to run the already running app. ErrAppAlreadyStarted = errors.New("app already started") errNoSuchApp = errors.New("no such app") + + ErrClosed = errors.New("proc manager is already closed") ) // ProcManager allows to manage skywire applications. type ProcManager interface { - Start(log *logging.Logger, c appcommon.Config, args []string, stdout, stderr io.Writer) (appcommon.ProcID, error) + io.Closer + Start(ctx context.Context, log *logging.Logger, conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) Exists(name string) bool Stop(name string) error Wait(name string) error Range(next func(name string, proc *Proc) bool) - StopAll() } -// procManager allows to manage skywire applications. -// Implements `ProcManager`. +// procManager manages skywire applications. It implements `ProcManager`. type procManager struct { - log *logging.Logger - discF *appdisc.Factory - procs map[string]*Proc - mx sync.RWMutex - rpcServer *Server + log *logging.Logger + + addr string // listening address + lis net.Listener + conns map[string]net.Conn + connsWG sync.WaitGroup + + discF *appdisc.Factory + procs map[string]*Proc + procsByKey map[appcommon.ProcKey]*Proc + + mx sync.RWMutex + done chan struct{} + doneOnce sync.Once } // NewProcManager constructs `ProcManager`. -func NewProcManager(log *logging.Logger, discF *appdisc.Factory, rpcServer *Server) ProcManager { - return &procManager{ - log: log, - discF: discF, - procs: make(map[string]*Proc), - rpcServer: rpcServer, +func NewProcManager(log *logging.Logger, discF *appdisc.Factory, addr string) (ProcManager, error) { + lis, err := net.Listen("tcp", addr) + if err != nil { + return nil, err } + + procM := &procManager{ + addr: addr, + log: log, + lis: lis, + conns: make(map[string]net.Conn), + discF: discF, + procs: make(map[string]*Proc), + procsByKey: make(map[appcommon.ProcKey]*Proc), + done: make(chan struct{}), + } + + procM.connsWG.Add(1) + go func() { + defer procM.connsWG.Done() + procM.serve() + }() + + return procM, nil } -// Start start the application according to its config and additional args. -func (m *procManager) Start(log *logging.Logger, c appcommon.Config, args []string, - stdout, stderr io.Writer) (appcommon.ProcID, error) { - if m.Exists(c.Name) { - return 0, ErrAppAlreadyStarted +func (m *procManager) serve() { + defer func() { + for _, conn := range m.conns { + _ = conn.Close() //nolint:errcheck + } + }() + + for { + conn, err := m.lis.Accept() + if err != nil { + if !isDone(m.done) { + m.log.WithError(err).WithField("remote_addr", conn.RemoteAddr()). + Info("Unexpected error occurred when accepting app conn.") + } + return + } + m.conns[conn.RemoteAddr().String()] = conn + + m.connsWG.Add(1) + go func(conn net.Conn) { + defer m.connsWG.Done() + + if ok := m.handleConn(conn); !ok { + if err := conn.Close(); err != nil { + m.log.WithError(err).WithField("remote_addr", conn.RemoteAddr()). + Warn("Failed to close problematic app conn.") + } + } + }(conn) + } +} + +func (m *procManager) handleConn(conn net.Conn) bool { + // Read in and check key. + var key appcommon.ProcKey + if n, err := io.ReadFull(conn, key[:]); err != nil { + m.log. + WithError(err). + WithField("n", n). + WithField("remote_addr", conn.RemoteAddr()). + Warn("Failed to read proc key.") + return false } - disc, ok := m.discF.Updater(c, args) + // Push conn to Proc. + m.mx.RLock() + proc, ok := m.procsByKey[key] + m.mx.RUnlock() if !ok { - log.WithField("appName", c.Name). - Debug("No app discovery associated with app.") + return false } + if ok := proc.InjectConn(conn); !ok { + return false + } + return true +} - p, err := NewProc(log, disc, c, args, stdout, stderr) - if err != nil { - return 0, err +// Start starts the application according to its config and additional args. +func (m *procManager) Start(ctx context.Context, log *logging.Logger, conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) { + m.mx.Lock() + defer m.mx.Unlock() + + // isDone should be called within the protection of a mutex. + // Otherwise we may be able to start an app after calling Close. + if isDone(m.done) { + return 0, ErrClosed } - if err := m.rpcServer.Register(p.key); err != nil { - return 0, err + if _, ok := m.procs[conf.AppName]; ok { + return 0, ErrAppAlreadyStarted } - m.mx.Lock() - m.procs[c.Name] = p - m.mx.Unlock() + conf.EnsureKey() + + disc, ok := m.discF.Updater(conf) + if !ok { + log.WithField("appName", conf.AppName). + Debug("No app discovery associated with app.") + } + + proc := NewProc(log, conf, disc, stdout, stderr) + m.procs[conf.AppName] = proc + m.procsByKey[conf.ProcKey] = proc - if err := p.Start(); err != nil { + ctx, cancel := context.WithTimeout(ctx, ProcStartTimeout) + defer cancel() + + if err := proc.Start(ctx); err != nil { return 0, err } - return appcommon.ProcID(p.cmd.Process.Pid), nil + return appcommon.ProcID(proc.cmd.Process.Pid), nil } // Exists check whether app exists in the manager instance. @@ -144,11 +240,8 @@ func (m *procManager) Range(next func(name string, proc *Proc) bool) { } } -// StopAll stops all the apps run with this manager instance. -func (m *procManager) StopAll() { - m.mx.Lock() - defer m.mx.Unlock() - +// stopAll stops all the apps run with this manager instance. +func (m *procManager) stopAll() { for name, proc := range m.procs { log := m.log.WithField("app_name", name) if err := proc.Stop(); err != nil && strings.Contains(err.Error(), "process already finished") { @@ -161,6 +254,22 @@ func (m *procManager) StopAll() { m.procs = make(map[string]*Proc) } +// Close implements io.Closer +func (m *procManager) Close() error { + m.mx.Lock() + defer m.mx.Unlock() + + if isDone(m.done) { + return ErrClosed + } + close(m.done) + + m.stopAll() + err := m.lis.Close() + m.connsWG.Wait() + return err +} + // pop removes application from the manager instance and returns it. func (m *procManager) pop(name string) (*Proc, error) { m.mx.Lock() @@ -188,3 +297,12 @@ func (m *procManager) get(name string) (*Proc, error) { return p, nil } + +func isDone(done chan struct{}) bool { + select { + case <-done: + return true + default: + return false + } +} diff --git a/pkg/app/appserver/proc_manager_test.go b/pkg/app/appserver/proc_manager_test.go index de0763e0c..21a273093 100644 --- a/pkg/app/appserver/proc_manager_test.go +++ b/pkg/app/appserver/proc_manager_test.go @@ -14,7 +14,7 @@ import ( var appDisc = new(appdisc.Factory) func TestProcManager_Exists(t *testing.T) { - srv := New(nil, appcommon.DefaultServerAddr) + srv := New(nil, appcommon.DefaultAppSrvAddr) mIfc := NewProcManager(logging.MustGetLogger("proc_manager"), appDisc, srv) m, ok := mIfc.(*procManager) @@ -32,7 +32,7 @@ func TestProcManager_Exists(t *testing.T) { } func TestProcManager_Range(t *testing.T) { - srv := New(nil, appcommon.DefaultServerAddr) + srv := New(nil, appcommon.DefaultAppSrvAddr) mIfc := NewProcManager(logging.MustGetLogger("proc_manager"), appDisc, srv) m, ok := mIfc.(*procManager) @@ -62,7 +62,7 @@ func TestProcManager_Range(t *testing.T) { } func TestProcManager_Pop(t *testing.T) { - srv := New(nil, appcommon.DefaultServerAddr) + srv := New(nil, appcommon.DefaultAppSrvAddr) mIfc := NewProcManager(logging.MustGetLogger("proc_manager"), appDisc, srv) m, ok := mIfc.(*procManager) diff --git a/pkg/app/appserver/server.go b/pkg/app/appserver/server.go deleted file mode 100644 index df9d829c3..000000000 --- a/pkg/app/appserver/server.go +++ /dev/null @@ -1,92 +0,0 @@ -package appserver - -import ( - "fmt" - "net" - "net/rpc" - "strings" - "sync" - - "github.com/SkycoinProject/skycoin/src/util/logging" - - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" -) - -// Server is a server for app/visor communication. -// TODO(evanlinjin): In my humble opinion, this should be renamed to 'RPCServer' to avoid confusion. -type Server struct { - log *logging.Logger - lis net.Listener - addr string - rpcS *rpc.Server - rpcGW *RPCGateway - done sync.WaitGroup - stopCh chan struct{} -} - -// New constructs server. -func New(log *logging.Logger, addr string) *Server { - return &Server{ - log: log, - addr: addr, - rpcS: rpc.NewServer(), - stopCh: make(chan struct{}), - } -} - -// Register registers an app key in RPC server. -func (s *Server) Register(appKey appcommon.Key) error { - logger := logging.MustGetLogger(fmt.Sprintf("app_gateway:%s", appKey)) - - s.rpcGW = NewRPCGateway(logger) - return s.rpcS.RegisterName(string(appKey), s.rpcGW) -} - -// ListenAndServe starts listening for incoming app connections via tcp socket. -func (s *Server) ListenAndServe() error { - l, err := net.Listen("tcp", s.addr) - if err != nil { - return err - } - - s.lis = l - - for { - conn, err := l.Accept() - if err != nil { - return err - } - - s.done.Add(1) // nolint: gomnd - - go s.serveConn(conn) - } -} - -// Close closes the server. -func (s *Server) Close() error { - var err error - - if s.lis != nil { - err = s.lis.Close() - } - - close(s.stopCh) - - s.done.Wait() - - return err -} - -// serveConn serves RPC on a single connection. -func (s *Server) serveConn(conn net.Conn) { - go s.rpcS.ServeConn(conn) - - <-s.stopCh - - if err := conn.Close(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - s.log.WithError(err).Error("Unexpected error while closing conn.") - } - - s.done.Done() -} diff --git a/pkg/app/appserver/server_test.go b/pkg/app/appserver/server_test.go index b82930a9f..ca1a3637e 100644 --- a/pkg/app/appserver/server_test.go +++ b/pkg/app/appserver/server_test.go @@ -26,17 +26,17 @@ const ( func TestServer_ListenAndServe(t *testing.T) { l := logging.MustGetLogger("app_server") - s := appserver.New(l, appcommon.DefaultServerAddr) + s := appserver.New(l, appcommon.DefaultAppSrvAddr) - appKey := appcommon.GenerateAppKey() + appKey := appcommon.RandProcKey() require.NoError(t, s.Register(appKey)) visorPK, _ := cipher.GenerateKeyPair() clientConfig := app.ClientConfig{ VisorPK: visorPK, - ServerAddr: appcommon.DefaultServerAddr, - AppKey: appKey, + ServerAddr: appcommon.DefaultAppSrvAddr, + ProcKey: appKey, } errCh := make(chan error, 1) diff --git a/pkg/app/client.go b/pkg/app/client.go index 24cea7f82..bc8d5b17d 100644 --- a/pkg/app/client.go +++ b/pkg/app/client.go @@ -24,25 +24,32 @@ var ( ErrVisorPKInvalid = errors.New("visor PK is invalid") // ErrServerAddrNotProvided is returned when app server address is not provided. ErrServerAddrNotProvided = errors.New("server address is not provided") - // ErrAppKeyNotProvided is returned when the app key is not provided. - ErrAppKeyNotProvided = errors.New("app key is not provided") + // ErrProcKeyNotProvided is returned when the proc key is not provided. + ErrProcKeyNotProvided = errors.New("proc key is not provided") + // ErrProcKeyInvalid occurs when proc key is invalid. + ErrProcKeyInvalid = errors.New("proc key is invalid") ) // ClientConfig is a configuration for `Client`. type ClientConfig struct { VisorPK cipher.PubKey ServerAddr string - AppKey appcommon.Key + ProcKey appcommon.ProcKey } // ClientConfigFromEnv creates client config from the ENV args. func ClientConfigFromEnv() (ClientConfig, error) { - appKey := os.Getenv(appcommon.EnvAppKey) - if appKey == "" { - return ClientConfig{}, ErrAppKeyNotProvided + procKeyStr := os.Getenv(appcommon.EnvProcKey) + if procKeyStr == "" { + return ClientConfig{}, ErrProcKeyNotProvided } - serverAddr := os.Getenv(appcommon.EnvServerAddr) + var procKey appcommon.ProcKey + if err := procKey.UnmarshalText([]byte(procKeyStr)); err != nil { + return ClientConfig{}, ErrProcKeyInvalid + } + + serverAddr := os.Getenv(appcommon.EnvAppSrvAddr) if serverAddr == "" { return ClientConfig{}, ErrServerAddrNotProvided } @@ -57,11 +64,12 @@ func ClientConfigFromEnv() (ClientConfig, error) { return ClientConfig{}, ErrVisorPKInvalid } - return ClientConfig{ + conf := ClientConfig{ VisorPK: visorPK, ServerAddr: serverAddr, - AppKey: appcommon.Key(appKey), - }, nil + ProcKey: procKey, + } + return conf, nil } // Client is used by skywire apps. @@ -77,15 +85,19 @@ type Client struct { // - log: logger instance. // - config: client configuration. func NewClient(log *logging.Logger, config ClientConfig) (*Client, error) { - rpcCl, err := rpc.Dial("tcp", config.ServerAddr) + conn, err := net.Dial("tcp", config.ServerAddr) if err != nil { - return nil, fmt.Errorf("error connecting to the app server: %v", err) + return nil, fmt.Errorf("failed to dial to app server: %w", err) + } + if _, err := conn.Write(config.ProcKey[:]); err != nil { + return nil, fmt.Errorf("failed to provide proc key to app server: %w", err) } + rpcC := rpc.NewClient(conn) return &Client{ log: log, visorPK: config.VisorPK, - rpc: NewRPCClient(rpcCl, config.AppKey), + rpc: NewRPCClient(rpcC, config.ProcKey), lm: idmanager.New(), cm: idmanager.New(), }, nil diff --git a/pkg/app/client_test.go b/pkg/app/client_test.go index 80cb9afa7..4bfc2356c 100644 --- a/pkg/app/client_test.go +++ b/pkg/app/client_test.go @@ -17,10 +17,10 @@ import ( func TestClientConfigFromEnv(t *testing.T) { resetEnv := func(t *testing.T) { - err := os.Setenv(appcommon.EnvAppKey, "") + err := os.Setenv(appcommon.EnvProcKey, "") require.NoError(t, err) - err = os.Setenv(appcommon.EnvServerAddr, "") + err = os.Setenv(appcommon.EnvAppSrvAddr, "") require.NoError(t, err) err = os.Setenv(appcommon.EnvVisorPK, "") @@ -34,14 +34,14 @@ func TestClientConfigFromEnv(t *testing.T) { wantCfg := ClientConfig{ VisorPK: visorPK, - ServerAddr: appcommon.DefaultServerAddr, - AppKey: "key", + ServerAddr: appcommon.DefaultAppSrvAddr, + ProcKey: "key", } - err := os.Setenv(appcommon.EnvAppKey, string(wantCfg.AppKey)) + err := os.Setenv(appcommon.EnvProcKey, string(wantCfg.ProcKey)) require.NoError(t, err) - err = os.Setenv(appcommon.EnvServerAddr, wantCfg.ServerAddr) + err = os.Setenv(appcommon.EnvAppSrvAddr, wantCfg.ServerAddr) require.NoError(t, err) err = os.Setenv(appcommon.EnvVisorPK, wantCfg.VisorPK.Hex()) @@ -56,13 +56,13 @@ func TestClientConfigFromEnv(t *testing.T) { resetEnv(t) _, err := ClientConfigFromEnv() - require.Equal(t, err, ErrAppKeyNotProvided) + require.Equal(t, err, ErrProcKeyNotProvided) }) t.Run("no app server address", func(t *testing.T) { resetEnv(t) - err := os.Setenv(appcommon.EnvAppKey, "val") + err := os.Setenv(appcommon.EnvProcKey, "val") require.NoError(t, err) _, err = ClientConfigFromEnv() @@ -72,10 +72,10 @@ func TestClientConfigFromEnv(t *testing.T) { t.Run("no visor PK", func(t *testing.T) { resetEnv(t) - err := os.Setenv(appcommon.EnvAppKey, "val") + err := os.Setenv(appcommon.EnvProcKey, "val") require.NoError(t, err) - err = os.Setenv(appcommon.EnvServerAddr, appcommon.DefaultServerAddr) + err = os.Setenv(appcommon.EnvAppSrvAddr, appcommon.DefaultAppSrvAddr) require.NoError(t, err) _, err = ClientConfigFromEnv() @@ -85,10 +85,10 @@ func TestClientConfigFromEnv(t *testing.T) { t.Run("invalid visor PK", func(t *testing.T) { resetEnv(t) - err := os.Setenv(appcommon.EnvAppKey, "val") + err := os.Setenv(appcommon.EnvProcKey, "val") require.NoError(t, err) - err = os.Setenv(appcommon.EnvServerAddr, appcommon.DefaultServerAddr) + err = os.Setenv(appcommon.EnvAppSrvAddr, appcommon.DefaultAppSrvAddr) require.NoError(t, err) err = os.Setenv(appcommon.EnvVisorPK, "val") diff --git a/pkg/app/conn_test.go b/pkg/app/conn_test.go index c4dceec04..5e95b6d42 100644 --- a/pkg/app/conn_test.go +++ b/pkg/app/conn_test.go @@ -242,7 +242,7 @@ func TestConn_TestConn(t *testing.T) { cl1 := Client{ log: logging.MustGetLogger("test_client_1"), visorPK: keys[0].PK, - rpc: NewRPCClient(rpcCl1, appcommon.Key(appKeys[0].PK.Hex())), + rpc: NewRPCClient(rpcCl1, appcommon.ProcKey(appKeys[0].PK.Hex())), lm: idmanager.New(), cm: idmanager.New(), } @@ -255,7 +255,7 @@ func TestConn_TestConn(t *testing.T) { cl2 := Client{ log: logging.MustGetLogger("test_client_2"), visorPK: keys[1].PK, - rpc: NewRPCClient(rpcCl2, appcommon.Key(appKeys[1].PK.Hex())), + rpc: NewRPCClient(rpcCl2, appcommon.ProcKey(appKeys[1].PK.Hex())), lm: idmanager.New(), cm: idmanager.New(), } diff --git a/pkg/app/rpc_client.go b/pkg/app/rpc_client.go index 65d4bfff9..b36f56b91 100644 --- a/pkg/app/rpc_client.go +++ b/pkg/app/rpc_client.go @@ -30,11 +30,11 @@ type RPCClient interface { // rpcClient implements `RPCClient`. type rpcClient struct { rpc *rpc.Client - appKey appcommon.Key + appKey appcommon.ProcKey } // NewRPCClient constructs new `rpcClient`. -func NewRPCClient(rpc *rpc.Client, appKey appcommon.Key) RPCClient { +func NewRPCClient(rpc *rpc.Client, appKey appcommon.ProcKey) RPCClient { return &rpcClient{ rpc: rpc, appKey: appKey, @@ -148,5 +148,5 @@ func (c *rpcClient) SetWriteDeadline(id uint16, t time.Time) error { // formatMethod formats complete RPC method signature. func (c *rpcClient) formatMethod(method string) string { const methodFmt = "%s.%s" - return fmt.Sprintf(methodFmt, c.appKey, method) + return fmt.Sprintf(methodFmt, c.appKey.String(), method) } diff --git a/pkg/visor/config.go b/pkg/visor/config.go index c7f504afb..26d5211cb 100644 --- a/pkg/visor/config.go +++ b/pkg/visor/config.go @@ -275,10 +275,10 @@ func (c *Config) LocalDir() (string, error) { } // AppServerAddress extracts and returns AppServerAddr from Visor Config. -// If it is not found, it sets appcommon.DefaultServerAddr as AppServerAddr and returns it. +// If it is not found, it sets appcommon.DefaultAppSrvAddr as AppServerAddr and returns it. func (c *Config) AppServerAddress() string { if c.AppServerAddr == "" { - c.AppServerAddr = appcommon.DefaultServerAddr + c.AppServerAddr = appcommon.DefaultAppSrvAddr if err := c.flush(); err != nil && c.log != nil { c.log.WithError(err).Errorf("Failed to flush config to disk") } diff --git a/pkg/visor/gateway.go b/pkg/visor/gateway.go index 070b4d0e0..9124412a7 100644 --- a/pkg/visor/gateway.go +++ b/pkg/visor/gateway.go @@ -97,7 +97,7 @@ package visor // // if reqBody.AutoStart != nil { // if *reqBody.AutoStart != appS.AutoStart { -// if err := v.setAutoStart(appS.Name, *reqBody.AutoStart); err != nil { +// if err := v.setAutoStart(appS.AppName, *reqBody.AutoStart); err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return // } @@ -107,12 +107,12 @@ package visor // if reqBody.Status != nil { // switch *reqBody.Status { // case statusStop: -// if err := v.StopApp(appS.Name); err != nil { +// if err := v.StopApp(appS.AppName); err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return // } // case statusStart: -// if err := v.StartApp(appS.Name); err != nil { +// if err := v.StartApp(appS.AppName); err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return // } @@ -128,21 +128,21 @@ package visor // skysocksClientName = "skysocks-client" // ) // -// if reqBody.Passcode != nil && appS.Name == skysocksName { +// if reqBody.Passcode != nil && appS.AppName == skysocksName { // if err := v.setSocksPassword(*reqBody.Passcode); err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return // } // } // -// if reqBody.PK != nil && appS.Name == skysocksClientName { +// if reqBody.PK != nil && appS.AppName == skysocksClientName { // if err := v.setSocksClientPK(*reqBody.PK); err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return // } // } // -// appS, _ = v.App(appS.Name) +// appS, _ = v.App(appS.AppName) // httputil.WriteJSON(w, r, http.StatusOK, appS) // } //} @@ -169,7 +169,7 @@ package visor // t = time.Unix(0, 0) // } // -// ls, err := app.NewLogStore(filepath.Join(v.dir(), appS.Name), appS.Name, "bbolt") +// ls, err := app.NewLogStore(filepath.Join(v.dir(), appS.AppName), appS.AppName, "bbolt") // if err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return diff --git a/pkg/visor/rpc_test.go b/pkg/visor/rpc_test.go index dd2b73542..ea24630ab 100644 --- a/pkg/visor/rpc_test.go +++ b/pkg/visor/rpc_test.go @@ -98,8 +98,8 @@ func TestListApps(t *testing.T) { pm.On("Exists", apps["bar"].App).Return(true) n := Visor{ - appsConf: apps, - procManager: pm, + appsConf: apps, + procM: pm, } rpc := &RPC{visor: &n, log: logrus.New()} @@ -158,7 +158,7 @@ func TestStartStopApp(t *testing.T) { visorCfg := Config{ KeyPair: keyPair, - AppServerAddr: appcommon.DefaultServerAddr, + AppServerAddr: appcommon.DefaultAppSrvAddr, } visor := &Visor{ @@ -174,9 +174,9 @@ func TestStartStopApp(t *testing.T) { require.NoError(t, os.RemoveAll(visor.dir())) }() - appCfg1 := appcommon.Config{ - Name: app, - ServerAddr: appcommon.DefaultServerAddr, + appCfg1 := appcommon.ProcConfig{ + AppName: app, + AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: visorCfg.Keys().PubKey.Hex(), RoutingPort: apps["foo"].Port, WorkDir: filepath.Join("", app), @@ -193,7 +193,7 @@ func TestStartStopApp(t *testing.T) { pm.On("Exists", app).Return(true) pm.On("Exists", unknownApp).Return(false) - visor.procManager = pm + visor.procM = pm rpc := &RPC{visor: visor, log: logrus.New()} diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 1dab9eb9d..9f9cde81d 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -93,9 +93,8 @@ type Visor struct { cliLis net.Listener hvErrs map[cipher.PubKey]chan error // errors returned when the associated hypervisor ServeRPCClient returns - appDiscF *appdisc.Factory - procManager appserver.ProcManager - appRPCServer *appserver.Server + appDiscF *appdisc.Factory + procM appserver.ProcManager // cancel is to be called when visor.Close is triggered. cancel context.CancelFunc @@ -225,15 +224,11 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con ProxyDisc: cfg.AppDiscConfig().ProxyDisc, } - visor.appRPCServer = appserver.New(logging.MustGetLogger("app_rpc_server"), visor.conf.AppServerAddr) - - go func() { - if err := visor.appRPCServer.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - visor.logger.WithError(err).Error("Serve app_rpc stopped.") - } - }() - - visor.procManager = appserver.NewProcManager(logging.MustGetLogger("proc_manager"), visor.appDiscF, visor.appRPCServer) + logProcM := logging.MustGetLogger("proc_manager") + visor.procM, err = appserver.NewProcManager(logProcM, visor.appDiscF, visor.conf.AppServerAddr) + if err != nil { + return nil, fmt.Errorf("failed to start proc manager: %w", err) + } visor.updater = updater.New(visor.logger, visor.restartCtx, visor.appsPath) @@ -390,6 +385,7 @@ func (visor *Visor) startRPC(ctx context.Context) { } } +// TODO: We should have this re-defined. func (visor *Visor) dir() string { return pathutil.VisorDir(visor.conf.Keys().PubKey.String()) } @@ -483,16 +479,16 @@ func (visor *Visor) Close() (err error) { } } - visor.procManager.StopAll() - - if err = visor.router.Close(); err != nil { - visor.logger.WithError(err).Error("Failed to stop router.") + if err := visor.procM.Close(); err != nil { + visor.logger.WithError(err).Error("Proc manager closed with unexpected error.") } else { - visor.logger.Info("Router stopped successfully.") + visor.logger.Info("Proc manager closed cleanly.") } - if err := visor.appRPCServer.Close(); err != nil { - visor.logger.WithError(err).Error("RPC server closed with error.") + if err = visor.router.Close(); err != nil { + visor.logger.WithError(err).Error("Router closed with unexpected error.") + } else { + visor.logger.Info("Router closed cleanly.") } return err @@ -505,7 +501,7 @@ func (visor *Visor) App(name string) (*AppState, bool) { return nil, false } state := &AppState{AppConfig: app, Status: AppStatusStopped} - if visor.procManager.Exists(app.App) { + if visor.procM.Exists(app.App) { state.Status = AppStatusRunning } return state, true @@ -519,7 +515,7 @@ func (visor *Visor) Apps() []*AppState { for _, app := range visor.appsConf { state := &AppState{AppConfig: app, Status: AppStatusStopped} - if visor.procManager.Exists(app.App) { + if visor.procM.Exists(app.App) { state.Status = AppStatusRunning } @@ -554,6 +550,8 @@ func (visor *Visor) StartApp(appName string) error { // SpawnApp configures and starts new App. func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err error) { + ctx := context.Background() + visor.logger. WithField("app_name", config.App). WithField("args", config.Args). @@ -563,9 +561,9 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er return fmt.Errorf("can't bind to reserved port %d", config.Port) } - appCfg := appcommon.Config{ - Name: config.App, - ServerAddr: visor.conf.AppServerAddr, + appCfg := appcommon.ProcConfig{ + AppName: config.App, + AppSrvAddr: visor.conf.AppServerAddr, VisorPK: visor.conf.Keys().PubKey.Hex(), RoutingPort: config.Port, BinaryDir: visor.appsPath, @@ -591,9 +589,8 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er }() appLogger := logging.MustGetLogger(fmt.Sprintf("app_%s", config.App)) - appArgs := append([]string{filepath.Join(visor.dir(), config.App)}, config.Args...) - pid, err := visor.procManager.Start(appLogger, appCfg, appArgs, logger, errLogger) + pid, err := visor.procM.Start(ctx, appLogger, appCfg, logger, errLogger) if err != nil { return fmt.Errorf("error running app %s: %v", config.App, err) } @@ -613,7 +610,7 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er visor.pidMu.Unlock() - return visor.procManager.Wait(config.App) + return visor.procM.Wait(config.App) } func (visor *Visor) persistPID(name string, pid appcommon.ProcID) error { @@ -637,13 +634,13 @@ func (visor *Visor) persistPID(name string, pid appcommon.ProcID) error { // StopApp stops running App. func (visor *Visor) StopApp(appName string) error { - if !visor.procManager.Exists(appName) { + if !visor.procM.Exists(appName) { return ErrUnknownApp } visor.logger.Infof("Stopping app %s and closing ports", appName) - if err := visor.procManager.Stop(appName); err != nil { + if err := visor.procM.Stop(appName); err != nil { visor.logger.Warn("Failed to stop app: ", err) return err } @@ -723,7 +720,7 @@ func (visor *Visor) setSocksPassword(password string) error { return err } - if visor.procManager.Exists(socksName) { + if visor.procM.Exists(socksName) { visor.logger.Infof("Updated %v password, restarting it", socksName) return visor.RestartApp(socksName) } @@ -745,7 +742,7 @@ func (visor *Visor) setSocksClientPK(pk cipher.PubKey) error { return err } - if visor.procManager.Exists(socksClientName) { + if visor.procM.Exists(socksClientName) { visor.logger.Infof("Updated %v PK, restarting it", socksClientName) return visor.RestartApp(socksClientName) } diff --git a/pkg/visor/visor_test.go b/pkg/visor/visor_test.go index d14b690e2..3b0daf636 100644 --- a/pkg/visor/visor_test.go +++ b/pkg/visor/visor_test.go @@ -105,7 +105,7 @@ func TestVisorStartClose(t *testing.T) { visorCfg := Config{ KeyPair: NewKeyPair(), - AppServerAddr: appcommon.DefaultServerAddr, + AppServerAddr: appcommon.DefaultAppSrvAddr, } logger := logging.MustGetLogger("test") @@ -119,9 +119,9 @@ func TestVisorStartClose(t *testing.T) { } pm := &appserver.MockProcManager{} - appCfg1 := appcommon.Config{ - Name: apps["skychat"].App, - ServerAddr: appcommon.DefaultServerAddr, + appCfg1 := appcommon.ProcConfig{ + AppName: apps["skychat"].App, + AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: visorCfg.Keys().PubKey.Hex(), RoutingPort: apps["skychat"].Port, WorkDir: filepath.Join("", apps["skychat"].App), @@ -134,7 +134,7 @@ func TestVisorStartClose(t *testing.T) { pm.On("StopAll").Return() - visor.procManager = pm + visor.procM = pm dmsgC := dmsg.NewClient(cipher.PubKey{}, cipher.SecKey{}, disc.NewMock(), nil) go dmsgC.Serve() @@ -182,7 +182,7 @@ func TestVisorSpawnApp(t *testing.T) { visorCfg := Config{ KeyPair: NewKeyPair(), - AppServerAddr: appcommon.DefaultServerAddr, + AppServerAddr: appcommon.DefaultAppSrvAddr, } visor := &Visor{ @@ -198,9 +198,9 @@ func TestVisorSpawnApp(t *testing.T) { require.NoError(t, os.RemoveAll(visor.dir())) }() - appCfg := appcommon.Config{ - Name: app.App, - ServerAddr: appcommon.DefaultServerAddr, + appCfg := appcommon.ProcConfig{ + AppName: app.App, + AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: visorCfg.Keys().PubKey.Hex(), RoutingPort: app.Port, WorkDir: filepath.Join("", app.App), @@ -216,12 +216,12 @@ func TestVisorSpawnApp(t *testing.T) { pm.On("Exists", app.App).Return(true) pm.On("Stop", app.App).Return(testhelpers.NoErr) - visor.procManager = pm + visor.procM = pm require.NoError(t, visor.StartApp(app.App)) time.Sleep(100 * time.Millisecond) - require.True(t, visor.procManager.Exists(app.App)) + require.True(t, visor.procM.Exists(app.App)) require.NoError(t, visor.StopApp(app.App)) } @@ -237,7 +237,7 @@ func TestVisorSpawnAppValidations(t *testing.T) { c := &Config{ KeyPair: NewKeyPair(), - AppServerAddr: appcommon.DefaultServerAddr, + AppServerAddr: appcommon.DefaultAppSrvAddr, } visor := &Visor{ @@ -258,9 +258,9 @@ func TestVisorSpawnAppValidations(t *testing.T) { Port: 3, } - appCfg := appcommon.Config{ - Name: app.App, - ServerAddr: appcommon.DefaultServerAddr, + appCfg := appcommon.ProcConfig{ + AppName: app.App, + AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: c.Keys().PubKey.Hex(), RoutingPort: app.Port, WorkDir: filepath.Join("", app.App), @@ -274,7 +274,7 @@ func TestVisorSpawnAppValidations(t *testing.T) { Return(appPID, testhelpers.NoErr) pm.On("Exists", app.App).Return(false) - visor.procManager = pm + visor.procM = pm errCh := make(chan error) go func() { @@ -298,9 +298,9 @@ func TestVisorSpawnAppValidations(t *testing.T) { wantErr := fmt.Sprintf("error running app skychat: %s", appserver.ErrAppAlreadyStarted) pm := &appserver.MockProcManager{} - appCfg := appcommon.Config{ - Name: app.App, - ServerAddr: appcommon.DefaultServerAddr, + appCfg := appcommon.ProcConfig{ + AppName: app.App, + AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: c.Keys().PubKey.Hex(), RoutingPort: app.Port, WorkDir: filepath.Join("", app.App), @@ -312,7 +312,7 @@ func TestVisorSpawnAppValidations(t *testing.T) { Return(appPID, appserver.ErrAppAlreadyStarted) pm.On("Exists", app.App).Return(true) - visor.procManager = pm + visor.procM = pm errCh := make(chan error) go func() { From 04c1200544800e1f2b249f546497b86c23ef9dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 1 May 2020 23:39:09 +1200 Subject: [PATCH 02/17] Added 'make install-generate' target. --- Makefile | 9 ++++ cmd/apps/skychat/static.go | 64 +++++++++++----------- go.sum | 1 + pkg/app/appserver/mock_proc_manager.go | 37 ++++++++----- pkg/hypervisor/README.md | 12 ++--- pkg/setup/README.md | 4 +- pkg/visor/README.md | 75 ++++++++++++++------------ 7 files changed, 115 insertions(+), 87 deletions(-) diff --git a/Makefile b/Makefile index f554053fd..244e210fc 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,15 @@ stop: ## Stop running skywire-visor on host config: ## Generate skywire.json -./skywire-cli visor gen-config -o ./skywire.json -r +install-generate: ## Installs required execs for go generate. + ${OPTS} go install github.com/mjibson/esc + ${OPTS} go install github.com/vektra/mockery/cmd/mockery + # If the following does not work, you may need to run: + # git config --global url.git@github.com:.insteadOf https://github.com/ + # Source: https://stackoverflow.com/questions/27500861/whats-the-proper-way-to-go-get-a-private-repository + # We are using 'go get' instead of 'go install' here, because we don't have a git tag in which 'readmegen' is already implemented. + ${OPTS} go get -u github.com/SkycoinPro/skywire-services/cmd/readmegen@master + generate: ## Generate mocks and config README's go generate ./... diff --git a/cmd/apps/skychat/static.go b/cmd/apps/skychat/static.go index d03fbaeac..825a9c1b7 100644 --- a/cmd/apps/skychat/static.go +++ b/cmd/apps/skychat/static.go @@ -119,7 +119,7 @@ func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { return nil, io.EOF } - return []os.FileInfo(fis[0:limit]), nil + return fis[0:limit], nil } func (f *_escFile) Stat() (os.FileInfo, error) { @@ -212,38 +212,38 @@ var _escData = map[string]*_escFile{ "/index.html": { name: "index.html", local: "static/index.html", - size: 5829, - modtime: 1540421522, + size: 5825, + modtime: 1579921887, compressed: ` -H4sIAAAAAAAC/7xYW2/jxhV+9684Sy9CCbEo2Wu7CUWqSNst0iKbFHWAPiyMeEQeiYMdzrAzQ9uqoP9e -zPBOkbLSBSo/iJzLuXznOxc5eBeLSO8yhESnbHVxEZhvYIRvQwe5s7oACBIksXkACFLUBKKESIU6dHK9 -mX3nrC6KPU01w9WfE6LhhywL5sV7uan0zryYZ6sK9sWz+WwE17MNSSnb+aAIVzOFkm6WvROK/gd9uP6Q -vbZ2IsGE9OHy/v6+XD0UGmEt4l1bS0xVxsjOhw3DtoSU8lmCdJtoH64Xi+ekvUfklnIfFq21NYm+bKXI -eezD5e3d3fX9bU8zaavV+KpnMUZCEk0F94ELjscOUJ6gpLonKe/gxKjSMwvkkZSMxDHl266lfesrqZ6i -Ma6JPAcd8zqLqcSosD4SLE9568ALjXXiw83NohOX2qBrTAfC9f335Gbdx82TGNGMItcz42qHIgxffbg+ -cm6mRdbWMSaK0ba08u5aaC3SvoniGeWGiRcfEhrH2PE1oRpnKiORjcCLJNmyF+nmNjJGM0XVG5YRj0Sa -PiPsK3CslmVzIUqIXovX03D8z/GrSeJ96MDQpnlhUWtPyBjlTJKY5sqHmzrytc0pKkW2eBTHEVp0/RmW -YkLYc/P4JNWYQkw0tpWWFL27G6wccRyP6bXSMB2Qdf3hdpjuC1h4d/8PPjVlsSoIMiXsDU88hTxG2XDt -8vo+iu6JQXL4gsQI6XPnymZz+93t/Rj4KiP8NFVLrGYMN7pPhJJZxdZN9gpKMBrD5ebe/I15txEyPaeY -NVE6QfZLxAGu2zrTMuiYNZRnuf5semloIvd4Fgj+EV3K2iTLhjSAjg/XQ3aclZgtI1W+TmnXzK5RcDOO -USceX2eazZFcM8q7bW2gYNo4/y4XFt4fWr3BTiLzehQJ5sVkYx7NtFDOOMS0R4gYUSp0ylbplKMLBNaG -crNrmQOCFxaFDskyL5JINP6zOjPRCVXTJUjUueSwIUzhspYLEFjHwDrmGAY5kDESYSJYjDJ0PnKNErJ8 -zWgEX3DnwHzkcmGEA8+E5Rg63zbGz42d9VvOgMYtL5Rz7Jgpvc4qmOesRGdu4anGupRQXl0q+5TTl19m -aSO9XdYr2UPwttO7D64pZJ+K/a8F9l+SaoSdyCWUGs+G9gF5PIhuMDfI1MNvJGmmy3PWObBj8r7djbjS -Mo+0kJNpe8N2goSqJg0UhPD5cXnqCITAc8aGzlTRgBD2h5NClLcR8iOJkomEcFVs/kbiuGG0nE6HJPym -FD7ka+P1GiftI1VOm09f1BtOe1mukomcnvLps3wcwiYWUZ4i194W9UeG5vFPu7/FE7eR7k49yjnKH3/9 -9BN8G3bvm89TwOgqIJBI3ITOZc3l93sJYRj28f8juMVY54IPrnsw9I0Yjb5U7GUY6bdKw/u9PARzsgrm -jK6eTsBYJUKqtkcwWmIV1v5MUoQQUrX1NlKk1m43RddYWwwG1tqq5bvLIUmWfk/v90aKVgbSH0Uu1WTq -afGgJeXbydTLSPygidSTmytwF+704LcvfKI81/jWlaflRVc/3cCktv1dZfs338C7PlUoj1geo6qPT49g -acjaoWF9oef74eJMSlVUPItQ/UJnBihnFZj5dfV+r9UhmNvnANOGb3UoD4YhlcEHP5hjugrM+FUum1J3 -COZ2paDQCIO66TpMICVyGRn2cHyBj8/I9YNdmbhzpdDt41Uc9wQvXYMQCnaGq6FIFDpiogmE8PeHX372 -MiKVJbRnVvvimzuN/D1o5Vvz/kI0XoFBxbcyy6n3yk7U5VJ177A8wYs6r4rv6djZuv60tD0WBWvk6mEk -m/szA7KReLRLPbLPi0fPtqTlOTW0eh8u3t2yPHS2bXC/kg0YfNSakFlujlXpf+codw9WrpA/MDZxq9/I -7rRuSfa3RrgC8+3ZlPiJKu1JTMUzTqrie9SfkLUOkzhuTi6/JsFDcN2TjamLwWPtRqq2NiUGOGey5TAO -ezP+jHLECj/Bjg3qKKk9cq9gDynqRMQ+uP/45eFX96r4L5pfpKSypZpudpN9Qz+/F96rKiN9q/5gfDjO -Gk8nyCfSTCGD9aAq9xKVJ75Mx46cXwZMq6jy39q1HJf4e9L/vGifqgRddtahGqJUTQRApvAUKAY346xp -sQZp82ygJgylnjz9lVCGMWhhedRE7P3e9oyn6ZiRh+PlwfBGxDALG5VYgTPtMrpD7BfKY/HikSwr+4wZ -kifT6mdbPUQH8+LXWjAv/mn93wAAAP//aOzW6sUWAAA= +H4sIAAAAAAAC/7xYbW/juBH+nl8xqyxONi6WnWyS3smSi2u7xbW4vSuaA/phEVxoaWwRS5EqSSVxDf/3 +gtS7LDm+LlDngyW+DJ955uHMOMG7WER6lyEkOmWri4vAfAMjfBs6yJ3VBUCQIInNA0CQoiYQJUQq1KGT +683sO2d1Ucxpqhmu/pwQDT9kWTAv3stJpXfmxTzbo2BfPJvPRnA925CUsp0PinA1UyjpZtlboeh/0Ifr +D9lrayYSTEgfLu/v78vRQ3EirEW8a58SU5UxsvNhw7BtIaV8liDdJtqH68XiOWnPEbml3IdFa2xNoi9b +KXIe+3B5e3d3fX/bO5m0j9X4qmcxRkISTQX3gQuOxw5QnqCkumcp7/DEqNIzS+SRlYzEMeXbLtI++sqq +p2iMayLPYce8zmIqMSrQR4LlKW8teKGxTny4uVl04lIDusZ0IFzff09u1n3ePIkRzShyPTOudiTC8NWH +6yPnZlpk7TPGTDHatlbuXQutRdqHKJ5Rbph48SGhcYwdXxOqcaYyEtkIvEiSLXuRbnYjYzRTVL2BjHgk +0vQZYV+RY09ZNhuihOi1eD1Nx/8cv1ok3ocODW2ZF4hac0LGKGeSxDRXPtzUka8xp6gU2eJRHEdk0fVn +2IoJYc/N45VUYwox0dg+tJTo3d1g5ojjeOxcaw3TAVvXH26H5b6AhXf3/9BTkxarhCBTwt7wxFPIY5SN +1i6v76PonhgmhzdIjJA+d7ZsNrff3d6Pka8ywk9LteRqxnCj+0IolVVM3WSvoASjMVxu7s3fmHcbIdNz +klkTpRNiv0Qc0LrNMy1Ax6qhPMv1Z1NLQxO5x7NI8I/kUuYmWRakAXZ8uB7CcdbFbIFU+TqlXZhdUHAz +zlEnHl8Hzd6RXDPKu2VtIGHaOP8uFxbeH1q1wXYi87oVCeZFZ2MeTbdQ9jjElEeIGFEqdMpS6ZStCwQW +QznZReaA4AWi0CFZ5kUSicZ/VmsmOqFqugSJOpccNoQpXNZ2AQLrGFjHHKMgBzJGIkwEi1GGzkeuUUKW +rxmN4AvuHJiPbC5AOPBMWI6h820Dfm5w1m85Axq3vFDOsWMm9TqrYJ6zkp25padq61JCebWprFNO3355 +Sxvr7bRe2R6it329++SaRPapmP9aYv8lqUbYiVxCeeLZ1D4gjwfZDeaGmbr5jSTNdLnOOge2Td63qxFX +WuaRFnIybU/YSpBQ1VwDBSF8flyeWgIh8JyxoTVVNCCE/eGkEeVthPxIomQiIVwVk7+ROG4ULafTIQu/ +KYUP+dp4vcZJe0l1p82nb+oNp70sV8lETk/59Fk+DnETiyhPkWtvi/ojQ/P4p93f4onbWHenHuUc5Y+/ +fvoJvg27+83nKWB0FRBIJG5C57LW8vu9hDAM+/z/EdyirXPBB9c9GPlGjEZfKvUyjPRbqeH9Xh6COVkF +c0ZXTydorC5CqrZHNFphFWh/JilCCKnaehspUovbTdE1aIvGwKKtSr67HLJk5ff0fm+saGUo/VHkUk2m +nhYPWlK+nUy9jMQPmkg9ubkCd+FOD357wyfKc41vbXlaXnTPpxuY1NjfVdi/+Qbe9aVCecTyGFW9fHpE +SyPWjgzrDT3fDxdnSqqS4lmC6ic600A5q8D0r6v3e60Owdw+B5g2eqtDeTAKqQAf/GCO6Sow7Vc5bFLd +IZjbkUJCIwrqXtdhASmRy8ioh+MLfHxGrh/syMSdK4Vun69iuSd46RqEUKgzXA1FojgjJppACH9/+OVn +LyNSWUF7ZrRvvtnT2N+DVr6F9xei8QoMK761WXa9V7ajLoeqfYflCV3U96r4no6trfNP67THImGNbD2M +3OZ+z4BsJB7tVI/s8+LRsyVpeU4Ord6Hk3c3LQ+tbQPuZ7IBwEelCZnV5liW/neOcvdg7Qr5A2MTt/qN +7E7rkmR/a4QrMN+evRI/UaU9ial4xkmVfI/qE7LWYhLHzcrl11zwEFz3ZGHqcvBYu5Gqbae2tjP5OOVN +6zOqD2v4hDI2qKOk9sa9gj2kqBMR++D+45eHX92r4j9ofnEdlU3TdLOb7Bvp+b3QXlW30bfHH6ZwmB7f +GE8nyCfSdCCDuaBK9RKVJ75Mx5acnwJMmajuvsW1HLf4e67+eZE+lQW6yqxDNSSnWgiATOEpUgxvxllT +Xg3T5tlQTRhKPXn6K6EMY9DC6qiJ2Pu9rRdP0zGQh+PhwfBGxCgLmyOxIqen6I6wXyiPxYtHsqysMaZB +nkyrn2x1Ax3Mi19qwbz4h/V/AwAA//9G17pUwRYAAA== `, }, diff --git a/go.sum b/go.sum index 27889888f..f1a9227b4 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.3.0 h1:OQIvuDgm00gWVWGTf4m4mCt6W1/0YqU7Ntg0mySWgaI= github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= diff --git a/pkg/app/appserver/mock_proc_manager.go b/pkg/app/appserver/mock_proc_manager.go index 8df0be593..e9f2d206f 100644 --- a/pkg/app/appserver/mock_proc_manager.go +++ b/pkg/app/appserver/mock_proc_manager.go @@ -3,10 +3,12 @@ package appserver import ( - io "io" + context "context" appcommon "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + io "io" + logging "github.com/SkycoinProject/skycoin/src/util/logging" mock "github.com/stretchr/testify/mock" @@ -17,6 +19,20 @@ type MockProcManager struct { mock.Mock } +// Close provides a mock function with given fields: +func (_m *MockProcManager) Close() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Exists provides a mock function with given fields: name func (_m *MockProcManager) Exists(name string) bool { ret := _m.Called(name) @@ -36,20 +52,20 @@ func (_m *MockProcManager) Range(next func(string, *Proc) bool) { _m.Called(next) } -// Start provides a mock function with given fields: log, c, args, stdout, stderr -func (_m *MockProcManager) Start(log *logging.Logger, c appcommon.ProcConfig, args []string, stdout io.Writer, stderr io.Writer) (appcommon.ProcID, error) { - ret := _m.Called(log, c, args, stdout, stderr) +// Start provides a mock function with given fields: ctx, log, conf, stdout, stderr +func (_m *MockProcManager) Start(ctx context.Context, log *logging.Logger, conf appcommon.ProcConfig, stdout io.Writer, stderr io.Writer) (appcommon.ProcID, error) { + ret := _m.Called(ctx, log, conf, stdout, stderr) var r0 appcommon.ProcID - if rf, ok := ret.Get(0).(func(*logging.Logger, appcommon.ProcConfig, []string, io.Writer, io.Writer) appcommon.ProcID); ok { - r0 = rf(log, c, args, stdout, stderr) + if rf, ok := ret.Get(0).(func(context.Context, *logging.Logger, appcommon.ProcConfig, io.Writer, io.Writer) appcommon.ProcID); ok { + r0 = rf(ctx, log, conf, stdout, stderr) } else { r0 = ret.Get(0).(appcommon.ProcID) } var r1 error - if rf, ok := ret.Get(1).(func(*logging.Logger, appcommon.ProcConfig, []string, io.Writer, io.Writer) error); ok { - r1 = rf(log, c, args, stdout, stderr) + if rf, ok := ret.Get(1).(func(context.Context, *logging.Logger, appcommon.ProcConfig, io.Writer, io.Writer) error); ok { + r1 = rf(ctx, log, conf, stdout, stderr) } else { r1 = ret.Error(1) } @@ -71,11 +87,6 @@ func (_m *MockProcManager) Stop(name string) error { return r0 } -// StopAll provides a mock function with given fields: -func (_m *MockProcManager) StopAll() { - _m.Called() -} - // Wait provides a mock function with given fields: name func (_m *MockProcManager) Wait(name string) error { ret := _m.Called(name) diff --git a/pkg/hypervisor/README.md b/pkg/hypervisor/README.md index 4c1265003..00e4e56ef 100644 --- a/pkg/hypervisor/README.md +++ b/pkg/hypervisor/README.md @@ -1,12 +1,12 @@ # Config -- `public_key` (PubKey) -- `secret_key` (SecKey) +- `public_key` ([PubKey](#PubKey)) +- `secret_key` ([SecKey](#SecKey)) - `db_path` (string) - `enable_auth` (bool) - `cookies` ([CookieConfig](#CookieConfig)) - `dmsg_discovery` (string) -- `dmsg_port` (uint16) +- `dmsg_port` ([uint16](#uint16)) - `http_addr` (string) - `enable_tls` (bool) - `tls_cert_file` (string) @@ -15,9 +15,9 @@ # CookieConfig -- `hash_key` (Key) -- `block_key` (Key) -- `expires_duration` (Duration) +- `hash_key` ([Key](#Key)) +- `block_key` ([Key](#Key)) +- `expires_duration` ([Duration](#Duration)) - `path` (string) - `domain` (string) - `-` (bool) diff --git a/pkg/setup/README.md b/pkg/setup/README.md index 1edcc41cd..0167c2a84 100644 --- a/pkg/setup/README.md +++ b/pkg/setup/README.md @@ -1,7 +1,7 @@ # Config -- `public_key` (PubKey) -- `secret_key` (SecKey) +- `public_key` ([PubKey](#PubKey)) +- `secret_key` ([SecKey](#SecKey)) - `dmsg` ([DmsgConfig](#DmsgConfig)) - `transport_discovery` (string) - `log_level` (string) diff --git a/pkg/visor/README.md b/pkg/visor/README.md index 9710efa06..1969686f1 100644 --- a/pkg/visor/README.md +++ b/pkg/visor/README.md @@ -11,43 +11,44 @@ - `transport` (*[TransportConfig](#TransportConfig)) - `routing` (*[RoutingConfig](#RoutingConfig)) - `uptime_tracker` (*[UptimeTrackerConfig](#UptimeTrackerConfig)) +- `app_discovery` (*[AppDiscConfig](#AppDiscConfig)) - `apps` ([][AppConfig](#AppConfig)) -- `trusted_visors` () +- `trusted_visors` ([][PubKey](#PubKey)) - `hypervisors` ([][HypervisorConfig](#HypervisorConfig)) - `apps_path` (string) - `local_path` (string) - `log_level` (string) -- `shutdown_timeout` (Duration) +- `shutdown_timeout` ([Duration](#Duration)) - `interfaces` (*[InterfaceConfig](#InterfaceConfig)) - `app_server_addr` (string) - `restart_check_delay` (string) +# KeyPair + +- `public_key` ([PubKey](#PubKey)) +- `secret_key` ([SecKey](#SecKey)) + + # DmsgPtyConfig -- `port` (uint16) +- `port` ([uint16](#uint16)) - `authorization_file` (string) - `cli_network` (string) - `cli_address` (string) -# TransportConfig - -- `discovery` (string) -- `log_store` (*[LogStoreConfig](#LogStoreConfig)) - - -# KeyPair +# RoutingConfig -- `public_key` (PubKey) -- `secret_key` (SecKey) +- `setup_nodes` ([][PubKey](#PubKey)) +- `route_finder` (string) +- `route_finder_timeout` ([Duration](#Duration)) -# RoutingConfig +# TransportConfig -- `setup_nodes` () -- `route_finder` (string) -- `route_finder_timeout` (Duration) +- `discovery` (string) +- `log_store` (*[LogStoreConfig](#LogStoreConfig)) # UptimeTrackerConfig @@ -55,28 +56,34 @@ - `addr` (string) -# LogStoreConfig +# AppDiscConfig -- `type` (LogStoreType) -- `location` (string) - - -# InterfaceConfig - -- `rpc` (string) +- `update_interval` ([Duration](#Duration)) +- `proxy_discovery_addr` (string) # AppConfig - `app` (string) - `auto_start` (bool) -- `port` (Port) +- `port` ([Port](#Port)) - `args` ([]string) # HypervisorConfig -- `public_key` (PubKey) +- `public_key` ([PubKey](#PubKey)) + + +# LogStoreConfig + +- `type` ([LogStoreType](#LogStoreType)) +- `location` (string) + + +# InterfaceConfig + +- `rpc` (string) # DmsgConfig @@ -85,18 +92,18 @@ - `sessions_count` (int) -# Logger +# STCPConfig -- `` (FieldLogger) +- `pk_table` (map[[PubKey](#PubKey)]string) +- `local_address` (string) -# Mutex +# Logger -- `state` (int32) -- `sema` (uint32) +- `` ([FieldLogger](#FieldLogger)) -# STCPConfig +# Mutex -- `pk_table` () -- `local_address` (string) +- `state` (int32) +- `sema` ([uint32](#uint32)) From d02d026e3941a6506d17e70eef5756a1cf8c0e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Mon, 4 May 2020 23:34:23 +1200 Subject: [PATCH 03/17] Various app cleanups. * Simplified app.Client initialization. * Simplified app logging. --- cmd/apps/helloworld/helloworld.go | 22 ++-- cmd/apps/skychat/chat.go | 49 ++++----- cmd/apps/skysocks-client/skysocks-client.go | 23 +--- cmd/apps/skysocks/skysocks.go | 27 ++--- pkg/app/{ => appcommon}/log.go | 9 +- pkg/app/{ => appcommon}/log_store.go | 2 +- pkg/app/{ => appcommon}/log_store_test.go | 2 +- pkg/app/{ => appcommon}/log_test.go | 2 +- pkg/app/appcommon/proc_config.go | 64 ++++++++--- pkg/app/appserver/proc.go | 6 +- pkg/app/client.go | 113 +++++++------------- pkg/app/listener.go | 4 +- pkg/hypervisor/hypervisor.go | 5 +- pkg/visor/config.go | 25 ++--- pkg/visor/rpc.go | 6 +- pkg/visor/rpc_client.go | 9 +- pkg/visor/rpc_test.go | 2 +- pkg/visor/visor.go | 65 ++++++----- pkg/visor/visor_test.go | 8 +- 19 files changed, 193 insertions(+), 250 deletions(-) rename pkg/app/{ => appcommon}/log.go (85%) rename pkg/app/{ => appcommon}/log_store.go (99%) rename pkg/app/{ => appcommon}/log_store_test.go (98%) rename pkg/app/{ => appcommon}/log_test.go (97%) diff --git a/cmd/apps/helloworld/helloworld.go b/cmd/apps/helloworld/helloworld.go index 705f9cad4..754dca162 100644 --- a/cmd/apps/helloworld/helloworld.go +++ b/cmd/apps/helloworld/helloworld.go @@ -4,11 +4,9 @@ simple client server app for skywire visor testing package main import ( - "log" "os" "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" @@ -21,24 +19,18 @@ const ( ) func main() { - if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { - log.Printf("Failed to output build info: %v", err) - } + appC := app.NewClient() + defer appC.Close() - clientConfig, err := app.ClientConfigFromEnv() - if err != nil { - log.Fatalf("Error getting client config: %v\n", err) - } + log := appC.Logger() - app, err := app.NewClient(logging.MustGetLogger("helloworld"), clientConfig) - if err != nil { - log.Fatalf("Error creating app client: %v\n", err) + if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { + log.Printf("Failed to output build info: %v", err) } - defer app.Close() if len(os.Args) == 1 { port := routing.Port(1024) - l, err := app.Listen(netType, port) + l, err := appC.Listen(netType, port) if err != nil { log.Fatalf("Error listening network %v on port %d: %v\n", netType, port, err) } @@ -72,7 +64,7 @@ func main() { log.Fatal("Failed to construct PubKey: ", err, os.Args[1]) } - conn, err := app.Dial(appnet.Addr{ + conn, err := appC.Dial(appnet.Addr{ Net: netType, PubKey: remotePK, Port: 10, diff --git a/cmd/apps/skychat/chat.go b/cmd/apps/skychat/chat.go index 31a4d2551..c414aeb42 100644 --- a/cmd/apps/skychat/chat.go +++ b/cmd/apps/skychat/chat.go @@ -25,7 +25,6 @@ import ( ) const ( - appName = "skychat" netType = appnet.TypeSkynet port = routing.Port(1) ) @@ -34,40 +33,30 @@ var addr = flag.String("addr", ":8001", "address to bind") var r = netutil.NewRetrier(50*time.Millisecond, 5, 2) var ( - chatApp *app.Client - clientCh chan string - chatConns map[cipher.PubKey]net.Conn - connsMu sync.Mutex - log *logging.MasterLogger + appC *app.Client + clientCh chan string + conns map[cipher.PubKey]net.Conn // Chat connections + connsMu sync.Mutex + log *logging.MasterLogger ) func main() { - log = app.NewLogger(appName) - flag.Parse() + appC = app.NewClient() + defer appC.Close() + + log = appC.Logger() if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { log.Printf("Failed to output build info: %v", err) } - clientConfig, err := app.ClientConfigFromEnv() - if err != nil { - log.Fatalf("Error getting client config: %v\n", err) - } - - // TODO: pass `log`? - a, err := app.NewClient(logging.MustGetLogger(fmt.Sprintf("app_%s", appName)), clientConfig) - if err != nil { - log.Fatal("Setup failure: ", err) - } - defer a.Close() - log.Println("Successfully created skychat app") - - chatApp = a + flag.Parse() + log.Println("Successfully started skychat.") clientCh = make(chan string) defer close(clientCh) - chatConns = make(map[cipher.PubKey]net.Conn) + conns = make(map[cipher.PubKey]net.Conn) go listenLoop() http.Handle("/", http.FileServer(FS(false))) @@ -79,7 +68,7 @@ func main() { } func listenLoop() { - l, err := chatApp.Listen(netType, port) + l, err := appC.Listen(netType, port) if err != nil { log.Printf("Error listening network %v on port %d: %v\n", netType, port, err) return @@ -96,7 +85,7 @@ func listenLoop() { raddr := conn.RemoteAddr().(appnet.Addr) connsMu.Lock() - chatConns[raddr.PubKey] = conn + conns[raddr.PubKey] = conn connsMu.Unlock() log.Printf("Accepted skychat conn on %s from %s\n", conn.LocalAddr(), raddr.PubKey) @@ -113,7 +102,7 @@ func handleConn(conn net.Conn) { log.Println("Failed to read packet:", err) raddr := conn.RemoteAddr().(appnet.Addr) connsMu.Lock() - delete(chatConns, raddr.PubKey) + delete(conns, raddr.PubKey) connsMu.Unlock() return } @@ -150,13 +139,13 @@ func messageHandler(w http.ResponseWriter, req *http.Request) { Port: 1, } connsMu.Lock() - conn, ok := chatConns[pk] + conn, ok := conns[pk] connsMu.Unlock() if !ok { var err error err = r.Do(func() error { - conn, err = chatApp.Dial(addr) + conn, err = appC.Dial(addr) return err }) if err != nil { @@ -165,7 +154,7 @@ func messageHandler(w http.ResponseWriter, req *http.Request) { } connsMu.Lock() - chatConns[pk] = conn + conns[pk] = conn connsMu.Unlock() go handleConn(conn) @@ -176,7 +165,7 @@ func messageHandler(w http.ResponseWriter, req *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) connsMu.Lock() - delete(chatConns, pk) + delete(conns, pk) connsMu.Unlock() return diff --git a/cmd/apps/skysocks-client/skysocks-client.go b/cmd/apps/skysocks-client/skysocks-client.go index 114554044..93004fbf4 100644 --- a/cmd/apps/skysocks-client/skysocks-client.go +++ b/cmd/apps/skysocks-client/skysocks-client.go @@ -5,13 +5,11 @@ package main import ( "flag" - "fmt" "io" "net" "time" "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/skywire-mainnet/internal/netutil" "github.com/SkycoinProject/skywire-mainnet/internal/skysocks" @@ -49,7 +47,10 @@ func dialServer(appCl *app.Client, pk cipher.PubKey) (net.Conn, error) { } func main() { - log := app.NewLogger(appName) + appC := app.NewClient() + defer appC.Close() + + log := appC.Logger() skysocks.Log = log.PackageLogger("skysocks") if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { @@ -60,20 +61,6 @@ func main() { var serverPK = flag.String("srv", "", "PubKey of the server to connect to") flag.Parse() - config, err := app.ClientConfigFromEnv() - if err != nil { - log.Fatalf("Error getting client config: %v\n", err) - } - - socksApp, err := app.NewClient(logging.MustGetLogger(fmt.Sprintf("app_%s", appName)), config) - - if err != nil { - log.Fatal("Setup failure: ", err) - } - defer func() { - socksApp.Close() - }() - if *serverPK == "" { log.Warn("Empty server PubKey. Exiting") return @@ -85,7 +72,7 @@ func main() { } for { - conn, err := dialServer(socksApp, pk) + conn, err := dialServer(appC, pk) if err != nil { log.Fatalf("Failed to dial to a server: %v", err) } diff --git a/cmd/apps/skysocks/skysocks.go b/cmd/apps/skysocks/skysocks.go index 3f1ff4e18..90a053678 100644 --- a/cmd/apps/skysocks/skysocks.go +++ b/cmd/apps/skysocks/skysocks.go @@ -5,12 +5,9 @@ package main import ( "flag" - "fmt" "os" "os/signal" - "github.com/SkycoinProject/skycoin/src/util/logging" - "github.com/SkycoinProject/skywire-mainnet/internal/skysocks" "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" @@ -25,37 +22,25 @@ const ( ) func main() { - log := app.NewLogger(appName) - skysocks.Log = log.PackageLogger("skysocks") + appC := app.NewClient() + defer appC.Close() + + log := appC.Logger() + skysocks.Log = log.PackageLogger(appName) if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { log.Printf("Failed to output build info: %v", err) } var passcode = flag.String("passcode", "", "Authorize user against this passcode") - flag.Parse() - config, err := app.ClientConfigFromEnv() - if err != nil { - log.Fatalf("Error getting client config: %v\n", err) - } - - socksApp, err := app.NewClient(logging.MustGetLogger(fmt.Sprintf("app_%s", appName)), config) - if err != nil { - log.Fatal("Setup failure: ", err) - } - - defer func() { - socksApp.Close() - }() - srv, err := skysocks.NewServer(*passcode, log) if err != nil { log.Fatal("Failed to create a new server: ", err) } - l, err := socksApp.Listen(netType, port) + l, err := appC.Listen(netType, port) if err != nil { log.Fatalf("Error listening network %v on port %d: %v\n", netType, port, err) } diff --git a/pkg/app/log.go b/pkg/app/appcommon/log.go similarity index 85% rename from pkg/app/log.go rename to pkg/app/appcommon/log.go index 8fad8a1d2..a7785af05 100644 --- a/pkg/app/log.go +++ b/pkg/app/appcommon/log.go @@ -1,8 +1,7 @@ -package app +package appcommon import ( "io" - "os" "time" "github.com/SkycoinProject/skycoin/src/util/logging" @@ -11,8 +10,8 @@ import ( // NewLogger returns a logger which persists app logs. This logger should be passed down // for use on any other function used by the app. It's configured from an additional app argument. // It modifies os.Args stripping from it such value. Should be called before using os.Args inside the app -func NewLogger(appName string) *logging.MasterLogger { - db, err := newBoltDB(os.Args[1], appName) +func NewLogger(dbPath string, appName string) *logging.MasterLogger { + db, err := newBoltDB(dbPath, appName) if err != nil { panic(err) } @@ -20,7 +19,7 @@ func NewLogger(appName string) *logging.MasterLogger { l := newAppLogger() l.SetOutput(io.MultiWriter(l.Out, db)) - os.Args = append([]string{os.Args[0]}, os.Args[2:]...) + //os.Args = append([]string{os.Args[0]}, os.Args[2:]...) return l } diff --git a/pkg/app/log_store.go b/pkg/app/appcommon/log_store.go similarity index 99% rename from pkg/app/log_store.go rename to pkg/app/appcommon/log_store.go index c8c2a6523..1a704598b 100644 --- a/pkg/app/log_store.go +++ b/pkg/app/appcommon/log_store.go @@ -1,4 +1,4 @@ -package app +package appcommon import ( "bytes" diff --git a/pkg/app/log_store_test.go b/pkg/app/appcommon/log_store_test.go similarity index 98% rename from pkg/app/log_store_test.go rename to pkg/app/appcommon/log_store_test.go index 8ff172d1f..b727fa03c 100644 --- a/pkg/app/log_store_test.go +++ b/pkg/app/appcommon/log_store_test.go @@ -1,4 +1,4 @@ -package app +package appcommon import ( "fmt" diff --git a/pkg/app/log_test.go b/pkg/app/appcommon/log_test.go similarity index 97% rename from pkg/app/log_test.go rename to pkg/app/appcommon/log_test.go index 1b026d8b1..b37713633 100644 --- a/pkg/app/log_test.go +++ b/pkg/app/appcommon/log_test.go @@ -1,4 +1,4 @@ -package app +package appcommon import ( "io/ioutil" diff --git a/pkg/app/appcommon/proc_config.go b/pkg/app/appcommon/proc_config.go index 2422ae939..3924624f9 100644 --- a/pkg/app/appcommon/proc_config.go +++ b/pkg/app/appcommon/proc_config.go @@ -2,10 +2,13 @@ package appcommon import ( "encoding/hex" + "encoding/json" "errors" "fmt" - "path/filepath" + "os" + "github.com/SkycoinProject/dmsg/cipher" + "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/google/uuid" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" @@ -15,6 +18,9 @@ import ( const DefaultAppSrvAddr = "localhost:5505" const ( + // EnvProcConfig is the env name which contains a JSON-encoded proc config. + EnvProcConfig = "PROC_CONFIG" + // EnvProcKey is a name for env arg containing skywire application key. EnvProcKey = "PROC_KEY" // EnvAppSrvAddr is a name for env arg containing app server address. @@ -23,6 +29,10 @@ const ( EnvVisorPK = "VISOR_PK" ) +var ( + ErrProcConfigEnvNotDefined = fmt.Errorf("env '%s' is not defined", EnvProcConfig) +) + // ProcKey is a unique key to authenticate a proc within the app server. type ProcKey [16]byte @@ -68,14 +78,28 @@ type ProcID uint16 // ProcConfig defines configuration parameters for `Proc`. type ProcConfig struct { - AppName string `json:"app_name"` - AppSrvAddr string `json:"app_server_addr"` - ProcKey ProcKey `json:"proc_key"` - ProcArgs []string `json:"proc_args"` - VisorPK string `json:"visor_pk"` - RoutingPort routing.Port `json:"routing_port"` - BinaryDir string `json:"binary_dir"` - WorkDir string `json:"work_dir"` + AppName string `json:"app_name"` + AppSrvAddr string `json:"app_server_addr"` + ProcKey ProcKey `json:"proc_key"` + ProcArgs []string `json:"proc_args"` + ProcWorkDir string `json:"proc_work_dir"` + VisorPK cipher.PubKey `json:"visor_pk"` + RoutingPort routing.Port `json:"routing_port"` + BinaryLoc string `json:"binary_loc"` + LogDBLoc string `json:"log_db_loc"` +} + +// ProcConfigFromEnv obtains a ProcConfig from the associated env variable, returning an error if any. +func ProcConfigFromEnv() (ProcConfig, error) { + v, ok := os.LookupEnv(EnvProcConfig) + if !ok { + return ProcConfig{}, ErrProcConfigEnvNotDefined + } + var conf ProcConfig + if err := json.Unmarshal([]byte(v), &conf); err != nil { + return ProcConfig{}, fmt.Errorf("invalid %s env value: %w", EnvProcConfig, err) + } + return conf, nil } // EnsureKey ensures that a proc key is provided in the ProcConfig. @@ -85,17 +109,23 @@ func (c *ProcConfig) EnsureKey() { } } -// BinaryLoc returns the binary path using the associated fields of the ProcConfig. -func (c *ProcConfig) BinaryLoc() string { - return filepath.Join(c.BinaryDir, c.AppName) -} - // Envs returns the env variables that are passed to the associated proc. func (c *ProcConfig) Envs() []string { const format = "%s=%s" return []string{ - fmt.Sprintf(format, EnvProcKey, c.ProcKey), - fmt.Sprintf(format, EnvAppSrvAddr, c.AppSrvAddr), - fmt.Sprintf(format, EnvVisorPK, c.VisorPK), + fmt.Sprintf(format, EnvProcConfig, string(c.encodeJSON())), } } + +func (c *ProcConfig) encodeJSON() []byte { + b, err := json.Marshal(c) + if err != nil { + panic(err) + } + return b +} + +// Logger returns the associated logger for the app. +func (c *ProcConfig) Logger() *logging.MasterLogger { + return NewLogger(c.LogDBLoc, c.AppName) +} diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 037deb4af..378db85ce 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -40,9 +40,9 @@ type Proc struct { // NewProc constructs `Proc`. func NewProc(log *logging.Logger, conf appcommon.ProcConfig, disc appdisc.Updater, stdout, stderr io.Writer) *Proc { - cmd := exec.Command(conf.BinaryLoc(), conf.ProcArgs...) // nolint:gosec - cmd.Env = conf.Envs() - cmd.Dir = conf.WorkDir + cmd := exec.Command(conf.BinaryLoc, conf.ProcArgs...) // nolint:gosec + cmd.Dir = conf.ProcWorkDir + cmd.Env = append(os.Environ(), conf.Envs()...) cmd.Stdout = stdout cmd.Stderr = stderr diff --git a/pkg/app/client.go b/pkg/app/client.go index bc8d5b17d..afd583796 100644 --- a/pkg/app/client.go +++ b/pkg/app/client.go @@ -1,14 +1,11 @@ package app import ( - "errors" "fmt" "net" "net/rpc" - "os" "strings" - "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" @@ -17,92 +14,58 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/routing" ) -var ( - // ErrVisorPKNotProvided is returned when the visor PK is not provided. - ErrVisorPKNotProvided = errors.New("visor PK is not provided") - // ErrVisorPKInvalid is returned when the visor PK is invalid. - ErrVisorPKInvalid = errors.New("visor PK is invalid") - // ErrServerAddrNotProvided is returned when app server address is not provided. - ErrServerAddrNotProvided = errors.New("server address is not provided") - // ErrProcKeyNotProvided is returned when the proc key is not provided. - ErrProcKeyNotProvided = errors.New("proc key is not provided") - // ErrProcKeyInvalid occurs when proc key is invalid. - ErrProcKeyInvalid = errors.New("proc key is invalid") -) - -// ClientConfig is a configuration for `Client`. -type ClientConfig struct { - VisorPK cipher.PubKey - ServerAddr string - ProcKey appcommon.ProcKey +// Client is used by skywire apps. +type Client struct { + log *logging.MasterLogger + conf appcommon.ProcConfig + rpc RPCClient + lm *idmanager.Manager // contains listeners associated with their IDs + cm *idmanager.Manager // contains connections associated with their IDs } -// ClientConfigFromEnv creates client config from the ENV args. -func ClientConfigFromEnv() (ClientConfig, error) { - procKeyStr := os.Getenv(appcommon.EnvProcKey) - if procKeyStr == "" { - return ClientConfig{}, ErrProcKeyNotProvided - } - - var procKey appcommon.ProcKey - if err := procKey.UnmarshalText([]byte(procKeyStr)); err != nil { - return ClientConfig{}, ErrProcKeyInvalid - } - - serverAddr := os.Getenv(appcommon.EnvAppSrvAddr) - if serverAddr == "" { - return ClientConfig{}, ErrServerAddrNotProvided - } - - visorPKStr := os.Getenv(appcommon.EnvVisorPK) - if visorPKStr == "" { - return ClientConfig{}, ErrVisorPKNotProvided - } - - var visorPK cipher.PubKey - if err := visorPK.UnmarshalText([]byte(visorPKStr)); err != nil { - return ClientConfig{}, ErrVisorPKInvalid +// NewClient creates a new Client, panicking on any error. +func NewClient() *Client { + conf, err := appcommon.ProcConfigFromEnv() + if err != nil { + panic(fmt.Errorf("failed to obtain proc config: %w", err)) } - - conf := ClientConfig{ - VisorPK: visorPK, - ServerAddr: serverAddr, - ProcKey: procKey, + client, err := NewClientFromConfig(conf) + if err != nil { + conf.Logger().Panicf("app client: %v", err) } - return conf, nil -} - -// Client is used by skywire apps. -type Client struct { - log *logging.Logger - visorPK cipher.PubKey - rpc RPCClient - lm *idmanager.Manager // contains listeners associated with their IDs - cm *idmanager.Manager // contains connections associated with their IDs + return client } -// NewClient creates a new `Client`. The `Client` needs to be provided with: -// - log: logger instance. -// - config: client configuration. -func NewClient(log *logging.Logger, config ClientConfig) (*Client, error) { - conn, err := net.Dial("tcp", config.ServerAddr) +// NewClientFromConfig creates a new client from a given proc config. +func NewClientFromConfig(conf appcommon.ProcConfig) (*Client, error) { + conn, err := net.Dial("tcp", conf.AppSrvAddr) if err != nil { return nil, fmt.Errorf("failed to dial to app server: %w", err) } - if _, err := conn.Write(config.ProcKey[:]); err != nil { - return nil, fmt.Errorf("failed to provide proc key to app server: %w", err) + if _, err := conn.Write(conf.ProcKey[:]); err != nil { + return nil, fmt.Errorf("failed to send proc key back to app server: %w", err) } rpcC := rpc.NewClient(conn) return &Client{ - log: log, - visorPK: config.VisorPK, - rpc: NewRPCClient(rpcC, config.ProcKey), - lm: idmanager.New(), - cm: idmanager.New(), + log: conf.Logger(), + conf: conf, + rpc: NewRPCClient(rpcC, conf.ProcKey), + lm: idmanager.New(), + cm: idmanager.New(), }, nil } +// Config returns the underlying proc config. +func (c *Client) Config() appcommon.ProcConfig { + return c.conf +} + +// Logger returns the underlying logger. +func (c *Client) Logger() *logging.MasterLogger { + return c.log +} + // Dial dials the remote visor using `remote`. func (c *Client) Dial(remote appnet.Addr) (net.Conn, error) { connID, localPort, err := c.rpc.Dial(remote) @@ -115,7 +78,7 @@ func (c *Client) Dial(remote appnet.Addr) (net.Conn, error) { rpc: c.rpc, local: appnet.Addr{ Net: remote.Net, - PubKey: c.visorPK, + PubKey: c.conf.VisorPK, Port: localPort, }, remote: remote, @@ -146,7 +109,7 @@ func (c *Client) Dial(remote appnet.Addr) (net.Conn, error) { func (c *Client) Listen(n appnet.Type, port routing.Port) (net.Listener, error) { local := appnet.Addr{ Net: n, - PubKey: c.visorPK, + PubKey: c.conf.VisorPK, Port: port, } diff --git a/pkg/app/listener.go b/pkg/app/listener.go index bdd3c4faf..404ce7b81 100644 --- a/pkg/app/listener.go +++ b/pkg/app/listener.go @@ -5,7 +5,7 @@ import ( "net" "sync" - "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" "github.com/SkycoinProject/skywire-mainnet/pkg/app/idmanager" @@ -14,7 +14,7 @@ import ( // Listener is a listener for app server connections. // Implements `net.Listener`. type Listener struct { - log *logging.Logger + log logrus.FieldLogger id uint16 rpc RPCClient addr appnet.Addr diff --git a/pkg/hypervisor/hypervisor.go b/pkg/hypervisor/hypervisor.go index a33aea253..b5426643f 100644 --- a/pkg/hypervisor/hypervisor.go +++ b/pkg/hypervisor/hypervisor.go @@ -14,6 +14,8 @@ import ( "sync" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/dmsgpty" @@ -23,7 +25,6 @@ import ( "github.com/go-chi/chi/middleware" "github.com/google/uuid" - "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" @@ -469,7 +470,7 @@ func (hv *Hypervisor) appLogsSince() http.HandlerFunc { } httputil.WriteJSON(w, r, http.StatusOK, &LogsRes{ - LastLogTimestamp: app.TimestampFromLog(logs[len(logs)-1]), + LastLogTimestamp: appcommon.TimestampFromLog(logs[len(logs)-1]), Logs: logs, }) }) diff --git a/pkg/visor/config.go b/pkg/visor/config.go index 26d5211cb..6738907c8 100644 --- a/pkg/visor/config.go +++ b/pkg/visor/config.go @@ -47,9 +47,9 @@ var ( // Config defines configuration parameters for Visor. type Config struct { - Path *string `json:"-"` - log *logging.Logger - flushMu sync.Mutex + Path *string `json:"-"` + log *logging.Logger `json:"-"` + flushMu sync.Mutex `json:"-"` Version string `json:"version"` KeyPair *KeyPair `json:"key_pair"` @@ -59,24 +59,21 @@ type Config struct { Transport *TransportConfig `json:"transport"` Routing *RoutingConfig `json:"routing"` UptimeTracker *UptimeTrackerConfig `json:"uptime_tracker,omitempty"` - AppDiscovery *AppDiscConfig `json:"app_discovery,omitempty"` - Apps []AppConfig `json:"apps"` + AppDiscovery *AppDiscConfig `json:"app_discovery,omitempty"` + Apps []AppConfig `json:"apps"` + AppServerAddr string `json:"app_server_addr"` + AppsPath string `json:"apps_path"` + LocalPath string `json:"local_path"` TrustedVisors []cipher.PubKey `json:"trusted_visors"` Hypervisors []HypervisorConfig `json:"hypervisors"` - AppsPath string `json:"apps_path"` - LocalPath string `json:"local_path"` - - LogLevel string `json:"log_level"` - ShutdownTimeout Duration `json:"shutdown_timeout,omitempty"` // time value, examples: 10s, 1m, etc - Interfaces *InterfaceConfig `json:"interfaces"` - AppServerAddr string `json:"app_server_addr"` - - RestartCheckDelay string `json:"restart_check_delay,omitempty"` + LogLevel string `json:"log_level"` + ShutdownTimeout Duration `json:"shutdown_timeout,omitempty"` // time value, examples: 10s, 1m, etc + RestartCheckDelay string `json:"restart_check_delay,omitempty"` } // Flush flushes config to file. diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index e68264a85..0e17e1e89 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -7,14 +7,14 @@ import ( "net/http" "net/rpc" "os" - "path/filepath" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/dmsg/cipher" "github.com/google/uuid" "github.com/sirupsen/logrus" - "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/transport" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" @@ -120,7 +120,7 @@ type AppLogsRequest struct { func (r *RPC) LogsSince(in *AppLogsRequest, out *[]string) (err error) { defer rpcutil.LogCall(r.log, "LogsSince", in)(out, &err) - ls, err := app.NewLogStore(filepath.Join(r.visor.dir(), in.AppName), in.AppName, "bbolt") + ls, err := appcommon.NewLogStore(r.visor.appLogLoc(in.AppName), in.AppName, "bbolt") if err != nil { return err } diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index df72df097..36616eb5b 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -10,11 +10,12 @@ import ( "sync" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/google/uuid" - "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/snet/snettest" @@ -279,7 +280,7 @@ type mockRPCClient struct { s *Summary tpTypes []string rt routing.Table - appls app.LogStore + logS appcommon.LogStore sync.RWMutex } @@ -483,9 +484,9 @@ func (mc *mockRPCClient) SetSocksClientPK(cipher.PubKey) error { }) } -// LogsSince implements RPCClient. Manually set (*mockRPPClient).appls before calling this function +// LogsSince implements RPCClient. Manually set (*mockRPPClient).logS before calling this function func (mc *mockRPCClient) LogsSince(timestamp time.Time, _ string) ([]string, error) { - return mc.appls.LogsSince(timestamp) + return mc.logS.LogsSince(timestamp) } // TransportTypes implements RPCClient. diff --git a/pkg/visor/rpc_test.go b/pkg/visor/rpc_test.go index ea24630ab..9e7c03d29 100644 --- a/pkg/visor/rpc_test.go +++ b/pkg/visor/rpc_test.go @@ -179,7 +179,7 @@ func TestStartStopApp(t *testing.T) { AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: visorCfg.Keys().PubKey.Hex(), RoutingPort: apps["foo"].Port, - WorkDir: filepath.Join("", app), + ProcWorkDir: filepath.Join("", app), } appArgs1 := append([]string{filepath.Join(visor.dir(), app)}, apps["foo"].Args...) diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 9f9cde81d..6931535ff 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -133,13 +133,13 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con STCP: cfg.STCP, }) if err := visor.n.Init(ctx); err != nil { - return nil, fmt.Errorf("failed to init network: %v", err) + return nil, fmt.Errorf("failed to init network: %w", err) } if cfg.DmsgPty != nil { pty, err := cfg.DmsgPtyHost(visor.n.Dmsg()) if err != nil { - return nil, fmt.Errorf("failed to setup pty: %v", err) + return nil, fmt.Errorf("failed to setup pty: %w", err) } visor.pty = pty } else { @@ -148,12 +148,12 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con trDiscovery, err := cfg.TransportDiscovery() if err != nil { - return nil, fmt.Errorf("invalid transport discovery config: %s", err) + return nil, fmt.Errorf("invalid transport discovery config: %w", err) } logStore, err := cfg.TransportLogStore() if err != nil { - return nil, fmt.Errorf("invalid TransportLogStore: %s", err) + return nil, fmt.Errorf("invalid TransportLogStore: %w", err) } tmConfig := &transport.ManagerConfig{ @@ -166,7 +166,7 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con visor.tm, err = transport.NewManager(visor.n, tmConfig) if err != nil { - return nil, fmt.Errorf("transport manager: %s", err) + return nil, fmt.Errorf("transport manager: %w", err) } rConfig := &router.Config{ @@ -180,23 +180,26 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con r, err := router.New(visor.n, rConfig) if err != nil { - return nil, fmt.Errorf("failed to setup router: %v", err) + return nil, fmt.Errorf("failed to setup router: %w", err) } visor.router = r visor.appsConf, err = cfg.AppsConfig() if err != nil { - return nil, fmt.Errorf("invalid AppsConfig: %s", err) + return nil, fmt.Errorf("invalid AppsConfig: %w", err) } visor.appsPath, err = cfg.AppsDir() if err != nil { - return nil, fmt.Errorf("invalid AppsPath: %s", err) + return nil, fmt.Errorf("invalid AppsPath: %w", err) } visor.localPath, err = cfg.LocalDir() if err != nil { - return nil, fmt.Errorf("invalid LocalPath: %s", err) + return nil, fmt.Errorf("invalid LocalPath: %w", err) + } + if err := pathutil.EnsureDir(visor.localPath); err != nil { + return nil, fmt.Errorf("failed to ensure 'local_path': %w", err) } if lvl, err := logging.LevelFromString(cfg.LogLevel); err == nil { @@ -206,7 +209,7 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con if cfg.Interfaces != nil { l, err := net.Listen("tcp", cfg.Interfaces.RPCAddress) if err != nil { - return nil, fmt.Errorf("failed to setup RPC listener: %s", err) + return nil, fmt.Errorf("failed to setup RPC listener: %w", err) } visor.cliLis = l @@ -248,10 +251,6 @@ func (visor *Visor) Start() error { visor.startedAt = time.Now() - if err := pathutil.EnsureDir(visor.dir()); err != nil { - return err - } - if err := visor.startApps(); err != nil { return err } @@ -385,13 +384,8 @@ func (visor *Visor) startRPC(ctx context.Context) { } } -// TODO: We should have this re-defined. -func (visor *Visor) dir() string { - return pathutil.VisorDir(visor.conf.Keys().PubKey.String()) -} - -func (visor *Visor) pidFile() (*os.File, error) { - f, err := os.OpenFile(filepath.Join(visor.dir(), "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) +func (visor *Visor) appsPIDFile() (*os.File, error) { + f, err := os.OpenFile(filepath.Join(visor.appsPath, "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) if err != nil { return nil, err } @@ -402,7 +396,7 @@ func (visor *Visor) pidFile() (*os.File, error) { func (visor *Visor) closePreviousApps() error { visor.logger.Info("killing previously ran apps if any...") - pids, err := visor.pidFile() + pids, err := visor.appsPIDFile() if err != nil { return err } @@ -547,6 +541,9 @@ func (visor *Visor) StartApp(appName string) error { return ErrUnknownApp } +func (visor *Visor) appLogLoc(appName string) string { + return filepath.Join(visor.appsPath, appName+"_log.db") +} // SpawnApp configures and starts new App. func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err error) { @@ -564,33 +561,35 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er appCfg := appcommon.ProcConfig{ AppName: config.App, AppSrvAddr: visor.conf.AppServerAddr, - VisorPK: visor.conf.Keys().PubKey.Hex(), + ProcKey: appcommon.RandProcKey(), + ProcArgs: config.Args, + ProcWorkDir: filepath.Join(visor.localPath, config.App), + VisorPK: visor.conf.Keys().PubKey, RoutingPort: config.Port, - BinaryDir: visor.appsPath, - WorkDir: filepath.Join(visor.localPath, config.App), + BinaryLoc: filepath.Join(visor.appsPath, config.App), + LogDBLoc: visor.appLogLoc(config.App), } - if _, err := ensureDir(appCfg.WorkDir); err != nil { + if _, err := ensureDir(appCfg.ProcWorkDir); err != nil { return err } // TODO: make PackageLogger return *RuleEntry. FieldLogger doesn't expose Writer. - logger := visor.logger.WithField("_module", config.App).Writer() - errLogger := visor.logger.WithField("_module", config.App+"[ERROR]").Writer() + stdout := visor.logger.WithField("_module", config.App).Writer() + stderr := visor.logger.WithField("_module", config.App+"[ERROR]").Writer() defer func() { - if logErr := logger.Close(); err == nil && logErr != nil { + if logErr := stdout.Close(); err == nil && logErr != nil { err = logErr } - - if logErr := errLogger.Close(); err == nil && logErr != nil { + if logErr := stderr.Close(); err == nil && logErr != nil { err = logErr } }() appLogger := logging.MustGetLogger(fmt.Sprintf("app_%s", config.App)) - pid, err := visor.procM.Start(ctx, appLogger, appCfg, logger, errLogger) + pid, err := visor.procM.Start(ctx, appLogger, appCfg, stdout, stderr) if err != nil { return fmt.Errorf("error running app %s: %v", config.App, err) } @@ -614,7 +613,7 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er } func (visor *Visor) persistPID(name string, pid appcommon.ProcID) error { - pidF, err := visor.pidFile() + pidF, err := visor.appsPIDFile() if err != nil { return err } diff --git a/pkg/visor/visor_test.go b/pkg/visor/visor_test.go index 3b0daf636..08e3de691 100644 --- a/pkg/visor/visor_test.go +++ b/pkg/visor/visor_test.go @@ -124,7 +124,7 @@ func TestVisorStartClose(t *testing.T) { AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: visorCfg.Keys().PubKey.Hex(), RoutingPort: apps["skychat"].Port, - WorkDir: filepath.Join("", apps["skychat"].App), + ProcWorkDir: filepath.Join("", apps["skychat"].App), } appArgs1 := append([]string{filepath.Join(visor.dir(), apps["skychat"].App)}, apps["skychat"].Args...) appPID1 := appcommon.ProcID(10) @@ -203,7 +203,7 @@ func TestVisorSpawnApp(t *testing.T) { AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: visorCfg.Keys().PubKey.Hex(), RoutingPort: app.Port, - WorkDir: filepath.Join("", app.App), + ProcWorkDir: filepath.Join("", app.App), } appArgs := append([]string{filepath.Join(visor.dir(), app.App)}, app.Args...) @@ -263,7 +263,7 @@ func TestVisorSpawnAppValidations(t *testing.T) { AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: c.Keys().PubKey.Hex(), RoutingPort: app.Port, - WorkDir: filepath.Join("", app.App), + ProcWorkDir: filepath.Join("", app.App), } appArgs := append([]string{filepath.Join(visor.dir(), app.App)}, app.Args...) @@ -303,7 +303,7 @@ func TestVisorSpawnAppValidations(t *testing.T) { AppSrvAddr: appcommon.DefaultAppSrvAddr, VisorPK: c.Keys().PubKey.Hex(), RoutingPort: app.Port, - WorkDir: filepath.Join("", app.App), + ProcWorkDir: filepath.Join("", app.App), } appArgs := append([]string{filepath.Join(visor.dir(), app.App)}, app.Args...) From 472293558b183e4844eb5cc98f1a1a30d511a22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 5 May 2020 13:07:21 +1200 Subject: [PATCH 04/17] Various fixes and simplifications. --- pkg/app/appserver/proc.go | 58 +++++++++++++++++-------------- pkg/app/appserver/proc_manager.go | 34 ++++++++++++------ pkg/visor/visor.go | 10 ++---- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 378db85ce..eb6752a38 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -1,7 +1,6 @@ package appserver import ( - "context" "errors" "io" "net" @@ -35,7 +34,8 @@ type Proc struct { waitMx sync.Mutex waitErr error - connCh chan net.Conn + connCh chan net.Conn + connOnce sync.Once } // NewProc constructs `Proc`. @@ -57,30 +57,31 @@ func NewProc(log *logging.Logger, conf appcommon.ProcConfig, disc appdisc.Update // InjectConn introduces the connection to the Proc after it is started. func (p *Proc) InjectConn(conn net.Conn) bool { - select { - case p.connCh <- conn: - return true - default: - return false - } + ok := false + p.connOnce.Do(func() { + p.connCh <- conn + close(p.connCh) + ok = true + }) + return ok } -func (p *Proc) awaitConn(ctx context.Context) error { - select { - case conn := <-p.connCh: - rpcS := rpc.NewServer() - if err := rpcS.RegisterName(p.conf.ProcKey.String(), NewRPCGateway(p.log)); err != nil { - panic(err) - } - go rpcS.ServeConn(conn) - return nil - case <-ctx.Done(): - return ctx.Err() +func (p *Proc) awaitConn() bool { + conn, ok := <-p.connCh + if !ok { + return false + } + rpcS := rpc.NewServer() + if err := rpcS.RegisterName(p.conf.ProcKey.String(), NewRPCGateway(p.log)); err != nil { + panic(err) } + go rpcS.ServeConn(conn) + p.log.Info("Associated and serving proc conn.") + return true } // Start starts the application. -func (p *Proc) Start(ctx context.Context) error { +func (p *Proc) Start() error { if !atomic.CompareAndSwapInt32(&p.isRunning, 0, 1) { return errProcAlreadyRunning } @@ -92,13 +93,13 @@ func (p *Proc) Start(ctx context.Context) error { p.waitMx.Unlock() return err } - if err := p.awaitConn(ctx); err != nil { - _ = p.cmd.Process.Kill() //nolint:errcheck - p.waitMx.Unlock() - return err - } go func() { + if ok := p.awaitConn(); !ok { + _ = p.cmd.Process.Kill() //nolint:errcheck + p.waitMx.Unlock() + return + } p.disc.Start() p.waitErr = p.cmd.Wait() p.disc.Stop() @@ -121,7 +122,12 @@ func (p *Proc) Stop() error { // the lock will be acquired as soon as the cmd finishes its work p.waitMx.Lock() - defer p.waitMx.Unlock() + defer func() { + p.waitMx.Unlock() + p.connOnce.Do(func() { + close(p.connCh) + }) + }() return nil } diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index 226ab6e91..effa419d6 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -1,7 +1,6 @@ package appserver import ( - "context" "errors" "fmt" "io" @@ -35,7 +34,7 @@ var ( // ProcManager allows to manage skywire applications. type ProcManager interface { io.Closer - Start(ctx context.Context, log *logging.Logger, conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) + Start(conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) Exists(name string) bool Stop(name string) error Wait(name string) error @@ -120,35 +119,44 @@ func (m *procManager) serve() { } func (m *procManager) handleConn(conn net.Conn) bool { + log := m.log.WithField("remote", conn.RemoteAddr()) + log.Debug("Accepting proc conn...") + // Read in and check key. var key appcommon.ProcKey if n, err := io.ReadFull(conn, key[:]); err != nil { - m.log. - WithError(err). + log.WithError(err). WithField("n", n). - WithField("remote_addr", conn.RemoteAddr()). Warn("Failed to read proc key.") return false } + log = log.WithField("proc_key", key.String()) + log.Debug("Read proc key.") + // Push conn to Proc. m.mx.RLock() proc, ok := m.procsByKey[key] m.mx.RUnlock() if !ok { + log.Error("Failed to find proc of given key.") return false } if ok := proc.InjectConn(conn); !ok { + log.Error("Failed to associate conn with proc.") return false } + log.Info("Accepted proc conn.") return true } // Start starts the application according to its config and additional args. -func (m *procManager) Start(ctx context.Context, log *logging.Logger, conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) { +func (m *procManager) Start(conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) { m.mx.Lock() defer m.mx.Unlock() + log := logging.MustGetLogger("proc:" + conf.AppName + ":" + conf.ProcKey.String()) + // isDone should be called within the protection of a mutex. // Otherwise we may be able to start an app after calling Close. if isDone(m.done) { @@ -159,7 +167,14 @@ func (m *procManager) Start(ctx context.Context, log *logging.Logger, conf appco return 0, ErrAppAlreadyStarted } - conf.EnsureKey() + // Ensure proc key is unique (just in case - this is probably not necessary). + for { + if _, ok := m.procsByKey[conf.ProcKey]; ok { + conf.EnsureKey() + continue + } + break + } disc, ok := m.discF.Updater(conf) if !ok { @@ -171,10 +186,7 @@ func (m *procManager) Start(ctx context.Context, log *logging.Logger, conf appco m.procs[conf.AppName] = proc m.procsByKey[conf.ProcKey] = proc - ctx, cancel := context.WithTimeout(ctx, ProcStartTimeout) - defer cancel() - - if err := proc.Start(ctx); err != nil { + if err := proc.Start(); err != nil { return 0, err } diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 6931535ff..005725654 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -385,7 +385,7 @@ func (visor *Visor) startRPC(ctx context.Context) { } func (visor *Visor) appsPIDFile() (*os.File, error) { - f, err := os.OpenFile(filepath.Join(visor.appsPath, "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) + f, err := os.OpenFile(filepath.Join(visor.localPath, "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) if err != nil { return nil, err } @@ -542,13 +542,11 @@ func (visor *Visor) StartApp(appName string) error { return ErrUnknownApp } func (visor *Visor) appLogLoc(appName string) string { - return filepath.Join(visor.appsPath, appName+"_log.db") + return filepath.Join(visor.localPath, appName+"_log.db") } // SpawnApp configures and starts new App. func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err error) { - ctx := context.Background() - visor.logger. WithField("app_name", config.App). WithField("args", config.Args). @@ -587,9 +585,7 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er } }() - appLogger := logging.MustGetLogger(fmt.Sprintf("app_%s", config.App)) - - pid, err := visor.procM.Start(ctx, appLogger, appCfg, stdout, stderr) + pid, err := visor.procM.Start(appCfg, stdout, stderr) if err != nil { return fmt.Errorf("error running app %s: %v", config.App, err) } From a0633cdbd27929ef3b6a076ac7287528ff8b7b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 5 May 2020 19:13:51 +1200 Subject: [PATCH 05/17] App logging improvements. --- cmd/apps/helloworld/helloworld.go | 10 +++++++-- cmd/apps/skychat/chat.go | 14 +++++++----- cmd/apps/skysocks-client/skysocks-client.go | 11 +++++++-- cmd/apps/skysocks/skysocks.go | 11 +++++++-- internal/skysocks/client.go | 4 +++- internal/skysocks/server.go | 7 +++--- pkg/app/appcommon/log.go | 25 +-------------------- pkg/app/appserver/proc.go | 15 ++++++++----- pkg/app/appserver/proc_manager.go | 6 ++--- pkg/app/client.go | 22 +++++++++--------- pkg/visor/visor.go | 15 +------------ 11 files changed, 67 insertions(+), 73 deletions(-) diff --git a/cmd/apps/helloworld/helloworld.go b/cmd/apps/helloworld/helloworld.go index 754dca162..087b49b19 100644 --- a/cmd/apps/helloworld/helloworld.go +++ b/cmd/apps/helloworld/helloworld.go @@ -6,6 +6,8 @@ package main import ( "os" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skywire-mainnet/pkg/app" @@ -18,12 +20,16 @@ const ( netType = appnet.TypeSkynet ) +var log = logrus.New() + +func init() { + log.SetFormatter(&logrus.JSONFormatter{}) +} + func main() { appC := app.NewClient() defer appC.Close() - log := appC.Logger() - if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { log.Printf("Failed to output build info: %v", err) } diff --git a/cmd/apps/skychat/chat.go b/cmd/apps/skychat/chat.go index c414aeb42..9524e87ad 100644 --- a/cmd/apps/skychat/chat.go +++ b/cmd/apps/skychat/chat.go @@ -11,11 +11,13 @@ import ( "fmt" "net" "net/http" + "os" "sync" "time" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/skywire-mainnet/internal/netutil" "github.com/SkycoinProject/skywire-mainnet/pkg/app" @@ -33,20 +35,22 @@ var addr = flag.String("addr", ":8001", "address to bind") var r = netutil.NewRetrier(50*time.Millisecond, 5, 2) var ( + log = logrus.New() appC *app.Client clientCh chan string conns map[cipher.PubKey]net.Conn // Chat connections connsMu sync.Mutex - log *logging.MasterLogger ) +func init() { + log.SetFormatter(&logrus.JSONFormatter{}) +} + func main() { appC = app.NewClient() defer appC.Close() - log = appC.Logger() - - if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { + if _, err := buildinfo.Get().WriteTo(os.Stdout); err != nil { log.Printf("Failed to output build info: %v", err) } diff --git a/cmd/apps/skysocks-client/skysocks-client.go b/cmd/apps/skysocks-client/skysocks-client.go index 93004fbf4..65d2a168b 100644 --- a/cmd/apps/skysocks-client/skysocks-client.go +++ b/cmd/apps/skysocks-client/skysocks-client.go @@ -9,6 +9,8 @@ import ( "net" "time" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skywire-mainnet/internal/netutil" @@ -26,6 +28,12 @@ const ( socksPort = routing.Port(3) ) +var log = logrus.New() + +func init() { + log.SetFormatter(&logrus.JSONFormatter{}) +} + var r = netutil.NewRetrier(time.Second, 0, 1) func dialServer(appCl *app.Client, pk cipher.PubKey) (net.Conn, error) { @@ -50,8 +58,7 @@ func main() { appC := app.NewClient() defer appC.Close() - log := appC.Logger() - skysocks.Log = log.PackageLogger("skysocks") + skysocks.Log = log if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { log.Printf("Failed to output build info: %v", err) diff --git a/cmd/apps/skysocks/skysocks.go b/cmd/apps/skysocks/skysocks.go index 90a053678..1285e8609 100644 --- a/cmd/apps/skysocks/skysocks.go +++ b/cmd/apps/skysocks/skysocks.go @@ -8,6 +8,8 @@ import ( "os" "os/signal" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/skywire-mainnet/internal/skysocks" "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" @@ -21,12 +23,17 @@ const ( port routing.Port = 3 ) +var log = logrus.New() + +func init() { + log.SetFormatter(&logrus.JSONFormatter{}) +} + func main() { appC := app.NewClient() defer appC.Close() - log := appC.Logger() - skysocks.Log = log.PackageLogger(appName) + skysocks.Log = log if _, err := buildinfo.Get().WriteTo(log.Writer()); err != nil { log.Printf("Failed to output build info: %v", err) diff --git a/internal/skysocks/client.go b/internal/skysocks/client.go index ec042c6d9..b13fcaf49 100644 --- a/internal/skysocks/client.go +++ b/internal/skysocks/client.go @@ -7,6 +7,8 @@ import ( "sync" "time" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/yamux" @@ -14,7 +16,7 @@ import ( ) // Log is skysocks package level logger, it can be replaced with a different one from outside the package -var Log = logging.MustGetLogger("skysocks") // nolint: gochecknoglobals +var Log logrus.FieldLogger = logging.MustGetLogger("skysocks") // nolint: gochecknoglobals // Client implement multiplexing proxy client using yamux. type Client struct { diff --git a/internal/skysocks/server.go b/internal/skysocks/server.go index 8a90c1d27..f5bca9b46 100644 --- a/internal/skysocks/server.go +++ b/internal/skysocks/server.go @@ -5,7 +5,8 @@ import ( "net" "sync/atomic" - "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/yamux" "github.com/armon/go-socks5" ) @@ -14,12 +15,12 @@ import ( type Server struct { socks *socks5.Server listener net.Listener - log *logging.MasterLogger + log logrus.FieldLogger closed uint32 } // NewServer constructs a new Server. -func NewServer(passcode string, l *logging.MasterLogger) (*Server, error) { +func NewServer(passcode string, l logrus.FieldLogger) (*Server, error) { var credentials socks5.CredentialStore if passcode != "" { credentials = passcodeCredentials(passcode) diff --git a/pkg/app/appcommon/log.go b/pkg/app/appcommon/log.go index a7785af05..6748e6dcf 100644 --- a/pkg/app/appcommon/log.go +++ b/pkg/app/appcommon/log.go @@ -2,7 +2,6 @@ package appcommon import ( "io" - "time" "github.com/SkycoinProject/skycoin/src/util/logging" ) @@ -16,11 +15,8 @@ func NewLogger(dbPath string, appName string) *logging.MasterLogger { panic(err) } - l := newAppLogger() + l := logging.NewMasterLogger() l.SetOutput(io.MultiWriter(l.Out, db)) - - //os.Args = append([]string{os.Args[0]}, os.Args[2:]...) - return l } @@ -29,22 +25,3 @@ func NewLogger(dbPath string, appName string) *logging.MasterLogger { func TimestampFromLog(log string) string { return log[1:36] } - -func newPersistentLogger(path, appName string) (*logging.MasterLogger, LogStore, error) { - db, err := newBoltDB(path, appName) - if err != nil { - return nil, nil, err - } - - l := newAppLogger() - l.SetOutput(io.MultiWriter(l.Out, db)) - - return l, db, nil -} - -func newAppLogger() *logging.MasterLogger { - l := logging.NewMasterLogger() - l.Logger.Formatter.(*logging.TextFormatter).TimestampFormat = time.RFC3339Nano - - return l -} diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index eb6752a38..0d513113e 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -2,7 +2,7 @@ package appserver import ( "errors" - "io" + "fmt" "net" "net/rpc" "os" @@ -39,17 +39,22 @@ type Proc struct { } // NewProc constructs `Proc`. -func NewProc(log *logging.Logger, conf appcommon.ProcConfig, disc appdisc.Updater, stdout, stderr io.Writer) *Proc { +func NewProc(conf appcommon.ProcConfig, disc appdisc.Updater) *Proc { + + moduleName := fmt.Sprintf("proc:%s:%s", conf.AppName, conf.ProcKey) + cmd := exec.Command(conf.BinaryLoc, conf.ProcArgs...) // nolint:gosec cmd.Dir = conf.ProcWorkDir cmd.Env = append(os.Environ(), conf.Envs()...) - cmd.Stdout = stdout - cmd.Stderr = stderr + + log := conf.Logger() + cmd.Stdout = log.WithField("_module", moduleName).WithField("func", "(STDOUT)").Writer() + cmd.Stderr = log.WithField("_module", moduleName).WithField("func", "(STDERR)").Writer() return &Proc{ disc: disc, conf: conf, - log: log, + log: logging.MustGetLogger(moduleName), cmd: cmd, connCh: make(chan net.Conn, 1), } diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index effa419d6..6403512c7 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -34,7 +34,7 @@ var ( // ProcManager allows to manage skywire applications. type ProcManager interface { io.Closer - Start(conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) + Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) Exists(name string) bool Stop(name string) error Wait(name string) error @@ -151,7 +151,7 @@ func (m *procManager) handleConn(conn net.Conn) bool { } // Start starts the application according to its config and additional args. -func (m *procManager) Start(conf appcommon.ProcConfig, stdout, stderr io.Writer) (appcommon.ProcID, error) { +func (m *procManager) Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) { m.mx.Lock() defer m.mx.Unlock() @@ -182,7 +182,7 @@ func (m *procManager) Start(conf appcommon.ProcConfig, stdout, stderr io.Writer) Debug("No app discovery associated with app.") } - proc := NewProc(log, conf, disc, stdout, stderr) + proc := NewProc(conf, disc) m.procs[conf.AppName] = proc m.procsByKey[conf.ProcKey] = proc diff --git a/pkg/app/client.go b/pkg/app/client.go index afd583796..e4ce0b12c 100644 --- a/pkg/app/client.go +++ b/pkg/app/client.go @@ -6,7 +6,7 @@ import ( "net/rpc" "strings" - "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" @@ -16,7 +16,7 @@ import ( // Client is used by skywire apps. type Client struct { - log *logging.MasterLogger + log logrus.FieldLogger conf appcommon.ProcConfig rpc RPCClient lm *idmanager.Manager // contains listeners associated with their IDs @@ -25,11 +25,14 @@ type Client struct { // NewClient creates a new Client, panicking on any error. func NewClient() *Client { + log := logrus.New() + log.SetFormatter(&logrus.JSONFormatter{}) + conf, err := appcommon.ProcConfigFromEnv() if err != nil { - panic(fmt.Errorf("failed to obtain proc config: %w", err)) + log.WithError(err).Fatal("Failed to obtain proc config.") } - client, err := NewClientFromConfig(conf) + client, err := NewClientFromConfig(log, conf) if err != nil { conf.Logger().Panicf("app client: %v", err) } @@ -37,7 +40,7 @@ func NewClient() *Client { } // NewClientFromConfig creates a new client from a given proc config. -func NewClientFromConfig(conf appcommon.ProcConfig) (*Client, error) { +func NewClientFromConfig(log logrus.FieldLogger, conf appcommon.ProcConfig) (*Client, error) { conn, err := net.Dial("tcp", conf.AppSrvAddr) if err != nil { return nil, fmt.Errorf("failed to dial to app server: %w", err) @@ -48,7 +51,7 @@ func NewClientFromConfig(conf appcommon.ProcConfig) (*Client, error) { rpcC := rpc.NewClient(conn) return &Client{ - log: conf.Logger(), + log: log, conf: conf, rpc: NewRPCClient(rpcC, conf.ProcKey), lm: idmanager.New(), @@ -61,11 +64,6 @@ func (c *Client) Config() appcommon.ProcConfig { return c.conf } -// Logger returns the underlying logger. -func (c *Client) Logger() *logging.MasterLogger { - return c.log -} - // Dial dials the remote visor using `remote`. func (c *Client) Dial(remote appnet.Addr) (net.Conn, error) { connID, localPort, err := c.rpc.Dial(remote) @@ -92,7 +90,7 @@ func (c *Client) Dial(remote appnet.Addr) (net.Conn, error) { conn.freeConnMx.Unlock() if err := conn.Close(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - c.log.WithError(err).Error("Unexpected error while closing conn.") + //log.Printf("Received unexpected error when closing conn: %v", err) } return nil, err diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 005725654..50ec32fc0 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -572,20 +572,7 @@ func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err er return err } - // TODO: make PackageLogger return *RuleEntry. FieldLogger doesn't expose Writer. - stdout := visor.logger.WithField("_module", config.App).Writer() - stderr := visor.logger.WithField("_module", config.App+"[ERROR]").Writer() - - defer func() { - if logErr := stdout.Close(); err == nil && logErr != nil { - err = logErr - } - if logErr := stderr.Close(); err == nil && logErr != nil { - err = logErr - } - }() - - pid, err := visor.procM.Start(appCfg, stdout, stderr) + pid, err := visor.procM.Start(appCfg) if err != nil { return fmt.Errorf("error running app %s: %v", config.App, err) } From ff1dc835f1288448a4972471199eb82a9e9e98cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 5 May 2020 21:51:42 +1200 Subject: [PATCH 06/17] Fixed an issue where skywire port is not being deallocated after app exits. --- pkg/app/appserver/proc.go | 45 +++++++++++++++++++++++++----------- pkg/app/idmanager/manager.go | 22 ++++++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 0d513113e..e35adea7b 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -34,7 +34,9 @@ type Proc struct { waitMx sync.Mutex waitErr error - connCh chan net.Conn + rpcGW *RPCGateway + conn net.Conn + connCh chan struct{} // closes when conn is received. connOnce sync.Once } @@ -56,31 +58,37 @@ func NewProc(conf appcommon.ProcConfig, disc appdisc.Updater) *Proc { conf: conf, log: logging.MustGetLogger(moduleName), cmd: cmd, - connCh: make(chan net.Conn, 1), + connCh: make(chan struct{}, 1), } } // InjectConn introduces the connection to the Proc after it is started. +// It also prepares the RPC gateway. func (p *Proc) InjectConn(conn net.Conn) bool { ok := false + p.connOnce.Do(func() { - p.connCh <- conn - close(p.connCh) ok = true + p.conn = conn + p.rpcGW = NewRPCGateway(p.log) + + // Send signal. + p.connCh <- struct{}{} + close(p.connCh) }) + return ok } func (p *Proc) awaitConn() bool { - conn, ok := <-p.connCh - if !ok { + if _, ok := <-p.connCh; !ok { return false } rpcS := rpc.NewServer() - if err := rpcS.RegisterName(p.conf.ProcKey.String(), NewRPCGateway(p.log)); err != nil { + if err := rpcS.RegisterName(p.conf.ProcKey.String(), p.rpcGW); err != nil { panic(err) } - go rpcS.ServeConn(conn) + go rpcS.ServeConn(p.conn) p.log.Info("Associated and serving proc conn.") return true } @@ -91,7 +99,7 @@ func (p *Proc) Start() error { return errProcAlreadyRunning } - // acquire lock immediately + // Acquire lock immediately. p.waitMx.Lock() if err := p.cmd.Start(); err != nil { @@ -105,9 +113,22 @@ func (p *Proc) Start() error { p.waitMx.Unlock() return } + + // App discovery start/stop. p.disc.Start() + defer p.disc.Stop() + + // Wait for proc to exit. p.waitErr = p.cmd.Wait() - p.disc.Stop() + + // Close proc conn and associated listeners and connections. + if err := p.conn.Close(); err != nil { + p.log.WithError(err).Warn("Closing proc conn returned non-nil error.") + } + p.rpcGW.cm.CloseAll() + p.rpcGW.lm.CloseAll() + + // Unlock. p.waitMx.Unlock() }() @@ -129,9 +150,7 @@ func (p *Proc) Stop() error { p.waitMx.Lock() defer func() { p.waitMx.Unlock() - p.connOnce.Do(func() { - close(p.connCh) - }) + p.connOnce.Do(func() { close(p.connCh) }) }() return nil diff --git a/pkg/app/idmanager/manager.go b/pkg/app/idmanager/manager.go index 53bafc7d2..b004d3f04 100644 --- a/pkg/app/idmanager/manager.go +++ b/pkg/app/idmanager/manager.go @@ -3,6 +3,7 @@ package idmanager import ( "errors" "fmt" + "io" "sync" ) @@ -148,6 +149,27 @@ func (m *Manager) Len() int { return out } +func (m *Manager) CloseAll() { + wg := new(sync.WaitGroup) + + m.mx.Lock() + for _, v := range m.values { + c, ok := v.(io.Closer) + if !ok { + continue + } + wg.Add(1) + go func(c io.Closer) { + _ = c.Close() // nolint:errcheck + wg.Done() + }(c) + } + m.values = make(map[uint16]interface{}) + m.mx.Unlock() + + wg.Wait() +} + // constructFreeFunc constructs new func responsible for clearing // a slot with the specified `id`. func (m *Manager) constructFreeFunc(id uint16) func() bool { From 7ed51b4ae21628e996772f88fc57c678394b8f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 5 May 2020 23:22:48 +1200 Subject: [PATCH 07/17] Further app logging improvements. --- pkg/app/appcommon/log.go | 27 ----------- pkg/app/appcommon/log_store.go | 70 ++++++++++++++++++++--------- pkg/app/appcommon/log_store_test.go | 2 +- pkg/app/appcommon/log_test.go | 4 +- pkg/app/appcommon/proc_config.go | 6 --- pkg/app/appserver/proc.go | 28 +++++++----- pkg/app/appserver/proc_manager.go | 16 +++---- pkg/app/client.go | 2 +- pkg/visor/rpc.go | 10 ++--- pkg/visor/rpc_test.go | 8 ++-- pkg/visor/visor.go | 20 ++++----- 11 files changed, 97 insertions(+), 96 deletions(-) delete mode 100644 pkg/app/appcommon/log.go diff --git a/pkg/app/appcommon/log.go b/pkg/app/appcommon/log.go deleted file mode 100644 index 6748e6dcf..000000000 --- a/pkg/app/appcommon/log.go +++ /dev/null @@ -1,27 +0,0 @@ -package appcommon - -import ( - "io" - - "github.com/SkycoinProject/skycoin/src/util/logging" -) - -// NewLogger returns a logger which persists app logs. This logger should be passed down -// for use on any other function used by the app. It's configured from an additional app argument. -// It modifies os.Args stripping from it such value. Should be called before using os.Args inside the app -func NewLogger(dbPath string, appName string) *logging.MasterLogger { - db, err := newBoltDB(dbPath, appName) - if err != nil { - panic(err) - } - - l := logging.NewMasterLogger() - l.SetOutput(io.MultiWriter(l.Out, db)) - return l -} - -// TimestampFromLog is an utility function for retrieving the timestamp from a log. This function should be modified -// if the time layout is changed -func TimestampFromLog(log string) string { - return log[1:36] -} diff --git a/pkg/app/appcommon/log_store.go b/pkg/app/appcommon/log_store.go index 1a704598b..31f33bdf3 100644 --- a/pkg/app/appcommon/log_store.go +++ b/pkg/app/appcommon/log_store.go @@ -4,12 +4,38 @@ import ( "bytes" "fmt" "io" + "os" "strings" + "sync" "time" + "github.com/SkycoinProject/skycoin/src/util/logging" + "go.etcd.io/bbolt" ) +const timeLayout = time.RFC3339Nano + +// NewProcLogger returns a new proc logger. +func NewProcLogger(conf ProcConfig) (*logging.MasterLogger, LogStore) { + db, err := NewBBoltLogStore(conf.LogDBLoc, conf.AppName) + if err != nil { + panic(err) + } + + log := logging.NewMasterLogger() + log.Logger.Formatter.(*logging.TextFormatter).TimestampFormat = time.RFC3339Nano + log.SetOutput(io.MultiWriter(os.Stdout, db)) + + return log, db +} + +// TimestampFromLog is an utility function for retrieving the timestamp from a log. This function should be modified +// if the time layout is changed +func TimestampFromLog(log string) string { + return log[1 : 1+len(timeLayout)] +} + // LogStore stores logs from apps, for later consumption from the hypervisor type LogStore interface { // Write implements io.Writer @@ -24,22 +50,14 @@ type LogStore interface { LogsSince(t time.Time) ([]string, error) } -// NewLogStore returns a LogStore with path and app name of the given kind -func NewLogStore(path, appName, kind string) (LogStore, error) { - switch kind { - case "bbolt": - return newBoltDB(path, appName) - default: - return nil, fmt.Errorf("no LogStore of type %s", kind) - } -} - -type boltDBappLogs struct { +type bBoltLogStore struct { dbpath string bucket []byte + mx sync.RWMutex } -func newBoltDB(path, appName string) (_ LogStore, err error) { +// NewBBoltLogStore returns a bbolt implementation of an app log store. +func NewBBoltLogStore(path, appName string) (_ LogStore, err error) { db, err := bbolt.Open(path, 0600, nil) if err != nil { return nil, err @@ -63,13 +81,19 @@ func newBoltDB(path, appName string) (_ LogStore, err error) { return nil, err } - return &boltDBappLogs{path, b}, nil + return &bBoltLogStore{ + dbpath: path, + bucket: b, + }, nil } // Write implements io.Writer -func (l *boltDBappLogs) Write(p []byte) (n int, err error) { +func (l *bBoltLogStore) Write(p []byte) (n int, err error) { + l.mx.Lock() + defer l.mx.Unlock() + // ensure there is at least timestamp long bytes - if len(p) < 37 { + if len(p) < len(timeLayout)+2 { return 0, io.ErrShortBuffer } @@ -85,7 +109,7 @@ func (l *boltDBappLogs) Write(p []byte) (n int, err error) { }() // time in RFC3339Nano is between the bytes 1 and 36. This will change if other time layout is in use - t := p[1:36] + t := p[1 : 1+len(timeLayout)] err = db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(l.bucket) @@ -100,7 +124,10 @@ func (l *boltDBappLogs) Write(p []byte) (n int, err error) { } // Store implements LogStore -func (l *boltDBappLogs) Store(t time.Time, s string) (err error) { +func (l *bBoltLogStore) Store(t time.Time, s string) (err error) { + l.mx.Lock() + defer l.mx.Unlock() + db, err := bbolt.Open(l.dbpath, 0600, nil) if err != nil { return err @@ -111,7 +138,7 @@ func (l *boltDBappLogs) Store(t time.Time, s string) (err error) { err = cErr }() - parsedTime := []byte(t.Format(time.RFC3339Nano)) + parsedTime := []byte(t.Format(timeLayout)) return db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(l.bucket) @@ -120,7 +147,10 @@ func (l *boltDBappLogs) Store(t time.Time, s string) (err error) { } // LogSince implements LogStore -func (l *boltDBappLogs) LogsSince(t time.Time) (logs []string, err error) { +func (l *bBoltLogStore) LogsSince(t time.Time) (logs []string, err error) { + l.mx.RLock() + defer l.mx.RUnlock() + db, err := bbolt.Open(l.dbpath, 0600, nil) if err != nil { return nil, err @@ -135,7 +165,7 @@ func (l *boltDBappLogs) LogsSince(t time.Time) (logs []string, err error) { err = db.View(func(tx *bbolt.Tx) error { b := tx.Bucket(l.bucket) - parsedTime := []byte(t.Format(time.RFC3339Nano)) + parsedTime := []byte(t.Format(timeLayout)) c := b.Cursor() v := b.Get(parsedTime) diff --git a/pkg/app/appcommon/log_store_test.go b/pkg/app/appcommon/log_store_test.go index b727fa03c..af8bfbadd 100644 --- a/pkg/app/appcommon/log_store_test.go +++ b/pkg/app/appcommon/log_store_test.go @@ -16,7 +16,7 @@ func TestLogStore(t *testing.T) { defer os.Remove(p.Name()) // nolint - ls, err := newBoltDB(p.Name(), "foo") + ls, err := NewBBoltLogStore(p.Name(), "foo") require.NoError(t, err) t3, err := time.Parse(time.RFC3339, "2000-03-01T00:00:00Z") diff --git a/pkg/app/appcommon/log_test.go b/pkg/app/appcommon/log_test.go index b37713633..ab0829f07 100644 --- a/pkg/app/appcommon/log_test.go +++ b/pkg/app/appcommon/log_test.go @@ -21,13 +21,13 @@ func TestNewLogger(t *testing.T) { l, _, err := newPersistentLogger(p.Name(), appName) require.NoError(t, err) - dbl, err := newBoltDB(p.Name(), appName) + dbl, err := NewBBoltLogStore(p.Name(), appName) require.NoError(t, err) l.Info("bar") beginning := time.Unix(0, 0) - res, err := dbl.(*boltDBappLogs).LogsSince(beginning) + res, err := dbl.(*bBoltLogStore).LogsSince(beginning) require.NoError(t, err) require.Len(t, res, 1) require.Contains(t, res[0], "bar") diff --git a/pkg/app/appcommon/proc_config.go b/pkg/app/appcommon/proc_config.go index 3924624f9..84d643125 100644 --- a/pkg/app/appcommon/proc_config.go +++ b/pkg/app/appcommon/proc_config.go @@ -8,7 +8,6 @@ import ( "os" "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/google/uuid" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" @@ -124,8 +123,3 @@ func (c *ProcConfig) encodeJSON() []byte { } return b } - -// Logger returns the associated logger for the app. -func (c *ProcConfig) Logger() *logging.MasterLogger { - return NewLogger(c.LogDBLoc, c.AppName) -} diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index e35adea7b..d93744dc6 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -25,44 +25,52 @@ var ( // the running process itself and the RPC server for // app/visor communication. type Proc struct { - disc appdisc.Updater // App discovery client. + disc appdisc.Updater // app discovery client conf appcommon.ProcConfig log *logging.Logger + logDB appcommon.LogStore + cmd *exec.Cmd isRunning int32 waitMx sync.Mutex waitErr error - rpcGW *RPCGateway - conn net.Conn - connCh chan struct{} // closes when conn is received. - connOnce sync.Once + rpcGW *RPCGateway // gateway shared over 'conn' - introduced AFTER proc is started + conn net.Conn // connection to proc - introduced AFTER proc is started + connCh chan struct{} // push here when conn is received - protected by 'connOnce' + connOnce sync.Once // ensures we only push to 'connCh' once } // NewProc constructs `Proc`. func NewProc(conf appcommon.ProcConfig, disc appdisc.Updater) *Proc { - moduleName := fmt.Sprintf("proc:%s:%s", conf.AppName, conf.ProcKey) cmd := exec.Command(conf.BinaryLoc, conf.ProcArgs...) // nolint:gosec cmd.Dir = conf.ProcWorkDir cmd.Env = append(os.Environ(), conf.Envs()...) - log := conf.Logger() - cmd.Stdout = log.WithField("_module", moduleName).WithField("func", "(STDOUT)").Writer() - cmd.Stderr = log.WithField("_module", moduleName).WithField("func", "(STDERR)").Writer() + appLog, appLogDB := appcommon.NewProcLogger(conf) + cmd.Stdout = appLog.WithField("_module", moduleName).WithField("func", "(STDOUT)").Writer() + cmd.Stderr = appLog.WithField("_module", moduleName).WithField("func", "(STDERR)").Writer() return &Proc{ disc: disc, conf: conf, log: logging.MustGetLogger(moduleName), + logDB: appLogDB, cmd: cmd, connCh: make(chan struct{}, 1), } } +// Logs obtains the log store. +func (p *Proc) Logs() appcommon.LogStore { + return p.logDB +} + // InjectConn introduces the connection to the Proc after it is started. +// Only the first call will return true. // It also prepares the RPC gateway. func (p *Proc) InjectConn(conn net.Conn) bool { ok := false @@ -72,7 +80,7 @@ func (p *Proc) InjectConn(conn net.Conn) bool { p.conn = conn p.rpcGW = NewRPCGateway(p.log) - // Send signal. + // Send ready signal. p.connCh <- struct{}{} close(p.connCh) }) diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index 6403512c7..7fe8e6ea8 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -35,10 +35,10 @@ var ( type ProcManager interface { io.Closer Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) - Exists(name string) bool - Stop(name string) error - Wait(name string) error - Range(next func(name string, proc *Proc) bool) + ProcByName(appName string) (*Proc, bool) + Stop(appName string) error + Wait(appName string) error + Range(next func(appName string, proc *Proc) bool) } // procManager manages skywire applications. It implements `ProcManager`. @@ -193,14 +193,12 @@ func (m *procManager) Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) return appcommon.ProcID(proc.cmd.Process.Pid), nil } -// Exists check whether app exists in the manager instance. -func (m *procManager) Exists(name string) bool { +func (m *procManager) ProcByName(appName string) (*Proc, bool) { m.mx.RLock() defer m.mx.RUnlock() - _, ok := m.procs[name] - - return ok + proc, ok := m.procs[appName] + return proc, ok } // Stop stops the application. diff --git a/pkg/app/client.go b/pkg/app/client.go index e4ce0b12c..6b027c902 100644 --- a/pkg/app/client.go +++ b/pkg/app/client.go @@ -34,7 +34,7 @@ func NewClient() *Client { } client, err := NewClientFromConfig(log, conf) if err != nil { - conf.Logger().Panicf("app client: %v", err) + log.WithError(err).Panic("Failed to create app client.") } return client } diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index 0e17e1e89..15996f7fa 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -9,8 +9,6 @@ import ( "os" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/dmsg/cipher" "github.com/google/uuid" "github.com/sirupsen/logrus" @@ -120,12 +118,12 @@ type AppLogsRequest struct { func (r *RPC) LogsSince(in *AppLogsRequest, out *[]string) (err error) { defer rpcutil.LogCall(r.log, "LogsSince", in)(out, &err) - ls, err := appcommon.NewLogStore(r.visor.appLogLoc(in.AppName), in.AppName, "bbolt") - if err != nil { - return err + proc, ok := r.visor.procM.ProcByName(in.AppName) + if !ok { + return fmt.Errorf("proc of app name '%s' is not found", in.AppName) } - res, err := ls.LogsSince(in.TimeStamp) + res, err := proc.Logs().LogsSince(in.TimeStamp) if err != nil { return err } diff --git a/pkg/visor/rpc_test.go b/pkg/visor/rpc_test.go index 9e7c03d29..27a3fcad7 100644 --- a/pkg/visor/rpc_test.go +++ b/pkg/visor/rpc_test.go @@ -199,14 +199,14 @@ func TestStartStopApp(t *testing.T) { err = rpc.StartApp(&unknownApp, nil) require.Error(t, err) - assert.Equal(t, ErrUnknownApp, err) + assert.Equal(t, ErrAppProcNotRunning, err) require.NoError(t, rpc.StartApp(&app, nil)) time.Sleep(100 * time.Millisecond) err = rpc.StopApp(&unknownApp, nil) require.Error(t, err) - assert.Equal(t, ErrUnknownApp, err) + assert.Equal(t, ErrAppProcNotRunning, err) require.NoError(t, rpc.StopApp(&app, nil)) time.Sleep(100 * time.Millisecond) @@ -362,7 +362,7 @@ These tests have been commented out for the following reasons: // // err := gateway.SetAutoStart(&in1, &struct{}{}) // require.Error(t, err) -// assert.Equal(t, ErrUnknownApp, err) +// assert.Equal(t, ErrAppProcNotRunning, err) // // require.NoError(t, gateway.SetAutoStart(&in2, &struct{}{})) // assert.True(t, visor.appsConf[0].AutoStart) @@ -374,7 +374,7 @@ These tests have been commented out for the following reasons: // // err = client.SetAutoStart(in1.AppName, in1.AutoStart) // require.Error(t, err) -// assert.Equal(t, ErrUnknownApp.Error(), err.Error()) +// assert.Equal(t, ErrAppProcNotRunning.Error(), err.Error()) // // require.NoError(t, client.SetAutoStart(in2.AppName, in2.AutoStart)) // assert.True(t, visor.appsConf[0].AutoStart) diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 50ec32fc0..67bc8d9d2 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -50,8 +50,8 @@ const ( ) var ( - // ErrUnknownApp represents lookup error for App related calls. - ErrUnknownApp = errors.New("unknown app") + // ErrAppProcNotRunning represents lookup error for App related calls. + ErrAppProcNotRunning = errors.New("no process of given app is running") ) const ( @@ -495,7 +495,7 @@ func (visor *Visor) App(name string) (*AppState, bool) { return nil, false } state := &AppState{AppConfig: app, Status: AppStatusStopped} - if visor.procM.Exists(app.App) { + if _, ok := visor.procM.ProcByName(app.App); ok { state.Status = AppStatusRunning } return state, true @@ -509,7 +509,7 @@ func (visor *Visor) Apps() []*AppState { for _, app := range visor.appsConf { state := &AppState{AppConfig: app, Status: AppStatusStopped} - if visor.procM.Exists(app.App) { + if _, ok := visor.procM.ProcByName(app.App); ok { state.Status = AppStatusRunning } @@ -539,7 +539,7 @@ func (visor *Visor) StartApp(appName string) error { } } - return ErrUnknownApp + return ErrAppProcNotRunning } func (visor *Visor) appLogLoc(appName string) string { return filepath.Join(visor.localPath, appName+"_log.db") @@ -616,8 +616,8 @@ func (visor *Visor) persistPID(name string, pid appcommon.ProcID) error { // StopApp stops running App. func (visor *Visor) StopApp(appName string) error { - if !visor.procM.Exists(appName) { - return ErrUnknownApp + if _, ok := visor.procM.ProcByName(appName); !ok { + return ErrAppProcNotRunning } visor.logger.Infof("Stopping app %s and closing ports", appName) @@ -679,7 +679,7 @@ func (visor *Visor) UpdateAvailable() (*updater.Version, error) { func (visor *Visor) setAutoStart(appName string, autoStart bool) error { appConf, ok := visor.appsConf[appName] if !ok { - return ErrUnknownApp + return ErrAppProcNotRunning } appConf.AutoStart = autoStart @@ -702,7 +702,7 @@ func (visor *Visor) setSocksPassword(password string) error { return err } - if visor.procM.Exists(socksName) { + if _, ok := visor.procM.ProcByName(socksName); ok { visor.logger.Infof("Updated %v password, restarting it", socksName) return visor.RestartApp(socksName) } @@ -724,7 +724,7 @@ func (visor *Visor) setSocksClientPK(pk cipher.PubKey) error { return err } - if visor.procM.Exists(socksClientName) { + if _, ok := visor.procM.ProcByName(socksClientName); ok { visor.logger.Infof("Updated %v PK, restarting it", socksClientName) return visor.RestartApp(socksClientName) } From 2618bd6f57a0d3c910a64145f9ce9ffb9eb297d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 5 May 2020 23:42:30 +1200 Subject: [PATCH 08/17] Format. --- cmd/apps/helloworld/helloworld.go | 4 ---- cmd/apps/skychat/chat.go | 4 ---- cmd/apps/skysocks-client/skysocks-client.go | 4 ---- cmd/apps/skysocks/skysocks.go | 4 ---- 4 files changed, 16 deletions(-) diff --git a/cmd/apps/helloworld/helloworld.go b/cmd/apps/helloworld/helloworld.go index 087b49b19..6bde5742a 100644 --- a/cmd/apps/helloworld/helloworld.go +++ b/cmd/apps/helloworld/helloworld.go @@ -22,10 +22,6 @@ const ( var log = logrus.New() -func init() { - log.SetFormatter(&logrus.JSONFormatter{}) -} - func main() { appC := app.NewClient() defer appC.Close() diff --git a/cmd/apps/skychat/chat.go b/cmd/apps/skychat/chat.go index 9524e87ad..d902baf92 100644 --- a/cmd/apps/skychat/chat.go +++ b/cmd/apps/skychat/chat.go @@ -42,10 +42,6 @@ var ( connsMu sync.Mutex ) -func init() { - log.SetFormatter(&logrus.JSONFormatter{}) -} - func main() { appC = app.NewClient() defer appC.Close() diff --git a/cmd/apps/skysocks-client/skysocks-client.go b/cmd/apps/skysocks-client/skysocks-client.go index 65d2a168b..a228464c3 100644 --- a/cmd/apps/skysocks-client/skysocks-client.go +++ b/cmd/apps/skysocks-client/skysocks-client.go @@ -30,10 +30,6 @@ const ( var log = logrus.New() -func init() { - log.SetFormatter(&logrus.JSONFormatter{}) -} - var r = netutil.NewRetrier(time.Second, 0, 1) func dialServer(appCl *app.Client, pk cipher.PubKey) (net.Conn, error) { diff --git a/cmd/apps/skysocks/skysocks.go b/cmd/apps/skysocks/skysocks.go index 1285e8609..c6f14c07d 100644 --- a/cmd/apps/skysocks/skysocks.go +++ b/cmd/apps/skysocks/skysocks.go @@ -25,10 +25,6 @@ const ( var log = logrus.New() -func init() { - log.SetFormatter(&logrus.JSONFormatter{}) -} - func main() { appC := app.NewClient() defer appC.Close() From 2a8a1df7870cd8b120af561e10e248e1c0da02b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 5 May 2020 23:44:27 +1200 Subject: [PATCH 09/17] Use text logger by default for apps. --- pkg/app/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/app/client.go b/pkg/app/client.go index 6b027c902..27b4c3a01 100644 --- a/pkg/app/client.go +++ b/pkg/app/client.go @@ -26,7 +26,6 @@ type Client struct { // NewClient creates a new Client, panicking on any error. func NewClient() *Client { log := logrus.New() - log.SetFormatter(&logrus.JSONFormatter{}) conf, err := appcommon.ProcConfigFromEnv() if err != nil { From 14a4ddb8899b60d41a3c0613dfc33e5d77bd0976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Thu, 7 May 2020 00:24:13 +1200 Subject: [PATCH 10/17] Fix tests. --- cmd/apps/skysocks-client/skysocks-client.go | 1 - cmd/apps/skysocks/skysocks.go | 1 - pkg/app/appcommon/log_test.go | 34 ------ pkg/app/appcommon/proc_config.go | 15 +-- pkg/app/appserver/mock_proc_manager.go | 79 +++++++++----- pkg/app/appserver/proc_manager.go | 21 +++- pkg/app/appserver/proc_manager_test.go | 39 +++---- pkg/app/appserver/rpc_gateway.go | 8 +- pkg/app/appserver/server_test.go | 112 -------------------- pkg/app/client.go | 7 +- pkg/app/client_test.go | 110 ++++--------------- pkg/app/conn_test.go | 36 ++++--- pkg/app/idmanager/manager.go | 6 +- pkg/app/rpc_client_test.go | 75 +++++++------ pkg/visor/README.md | 72 ++++++------- pkg/visor/config.go | 6 +- pkg/visor/rpc_test.go | 29 ++--- pkg/visor/visor.go | 7 +- pkg/visor/visor_test.go | 107 ++++++++----------- 19 files changed, 276 insertions(+), 489 deletions(-) delete mode 100644 pkg/app/appcommon/log_test.go delete mode 100644 pkg/app/appserver/server_test.go diff --git a/cmd/apps/skysocks-client/skysocks-client.go b/cmd/apps/skysocks-client/skysocks-client.go index a228464c3..2e48c3d8c 100644 --- a/cmd/apps/skysocks-client/skysocks-client.go +++ b/cmd/apps/skysocks-client/skysocks-client.go @@ -23,7 +23,6 @@ import ( ) const ( - appName = "skysocks-client" netType = appnet.TypeSkynet socksPort = routing.Port(3) ) diff --git a/cmd/apps/skysocks/skysocks.go b/cmd/apps/skysocks/skysocks.go index c6f14c07d..c52af208d 100644 --- a/cmd/apps/skysocks/skysocks.go +++ b/cmd/apps/skysocks/skysocks.go @@ -18,7 +18,6 @@ import ( ) const ( - appName = "skysocks" netType = appnet.TypeSkynet port routing.Port = 3 ) diff --git a/pkg/app/appcommon/log_test.go b/pkg/app/appcommon/log_test.go deleted file mode 100644 index ab0829f07..000000000 --- a/pkg/app/appcommon/log_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package appcommon - -import ( - "io/ioutil" - "os" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -// TestNewLogger tests that after the new logger is created logs with it are persisted into storage -func TestNewLogger(t *testing.T) { - p, err := ioutil.TempFile("", "test-db") - require.NoError(t, err) - - defer os.Remove(p.Name()) // nolint - - appName := "foo" - - l, _, err := newPersistentLogger(p.Name(), appName) - require.NoError(t, err) - - dbl, err := NewBBoltLogStore(p.Name(), appName) - require.NoError(t, err) - - l.Info("bar") - - beginning := time.Unix(0, 0) - res, err := dbl.(*bBoltLogStore).LogsSince(beginning) - require.NoError(t, err) - require.Len(t, res, 1) - require.Contains(t, res[0], "bar") -} diff --git a/pkg/app/appcommon/proc_config.go b/pkg/app/appcommon/proc_config.go index 84d643125..83a816044 100644 --- a/pkg/app/appcommon/proc_config.go +++ b/pkg/app/appcommon/proc_config.go @@ -13,22 +13,17 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/routing" ) -// DefaultAppSrvAddr is the default address to run the app server at. -const DefaultAppSrvAddr = "localhost:5505" - const ( + // DefaultAppSrvAddr is the default address to run the app server at. + // TODO: Move to 'pkg/skyenv' + DefaultAppSrvAddr = "localhost:5505" + // EnvProcConfig is the env name which contains a JSON-encoded proc config. EnvProcConfig = "PROC_CONFIG" - - // EnvProcKey is a name for env arg containing skywire application key. - EnvProcKey = "PROC_KEY" - // EnvAppSrvAddr is a name for env arg containing app server address. - EnvAppSrvAddr = "APP_SERVER_ADDR" - // EnvVisorPK is a name for env arg containing public key of visor. - EnvVisorPK = "VISOR_PK" ) var ( + // ErrProcConfigEnvNotDefined occurs when an expected env is not defined. ErrProcConfigEnvNotDefined = fmt.Errorf("env '%s' is not defined", EnvProcConfig) ) diff --git a/pkg/app/appserver/mock_proc_manager.go b/pkg/app/appserver/mock_proc_manager.go index e9f2d206f..2c88d240e 100644 --- a/pkg/app/appserver/mock_proc_manager.go +++ b/pkg/app/appserver/mock_proc_manager.go @@ -3,15 +3,11 @@ package appserver import ( - context "context" + mock "github.com/stretchr/testify/mock" appcommon "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - io "io" - - logging "github.com/SkycoinProject/skycoin/src/util/logging" - - mock "github.com/stretchr/testify/mock" + net "net" ) // MockProcManager is an autogenerated mock type for the ProcManager type @@ -19,6 +15,22 @@ type MockProcManager struct { mock.Mock } +// Addr provides a mock function with given fields: +func (_m *MockProcManager) Addr() net.Addr { + ret := _m.Called() + + var r0 net.Addr + if rf, ok := ret.Get(0).(func() net.Addr); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(net.Addr) + } + } + + return r0 +} + // Close provides a mock function with given fields: func (_m *MockProcManager) Close() error { ret := _m.Called() @@ -33,18 +45,27 @@ func (_m *MockProcManager) Close() error { return r0 } -// Exists provides a mock function with given fields: name -func (_m *MockProcManager) Exists(name string) bool { - ret := _m.Called(name) +// ProcByName provides a mock function with given fields: appName +func (_m *MockProcManager) ProcByName(appName string) (*Proc, bool) { + ret := _m.Called(appName) - var r0 bool - if rf, ok := ret.Get(0).(func(string) bool); ok { - r0 = rf(name) + var r0 *Proc + if rf, ok := ret.Get(0).(func(string) *Proc); ok { + r0 = rf(appName) } else { - r0 = ret.Get(0).(bool) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*Proc) + } } - return r0 + var r1 bool + if rf, ok := ret.Get(1).(func(string) bool); ok { + r1 = rf(appName) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 } // Range provides a mock function with given fields: next @@ -52,20 +73,20 @@ func (_m *MockProcManager) Range(next func(string, *Proc) bool) { _m.Called(next) } -// Start provides a mock function with given fields: ctx, log, conf, stdout, stderr -func (_m *MockProcManager) Start(ctx context.Context, log *logging.Logger, conf appcommon.ProcConfig, stdout io.Writer, stderr io.Writer) (appcommon.ProcID, error) { - ret := _m.Called(ctx, log, conf, stdout, stderr) +// Start provides a mock function with given fields: conf +func (_m *MockProcManager) Start(conf appcommon.ProcConfig) (appcommon.ProcID, error) { + ret := _m.Called(conf) var r0 appcommon.ProcID - if rf, ok := ret.Get(0).(func(context.Context, *logging.Logger, appcommon.ProcConfig, io.Writer, io.Writer) appcommon.ProcID); ok { - r0 = rf(ctx, log, conf, stdout, stderr) + if rf, ok := ret.Get(0).(func(appcommon.ProcConfig) appcommon.ProcID); ok { + r0 = rf(conf) } else { r0 = ret.Get(0).(appcommon.ProcID) } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *logging.Logger, appcommon.ProcConfig, io.Writer, io.Writer) error); ok { - r1 = rf(ctx, log, conf, stdout, stderr) + if rf, ok := ret.Get(1).(func(appcommon.ProcConfig) error); ok { + r1 = rf(conf) } else { r1 = ret.Error(1) } @@ -73,13 +94,13 @@ func (_m *MockProcManager) Start(ctx context.Context, log *logging.Logger, conf return r0, r1 } -// Stop provides a mock function with given fields: name -func (_m *MockProcManager) Stop(name string) error { - ret := _m.Called(name) +// Stop provides a mock function with given fields: appName +func (_m *MockProcManager) Stop(appName string) error { + ret := _m.Called(appName) var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(name) + r0 = rf(appName) } else { r0 = ret.Error(0) } @@ -87,13 +108,13 @@ func (_m *MockProcManager) Stop(name string) error { return r0 } -// Wait provides a mock function with given fields: name -func (_m *MockProcManager) Wait(name string) error { - ret := _m.Called(name) +// Wait provides a mock function with given fields: appName +func (_m *MockProcManager) Wait(appName string) error { + ret := _m.Called(appName) var r0 error if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(name) + r0 = rf(appName) } else { r0 = ret.Error(0) } diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index 7fe8e6ea8..9778e424a 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -28,6 +28,7 @@ var ( ErrAppAlreadyStarted = errors.New("app already started") errNoSuchApp = errors.New("no such app") + // ErrClosed occurs when an action is called after proc manager is closed. ErrClosed = errors.New("proc manager is already closed") ) @@ -39,13 +40,13 @@ type ProcManager interface { Stop(appName string) error Wait(appName string) error Range(next func(appName string, proc *Proc) bool) + Addr() net.Addr } // procManager manages skywire applications. It implements `ProcManager`. type procManager struct { log *logging.Logger - addr string // listening address lis net.Listener conns map[string]net.Conn connsWG sync.WaitGroup @@ -54,20 +55,25 @@ type procManager struct { procs map[string]*Proc procsByKey map[appcommon.ProcKey]*Proc - mx sync.RWMutex - done chan struct{} - doneOnce sync.Once + mx sync.RWMutex + done chan struct{} } // NewProcManager constructs `ProcManager`. func NewProcManager(log *logging.Logger, discF *appdisc.Factory, addr string) (ProcManager, error) { + if log == nil { + log = logging.MustGetLogger("proc_manager") + } + if discF == nil { + discF = new(appdisc.Factory) + } + lis, err := net.Listen("tcp", addr) if err != nil { return nil, err } procM := &procManager{ - addr: addr, log: log, lis: lis, conns: make(map[string]net.Conn), @@ -264,6 +270,11 @@ func (m *procManager) stopAll() { m.procs = make(map[string]*Proc) } +// Addr returns the underlying listener's listening address. +func (m *procManager) Addr() net.Addr { + return m.lis.Addr() +} + // Close implements io.Closer func (m *procManager) Close() error { m.mx.Lock() diff --git a/pkg/app/appserver/proc_manager_test.go b/pkg/app/appserver/proc_manager_test.go index 21a273093..5fd360cf8 100644 --- a/pkg/app/appserver/proc_manager_test.go +++ b/pkg/app/appserver/proc_manager_test.go @@ -4,44 +4,39 @@ import ( "sort" "testing" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/stretchr/testify/require" - - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appdisc" ) -var appDisc = new(appdisc.Factory) - -func TestProcManager_Exists(t *testing.T) { - srv := New(nil, appcommon.DefaultAppSrvAddr) +func TestProcManager_ProcByName(t *testing.T) { + mI, err := NewProcManager(nil, nil, ":0") + require.NoError(t, err) - mIfc := NewProcManager(logging.MustGetLogger("proc_manager"), appDisc, srv) - m, ok := mIfc.(*procManager) + m, ok := mI.(*procManager) require.True(t, ok) appName := "app" - ok = m.Exists(appName) + _, ok = m.ProcByName(appName) require.False(t, ok) + m.mx.Lock() m.procs[appName] = nil + m.mx.Unlock() - ok = m.Exists(appName) + _, ok = m.ProcByName(appName) require.True(t, ok) } func TestProcManager_Range(t *testing.T) { - srv := New(nil, appcommon.DefaultAppSrvAddr) - - mIfc := NewProcManager(logging.MustGetLogger("proc_manager"), appDisc, srv) - m, ok := mIfc.(*procManager) + mI, err := NewProcManager(nil, nil, ":0") + require.NoError(t, err) + m, ok := mI.(*procManager) require.True(t, ok) - wantAppNames := []string{"app1", "app2", "app3"} + appNames := []string{"app1", "app2", "app3"} - for _, n := range wantAppNames { + for _, n := range appNames { m.procs[n] = nil } @@ -58,14 +53,14 @@ func TestProcManager_Range(t *testing.T) { m.Range(next) sort.Strings(gotAppNames) - require.Equal(t, gotAppNames, wantAppNames) + require.Equal(t, gotAppNames, appNames) } func TestProcManager_Pop(t *testing.T) { - srv := New(nil, appcommon.DefaultAppSrvAddr) + mI, err := NewProcManager(nil, nil, ":0") + require.NoError(t, err) - mIfc := NewProcManager(logging.MustGetLogger("proc_manager"), appDisc, srv) - m, ok := mIfc.(*procManager) + m, ok := mI.(*procManager) require.True(t, ok) appName := "app" diff --git a/pkg/app/appserver/rpc_gateway.go b/pkg/app/appserver/rpc_gateway.go index 815c5177e..402f40ba4 100644 --- a/pkg/app/appserver/rpc_gateway.go +++ b/pkg/app/appserver/rpc_gateway.go @@ -64,6 +64,9 @@ type RPCGateway struct { // NewRPCGateway constructs new server RPC interface. func NewRPCGateway(log *logging.Logger) *RPCGateway { + if log == nil { + log = logging.MustGetLogger("app_rpc_gateway") + } return &RPCGateway{ lm: idmanager.New(), cm: idmanager.New(), @@ -248,8 +251,9 @@ func (r *RPCGateway) Read(req *ReadReq, resp *ReadResp) error { resp.B = make([]byte, resp.N) copy(resp.B, buf[:resp.N]) } - - fmt.Printf("ERROR READING FROM APP CONN SERVER SIDE: %v\n", err) + if err != nil { + r.log.WithError(err).Warn("Received error when reading from server side.") + } resp.Err = ioErrToRPCIOErr(err) diff --git a/pkg/app/appserver/server_test.go b/pkg/app/appserver/server_test.go deleted file mode 100644 index ca1a3637e..000000000 --- a/pkg/app/appserver/server_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package appserver_test - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/SkycoinProject/dmsg" - "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/SkycoinProject/skywire-mainnet/pkg/app" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" -) - -const ( - sleepDelay = 500 * time.Millisecond -) - -func TestServer_ListenAndServe(t *testing.T) { - l := logging.MustGetLogger("app_server") - - s := appserver.New(l, appcommon.DefaultAppSrvAddr) - - appKey := appcommon.RandProcKey() - - require.NoError(t, s.Register(appKey)) - - visorPK, _ := cipher.GenerateKeyPair() - clientConfig := app.ClientConfig{ - VisorPK: visorPK, - ServerAddr: appcommon.DefaultAppSrvAddr, - ProcKey: appKey, - } - - errCh := make(chan error, 1) - - go func() { - err := s.ListenAndServe() - if err != nil { - fmt.Printf("ListenAndServe error: %v\n", err) - } - errCh <- err - }() - - time.Sleep(sleepDelay) - - dmsgLocal, dmsgRemote, remote := prepAddrs() - - var noErr error - - conn := &appcommon.MockConn{} - conn.On("LocalAddr").Return(dmsgLocal) - conn.On("RemoteAddr").Return(dmsgRemote) - conn.On("Close").Return(noErr) - - appnet.ClearNetworkers() - - n := &appnet.MockNetworker{} - - n.On("DialContext", mock.Anything, remote).Return(conn, noErr) - - require.NoError(t, appnet.AddNetworker(appnet.TypeDmsg, n)) - - cl, err := app.NewClient(logging.MustGetLogger("app_client"), clientConfig) - require.NoError(t, err) - - gotConn, err := cl.Dial(remote) - require.NoError(t, err) - require.NotNil(t, gotConn) - require.Equal(t, remote, gotConn.RemoteAddr()) - - require.NoError(t, s.Close()) - - err = <-errCh - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "use of closed network connection")) -} - -func prepAddrs() (dmsgLocal, dmsgRemote dmsg.Addr, remote appnet.Addr) { - localPK, _ := cipher.GenerateKeyPair() - remotePK, _ := cipher.GenerateKeyPair() - - const ( - localPort uint16 = 10 - remotePort uint16 = 11 - ) - - dmsgLocal = dmsg.Addr{ - PK: localPK, - Port: localPort, - } - - dmsgRemote = dmsg.Addr{ - PK: remotePK, - Port: remotePort, - } - - remote = appnet.Addr{ - Net: appnet.TypeDmsg, - PubKey: remotePK, - Port: routing.Port(remotePort), - } - - return -} diff --git a/pkg/app/client.go b/pkg/app/client.go index 27b4c3a01..0b9686177 100644 --- a/pkg/app/client.go +++ b/pkg/app/client.go @@ -47,12 +47,11 @@ func NewClientFromConfig(log logrus.FieldLogger, conf appcommon.ProcConfig) (*Cl if _, err := conn.Write(conf.ProcKey[:]); err != nil { return nil, fmt.Errorf("failed to send proc key back to app server: %w", err) } - rpcC := rpc.NewClient(conn) return &Client{ log: log, conf: conf, - rpc: NewRPCClient(rpcC, conf.ProcKey), + rpc: NewRPCClient(rpc.NewClient(conn), conf.ProcKey), lm: idmanager.New(), cm: idmanager.New(), }, nil @@ -89,7 +88,7 @@ func (c *Client) Dial(remote appnet.Addr) (net.Conn, error) { conn.freeConnMx.Unlock() if err := conn.Close(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { - //log.Printf("Received unexpected error when closing conn: %v", err) + c.log.WithError(err).Error("Received unexpected error when closing conn.") } return nil, err @@ -130,7 +129,7 @@ func (c *Client) Listen(n appnet.Type, port routing.Port) (net.Listener, error) listener.freeLisMx.Unlock() if err := listener.Close(); err != nil { - c.log.WithError(err).Error("error closing listener") + c.log.WithError(err).Error("Unexpected error while closing listener.") } return nil, err diff --git a/pkg/app/client_test.go b/pkg/app/client_test.go index 4bfc2356c..2e1a946aa 100644 --- a/pkg/app/client_test.go +++ b/pkg/app/client_test.go @@ -2,103 +2,19 @@ package app import ( "errors" - "os" "testing" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/stretchr/testify/require" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" "github.com/SkycoinProject/skywire-mainnet/pkg/app/idmanager" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" ) -func TestClientConfigFromEnv(t *testing.T) { - resetEnv := func(t *testing.T) { - err := os.Setenv(appcommon.EnvProcKey, "") - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvAppSrvAddr, "") - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvVisorPK, "") - require.NoError(t, err) - } - - t.Run("ok", func(t *testing.T) { - resetEnv(t) - - visorPK, _ := cipher.GenerateKeyPair() - - wantCfg := ClientConfig{ - VisorPK: visorPK, - ServerAddr: appcommon.DefaultAppSrvAddr, - ProcKey: "key", - } - - err := os.Setenv(appcommon.EnvProcKey, string(wantCfg.ProcKey)) - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvAppSrvAddr, wantCfg.ServerAddr) - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvVisorPK, wantCfg.VisorPK.Hex()) - require.NoError(t, err) - - gotCfg, err := ClientConfigFromEnv() - require.NoError(t, err) - require.Equal(t, wantCfg, gotCfg) - }) - - t.Run("no app key", func(t *testing.T) { - resetEnv(t) - - _, err := ClientConfigFromEnv() - require.Equal(t, err, ErrProcKeyNotProvided) - }) - - t.Run("no app server address", func(t *testing.T) { - resetEnv(t) - - err := os.Setenv(appcommon.EnvProcKey, "val") - require.NoError(t, err) - - _, err = ClientConfigFromEnv() - require.Equal(t, err, ErrServerAddrNotProvided) - }) - - t.Run("no visor PK", func(t *testing.T) { - resetEnv(t) - - err := os.Setenv(appcommon.EnvProcKey, "val") - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvAppSrvAddr, appcommon.DefaultAppSrvAddr) - require.NoError(t, err) - - _, err = ClientConfigFromEnv() - require.Equal(t, err, ErrVisorPKNotProvided) - }) - - t.Run("invalid visor PK", func(t *testing.T) { - resetEnv(t) - - err := os.Setenv(appcommon.EnvProcKey, "val") - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvAppSrvAddr, appcommon.DefaultAppSrvAddr) - require.NoError(t, err) - - err = os.Setenv(appcommon.EnvVisorPK, "val") - require.NoError(t, err) - - _, err = ClientConfigFromEnv() - require.Equal(t, err, ErrVisorPKInvalid) - }) -} - func TestClient_Dial(t *testing.T) { l := logging.MustGetLogger("app2_client") visorPK, _ := cipher.GenerateKeyPair() @@ -370,11 +286,23 @@ func TestClient_Close(t *testing.T) { } func prepClient(l *logging.Logger, visorPK cipher.PubKey, rpc RPCClient) *Client { + var procKey appcommon.ProcKey + copy(procKey[:], visorPK[:]) return &Client{ - log: l, - visorPK: visorPK, - rpc: rpc, - lm: idmanager.New(), - cm: idmanager.New(), + log: l, + conf: appcommon.ProcConfig{ + AppName: "", + AppSrvAddr: "", + ProcKey: procKey, + ProcArgs: nil, + ProcWorkDir: "", + VisorPK: visorPK, + RoutingPort: 0, + BinaryLoc: "", + LogDBLoc: "", + }, + rpc: rpc, + lm: idmanager.New(), + cm: idmanager.New(), } } diff --git a/pkg/app/conn_test.go b/pkg/app/conn_test.go index 5e95b6d42..ab7245138 100644 --- a/pkg/app/conn_test.go +++ b/pkg/app/conn_test.go @@ -221,13 +221,21 @@ func TestConn_TestConn(t *testing.T) { appKeys := snettest.GenKeyPairs(2) + var ( + procKey1 appcommon.ProcKey + procKey2 appcommon.ProcKey + ) + copy(procKey1[:], appKeys[0].PK[:]) + copy(procKey2[:], appKeys[1].PK[:]) + gateway1 := appserver.NewRPCGateway(logging.MustGetLogger("test_app_rpc_gateway1")) gateway2 := appserver.NewRPCGateway(logging.MustGetLogger("test_app_rpc_gateway2")) - err = rpcS.RegisterName(appKeys[0].PK.Hex(), gateway1) + + err = rpcS.RegisterName(procKey1.String(), gateway1) if err != nil { return nil, nil, nil, err } - err = rpcS.RegisterName(appKeys[1].PK.Hex(), gateway2) + err = rpcS.RegisterName(procKey2.String(), gateway2) if err != nil { return nil, nil, nil, err } @@ -240,11 +248,13 @@ func TestConn_TestConn(t *testing.T) { } cl1 := Client{ - log: logging.MustGetLogger("test_client_1"), - visorPK: keys[0].PK, - rpc: NewRPCClient(rpcCl1, appcommon.ProcKey(appKeys[0].PK.Hex())), - lm: idmanager.New(), - cm: idmanager.New(), + log: logging.MustGetLogger("test_client_1"), + conf: appcommon.ProcConfig{ + VisorPK: keys[0].PK, + }, + rpc: NewRPCClient(rpcCl1, procKey1), + lm: idmanager.New(), + cm: idmanager.New(), } rpcCl2, err := rpc.Dial(rpcL.Addr().Network(), rpcL.Addr().String()) @@ -253,11 +263,13 @@ func TestConn_TestConn(t *testing.T) { } cl2 := Client{ - log: logging.MustGetLogger("test_client_2"), - visorPK: keys[1].PK, - rpc: NewRPCClient(rpcCl2, appcommon.ProcKey(appKeys[1].PK.Hex())), - lm: idmanager.New(), - cm: idmanager.New(), + log: logging.MustGetLogger("test_client_2"), + conf: appcommon.ProcConfig{ + VisorPK: keys[1].PK, + }, + rpc: NewRPCClient(rpcCl2, procKey2), + lm: idmanager.New(), + cm: idmanager.New(), } c1, err := cl1.Dial(a2) diff --git a/pkg/app/idmanager/manager.go b/pkg/app/idmanager/manager.go index b004d3f04..6093618e0 100644 --- a/pkg/app/idmanager/manager.go +++ b/pkg/app/idmanager/manager.go @@ -149,22 +149,24 @@ func (m *Manager) Len() int { return out } +// CloseAll closes and removes all internal values that implements io.Closer func (m *Manager) CloseAll() { wg := new(sync.WaitGroup) m.mx.Lock() - for _, v := range m.values { + for k, v := range m.values { c, ok := v.(io.Closer) if !ok { continue } + delete(m.values, k) + wg.Add(1) go func(c io.Closer) { _ = c.Close() // nolint:errcheck wg.Done() }(c) } - m.values = make(map[uint16]interface{}) m.mx.Unlock() wg.Wait() diff --git a/pkg/app/rpc_client_test.go b/pkg/app/rpc_client_test.go index 394c1e1f6..b95abe312 100644 --- a/pkg/app/rpc_client_test.go +++ b/pkg/app/rpc_client_test.go @@ -13,7 +13,6 @@ import ( "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "golang.org/x/net/nettest" @@ -27,12 +26,14 @@ import ( func TestRPCClient_Dial(t *testing.T) { t.Run("ok", func(t *testing.T) { - s := prepRPCServer(t, prepGateway()) - rpcL, lisCleanup := prepListener(t) - defer lisCleanup() - go s.Accept(rpcL) - cl := prepRPCClient(t, rpcL.Addr().Network(), rpcL.Addr().String()) + rpcL, closeL := prepListener(t) + defer closeL() + + rpcS := prepRPCServer(t, appserver.NewRPCGateway(nil)) + go rpcS.Accept(rpcL) + + rpcC := prepRPCClient(t, rpcL.Addr().Network(), rpcL.Addr().String()) dmsgLocal, dmsgRemote, _, remote := prepAddrs() @@ -48,14 +49,14 @@ func TestRPCClient_Dial(t *testing.T) { err := appnet.AddNetworker(appnet.TypeDmsg, n) require.NoError(t, err) - connID, localPort, err := cl.Dial(remote) + connID, localPort, err := rpcC.Dial(remote) require.NoError(t, err) require.Equal(t, connID, uint16(1)) require.Equal(t, localPort, routing.Port(dmsgLocal.Port)) }) t.Run("dial error", func(t *testing.T) { - s := prepRPCServer(t, prepGateway()) + s := prepRPCServer(t, appserver.NewRPCGateway(nil)) rpcL, lisCleanup := prepListener(t) defer lisCleanup() go s.Accept(rpcL) @@ -85,7 +86,7 @@ func TestRPCClient_Dial(t *testing.T) { func TestRPCClient_Listen(t *testing.T) { t.Run("ok", func(t *testing.T) { - s := prepRPCServer(t, prepGateway()) + s := prepRPCServer(t, appserver.NewRPCGateway(nil)) rpcL, lisCleanup := prepListener(t) defer lisCleanup() go s.Accept(rpcL) @@ -111,7 +112,7 @@ func TestRPCClient_Listen(t *testing.T) { }) t.Run("listen error", func(t *testing.T) { - s := prepRPCServer(t, prepGateway()) + s := prepRPCServer(t, appserver.NewRPCGateway(nil)) rpcL, lisCleanup := prepListener(t) defer lisCleanup() go s.Accept(rpcL) @@ -142,7 +143,7 @@ func TestRPCClient_Accept(t *testing.T) { dmsgLocal, dmsgRemote, local, _ := prepAddrs() t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) lisConn := &appcommon.MockConn{} lisConn.On("LocalAddr").Return(dmsgLocal) @@ -177,7 +178,7 @@ func TestRPCClient_Accept(t *testing.T) { }) t.Run("accept error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) var lisConn net.Conn listenErr := errors.New("accept error") @@ -210,7 +211,7 @@ func TestRPCClient_Write(t *testing.T) { dmsgLocal, dmsgRemote, _, remote := prepAddrs() t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) writeBuf := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1} writeN := 10 @@ -240,7 +241,7 @@ func TestRPCClient_Write(t *testing.T) { }) t.Run("write error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) writeBuf := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1} writeN := 0 @@ -275,7 +276,7 @@ func TestRPCClient_Read(t *testing.T) { dmsgLocal, dmsgRemote, _, remote := prepAddrs() t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) readBufLen := 10 readBuf := make([]byte, readBufLen) @@ -306,7 +307,7 @@ func TestRPCClient_Read(t *testing.T) { }) t.Run("read error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) readBufLen := 10 readBuf := make([]byte, readBufLen) @@ -342,7 +343,7 @@ func TestRPCClient_CloseConn(t *testing.T) { dmsgLocal, dmsgRemote, _, remote := prepAddrs() t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) var noErr error @@ -369,7 +370,7 @@ func TestRPCClient_CloseConn(t *testing.T) { }) t.Run("close error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) closeErr := errors.New("close error") @@ -401,7 +402,7 @@ func TestRPCClient_CloseListener(t *testing.T) { _, _, local, _ := prepAddrs() t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) var noErr error @@ -426,7 +427,7 @@ func TestRPCClient_CloseListener(t *testing.T) { }) t.Run("close error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) closeErr := errors.New("close error") @@ -458,7 +459,7 @@ func TestRPCClient_SetDeadline(t *testing.T) { deadline := time.Now().Add(1 * time.Hour) t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) conn := &appcommon.MockConn{} conn.On("SetDeadline", mock.Anything).Return(func(d time.Time) error { @@ -489,7 +490,7 @@ func TestRPCClient_SetDeadline(t *testing.T) { }) t.Run("set deadline error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) conn := &appcommon.MockConn{} conn.On("SetDeadline", mock.Anything).Return(func(d time.Time) error { @@ -527,7 +528,7 @@ func TestRPCClient_SetReadDeadline(t *testing.T) { deadline := time.Now().Add(1 * time.Hour) t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) conn := &appcommon.MockConn{} conn.On("SetReadDeadline", mock.Anything).Return(func(d time.Time) error { @@ -558,7 +559,7 @@ func TestRPCClient_SetReadDeadline(t *testing.T) { }) t.Run("set deadline error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) conn := &appcommon.MockConn{} conn.On("SetReadDeadline", mock.Anything).Return(func(d time.Time) error { @@ -596,7 +597,7 @@ func TestRPCClient_SetWriteDeadline(t *testing.T) { deadline := time.Now().Add(1 * time.Hour) t.Run("ok", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) conn := &appcommon.MockConn{} conn.On("SetWriteDeadline", mock.Anything).Return(func(d time.Time) error { @@ -627,7 +628,7 @@ func TestRPCClient_SetWriteDeadline(t *testing.T) { }) t.Run("set deadline error", func(t *testing.T) { - gateway := prepGateway() + gateway := appserver.NewRPCGateway(nil) conn := &appcommon.MockConn{} conn.On("SetWriteDeadline", mock.Anything).Return(func(d time.Time) error { @@ -682,19 +683,24 @@ func prepNetworkerWithConn(t *testing.T, conn *appcommon.MockConn, remote appnet require.NoError(t, err) } -func prepGateway() *appserver.RPCGateway { - l := logging.MustGetLogger("rpc_gateway") - return appserver.NewRPCGateway(l) -} +// rpcProcKey is shared by prepRPCServer and prepRPCClient +var rpcProcKey = appcommon.RandProcKey() func prepRPCServer(t *testing.T, gateway *appserver.RPCGateway) *rpc.Server { s := rpc.NewServer() - err := s.Register(gateway) + err := s.RegisterName(rpcProcKey.String(), gateway) require.NoError(t, err) return s } +func prepRPCClient(t *testing.T, network, addr string) RPCClient { + rpcCl, err := rpc.Dial(network, addr) + require.NoError(t, err) + + return NewRPCClient(rpcCl, rpcProcKey) +} + func prepListener(t *testing.T) (lis net.Listener, cleanup func()) { lis, err := nettest.NewLocalListener("tcp") require.NoError(t, err) @@ -705,13 +711,6 @@ func prepListener(t *testing.T) (lis net.Listener, cleanup func()) { } } -func prepRPCClient(t *testing.T, network, addr string) RPCClient { - rpcCl, err := rpc.Dial(network, addr) - require.NoError(t, err) - - return NewRPCClient(rpcCl, "RPCGateway") -} - func prepAddrs() (dmsgLocal, dmsgRemote dmsg.Addr, local, remote appnet.Addr) { localPK, _ := cipher.GenerateKeyPair() localPort := uint16(10) diff --git a/pkg/visor/README.md b/pkg/visor/README.md index 1969686f1..fffcb701d 100644 --- a/pkg/visor/README.md +++ b/pkg/visor/README.md @@ -13,29 +13,37 @@ - `uptime_tracker` (*[UptimeTrackerConfig](#UptimeTrackerConfig)) - `app_discovery` (*[AppDiscConfig](#AppDiscConfig)) - `apps` ([][AppConfig](#AppConfig)) -- `trusted_visors` ([][PubKey](#PubKey)) -- `hypervisors` ([][HypervisorConfig](#HypervisorConfig)) +- `app_server_addr` (string) - `apps_path` (string) - `local_path` (string) +- `trusted_visors` ([][PubKey](#PubKey)) +- `hypervisors` ([][HypervisorConfig](#HypervisorConfig)) +- `interfaces` (*[InterfaceConfig](#InterfaceConfig)) - `log_level` (string) - `shutdown_timeout` ([Duration](#Duration)) -- `interfaces` (*[InterfaceConfig](#InterfaceConfig)) -- `app_server_addr` (string) - `restart_check_delay` (string) +# UptimeTrackerConfig + +- `addr` (string) + + # KeyPair - `public_key` ([PubKey](#PubKey)) - `secret_key` ([SecKey](#SecKey)) -# DmsgPtyConfig +# HypervisorConfig -- `port` ([uint16](#uint16)) -- `authorization_file` (string) -- `cli_network` (string) -- `cli_address` (string) +- `public_key` ([PubKey](#PubKey)) + + +# AppDiscConfig + +- `update_interval` ([Duration](#Duration)) +- `proxy_discovery_addr` (string) # RoutingConfig @@ -45,21 +53,23 @@ - `route_finder_timeout` ([Duration](#Duration)) -# TransportConfig +# DmsgPtyConfig -- `discovery` (string) -- `log_store` (*[LogStoreConfig](#LogStoreConfig)) +- `port` ([uint16](#uint16)) +- `authorization_file` (string) +- `cli_network` (string) +- `cli_address` (string) -# UptimeTrackerConfig +# TransportConfig -- `addr` (string) +- `discovery` (string) +- `log_store` (*[LogStoreConfig](#LogStoreConfig)) -# AppDiscConfig +# InterfaceConfig -- `update_interval` ([Duration](#Duration)) -- `proxy_discovery_addr` (string) +- `rpc` (string) # AppConfig @@ -70,40 +80,30 @@ - `args` ([]string) -# HypervisorConfig - -- `public_key` ([PubKey](#PubKey)) - - # LogStoreConfig - `type` ([LogStoreType](#LogStoreType)) - `location` (string) -# InterfaceConfig +# STCPConfig -- `rpc` (string) +- `pk_table` (map[[PubKey](#PubKey)]string) +- `local_address` (string) -# DmsgConfig +# Mutex -- `discovery` (string) -- `sessions_count` (int) +- `state` (int32) +- `sema` ([uint32](#uint32)) -# STCPConfig +# DmsgConfig -- `pk_table` (map[[PubKey](#PubKey)]string) -- `local_address` (string) +- `discovery` (string) +- `sessions_count` (int) # Logger - `` ([FieldLogger](#FieldLogger)) - - -# Mutex - -- `state` (int32) -- `sema` ([uint32](#uint32)) diff --git a/pkg/visor/config.go b/pkg/visor/config.go index 6738907c8..1883c0b78 100644 --- a/pkg/visor/config.go +++ b/pkg/visor/config.go @@ -47,9 +47,9 @@ var ( // Config defines configuration parameters for Visor. type Config struct { - Path *string `json:"-"` - log *logging.Logger `json:"-"` - flushMu sync.Mutex `json:"-"` + Path *string `json:"-"` + log *logging.Logger + flushMu sync.Mutex Version string `json:"version"` KeyPair *KeyPair `json:"key_pair"` diff --git a/pkg/visor/rpc_test.go b/pkg/visor/rpc_test.go index 27a3fcad7..cc29e3a91 100644 --- a/pkg/visor/rpc_test.go +++ b/pkg/visor/rpc_test.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "net/http" "os" - "path/filepath" "testing" "time" @@ -21,7 +20,6 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" - "github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil" ) func TestHealth(t *testing.T) { @@ -94,8 +92,8 @@ func TestListApps(t *testing.T) { } pm := &appserver.MockProcManager{} - pm.On("Exists", apps["foo"].App).Return(false) - pm.On("Exists", apps["bar"].App).Return(true) + pm.On("ProcByName", apps["foo"].App).Return(new(appserver.Proc), false) + pm.On("ProcByName", apps["bar"].App).Return(new(appserver.Proc), true) n := Visor{ appsConf: apps, @@ -138,6 +136,7 @@ func TestStartStopApp(t *testing.T) { defer func() { require.NoError(t, os.RemoveAll("skychat")) + require.NoError(t, os.RemoveAll("apps-pid.txt")) }() appCfg := []AppConfig{ @@ -168,30 +167,14 @@ func TestStartStopApp(t *testing.T) { conf: &visorCfg, } - require.NoError(t, pathutil.EnsureDir(visor.dir())) - - defer func() { - require.NoError(t, os.RemoveAll(visor.dir())) - }() - - appCfg1 := appcommon.ProcConfig{ - AppName: app, - AppSrvAddr: appcommon.DefaultAppSrvAddr, - VisorPK: visorCfg.Keys().PubKey.Hex(), - RoutingPort: apps["foo"].Port, - ProcWorkDir: filepath.Join("", app), - } - - appArgs1 := append([]string{filepath.Join(visor.dir(), app)}, apps["foo"].Args...) appPID1 := appcommon.ProcID(10) pm := &appserver.MockProcManager{} - pm.On("Start", mock.Anything, appCfg1, appArgs1, mock.Anything, mock.Anything). - Return(appPID1, testhelpers.NoErr) + pm.On("Start", mock.Anything).Return(appPID1, testhelpers.NoErr) pm.On("Wait", app).Return(testhelpers.NoErr) pm.On("Stop", app).Return(testhelpers.NoErr) - pm.On("Exists", app).Return(true) - pm.On("Exists", unknownApp).Return(false) + pm.On("ProcByName", app).Return(new(appserver.Proc), true) + pm.On("ProcByName", unknownApp).Return(new(appserver.Proc), false) visor.procM = pm diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 67bc8d9d2..f40feecbe 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -93,8 +93,7 @@ type Visor struct { cliLis net.Listener hvErrs map[cipher.PubKey]chan error // errors returned when the associated hypervisor ServeRPCClient returns - appDiscF *appdisc.Factory - procM appserver.ProcManager + procM appserver.ProcManager // cancel is to be called when visor.Close is triggered. cancel context.CancelFunc @@ -220,7 +219,7 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con visor.hvErrs[hv.PubKey] = make(chan error, 1) } - visor.appDiscF = &appdisc.Factory{ + appDiscF := &appdisc.Factory{ PK: pk, SK: sk, UpdateInterval: time.Duration(cfg.AppDiscConfig().UpdateInterval), @@ -228,7 +227,7 @@ func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Con } logProcM := logging.MustGetLogger("proc_manager") - visor.procM, err = appserver.NewProcManager(logProcM, visor.appDiscF, visor.conf.AppServerAddr) + visor.procM, err = appserver.NewProcManager(logProcM, appDiscF, visor.conf.AppServerAddr) if err != nil { return nil, fmt.Errorf("failed to start proc manager: %w", err) } diff --git a/pkg/visor/visor_test.go b/pkg/visor/visor_test.go index 08e3de691..c9a7d7b3f 100644 --- a/pkg/visor/visor_test.go +++ b/pkg/visor/visor_test.go @@ -23,7 +23,6 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" "github.com/SkycoinProject/skywire-mainnet/pkg/transport" - "github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil" ) var masterLogger *logging.MasterLogger @@ -78,6 +77,13 @@ func TestMain(m *testing.M) { //} func TestVisorStartClose(t *testing.T) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + require.NoError(t, os.RemoveAll("apps-pid.txt")) + }() + r := &router.MockRouter{} r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) r.On("Close").Return(testhelpers.NoErr) @@ -110,30 +116,24 @@ func TestVisorStartClose(t *testing.T) { logger := logging.MustGetLogger("test") + //procM, err := appserver.NewProcManager(logger, nil, ":0") + //require.NoError(t, err) + //visorCfg.AppServerAddr = procM.Addr().String() + visor := &Visor{ - conf: &visorCfg, - router: r, - appsConf: apps, - logger: logger, - appRPCServer: appserver.New(logger, visorCfg.AppServerAddr), + conf: &visorCfg, + router: r, + appsConf: apps, + logger: logger, + //procM: procM, } pm := &appserver.MockProcManager{} - appCfg1 := appcommon.ProcConfig{ - AppName: apps["skychat"].App, - AppSrvAddr: appcommon.DefaultAppSrvAddr, - VisorPK: visorCfg.Keys().PubKey.Hex(), - RoutingPort: apps["skychat"].Port, - ProcWorkDir: filepath.Join("", apps["skychat"].App), - } - appArgs1 := append([]string{filepath.Join(visor.dir(), apps["skychat"].App)}, apps["skychat"].Args...) appPID1 := appcommon.ProcID(10) - pm.On("Start", mock.Anything, appCfg1, appArgs1, mock.Anything, mock.Anything). - Return(appPID1, testhelpers.NoErr) - pm.On("Wait", apps["skychat"].App).Return(testhelpers.NoErr) - - pm.On("StopAll").Return() + pm.On("Start", mock.Anything).Return(appPID1, testhelpers.NoErr) + pm.On("Wait", apps["skychat"].App).Return(testhelpers.NoErr) + pm.On("Close").Return(testhelpers.NoErr) visor.procM = pm dmsgC := dmsg.NewClient(cipher.PubKey{}, cipher.SecKey{}, disc.NewMock(), nil) @@ -162,6 +162,13 @@ func TestVisorStartClose(t *testing.T) { } func TestVisorSpawnApp(t *testing.T) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "") + require.NoError(t, err) + defer func() { + require.NoError(t, os.RemoveAll(tmpDir)) + require.NoError(t, os.RemoveAll("apps-pid.txt")) + }() + r := &router.MockRouter{} r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) r.On("Close").Return(testhelpers.NoErr) @@ -192,28 +199,20 @@ func TestVisorSpawnApp(t *testing.T) { conf: &visorCfg, } - require.NoError(t, pathutil.EnsureDir(visor.dir())) + //appCfg := appcommon.ProcConfig{ + // AppName: app.App, + // AppSrvAddr: appcommon.DefaultAppSrvAddr, + // VisorPK: visorCfg.Keys().PubKey, + // RoutingPort: app.Port, + // ProcWorkDir: filepath.Join(tmpDir, app.App), + //} - defer func() { - require.NoError(t, os.RemoveAll(visor.dir())) - }() - - appCfg := appcommon.ProcConfig{ - AppName: app.App, - AppSrvAddr: appcommon.DefaultAppSrvAddr, - VisorPK: visorCfg.Keys().PubKey.Hex(), - RoutingPort: app.Port, - ProcWorkDir: filepath.Join("", app.App), - } - - appArgs := append([]string{filepath.Join(visor.dir(), app.App)}, app.Args...) appPID := appcommon.ProcID(10) pm := &appserver.MockProcManager{} pm.On("Wait", app.App).Return(testhelpers.NoErr) - pm.On("Start", mock.Anything, appCfg, appArgs, mock.Anything, mock.Anything). - Return(appPID, testhelpers.NoErr) - pm.On("Exists", app.App).Return(true) + pm.On("Start", mock.Anything).Return(appPID, testhelpers.NoErr) + pm.On("ProcByName", app.App).Return(new(appserver.Proc), true) pm.On("Stop", app.App).Return(testhelpers.NoErr) visor.procM = pm @@ -221,12 +220,17 @@ func TestVisorSpawnApp(t *testing.T) { require.NoError(t, visor.StartApp(app.App)) time.Sleep(100 * time.Millisecond) - require.True(t, visor.procM.Exists(app.App)) + _, ok := visor.procM.ProcByName(app.App) + require.True(t, ok) require.NoError(t, visor.StopApp(app.App)) } func TestVisorSpawnAppValidations(t *testing.T) { + tmpDir, err := ioutil.TempDir(os.TempDir(), "") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(tmpDir)) }() + r := &router.MockRouter{} r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) r.On("Close").Return(testhelpers.NoErr) @@ -246,12 +250,6 @@ func TestVisorSpawnAppValidations(t *testing.T) { conf: c, } - require.NoError(t, pathutil.EnsureDir(visor.dir())) - - defer func() { - require.NoError(t, os.RemoveAll(visor.dir())) - }() - t.Run("fail - can't bind to reserved port", func(t *testing.T) { app := AppConfig{ App: "skychat", @@ -261,18 +259,16 @@ func TestVisorSpawnAppValidations(t *testing.T) { appCfg := appcommon.ProcConfig{ AppName: app.App, AppSrvAddr: appcommon.DefaultAppSrvAddr, - VisorPK: c.Keys().PubKey.Hex(), + VisorPK: c.Keys().PubKey, RoutingPort: app.Port, - ProcWorkDir: filepath.Join("", app.App), + ProcWorkDir: filepath.Join(tmpDir, app.App), } - appArgs := append([]string{filepath.Join(visor.dir(), app.App)}, app.Args...) appPID := appcommon.ProcID(10) pm := &appserver.MockProcManager{} - pm.On("Run", mock.Anything, appCfg, appArgs, mock.Anything, mock.Anything). - Return(appPID, testhelpers.NoErr) - pm.On("Exists", app.App).Return(false) + pm.On("Run", mock.Anything, appCfg, app.Args, mock.Anything, mock.Anything).Return(appPID, testhelpers.NoErr) + pm.On("ProcByName", app.App).Return(new(appserver.Proc), false) visor.procM = pm @@ -298,19 +294,10 @@ func TestVisorSpawnAppValidations(t *testing.T) { wantErr := fmt.Sprintf("error running app skychat: %s", appserver.ErrAppAlreadyStarted) pm := &appserver.MockProcManager{} - appCfg := appcommon.ProcConfig{ - AppName: app.App, - AppSrvAddr: appcommon.DefaultAppSrvAddr, - VisorPK: c.Keys().PubKey.Hex(), - RoutingPort: app.Port, - ProcWorkDir: filepath.Join("", app.App), - } - appArgs := append([]string{filepath.Join(visor.dir(), app.App)}, app.Args...) appPID := appcommon.ProcID(10) - pm.On("Start", mock.Anything, appCfg, appArgs, mock.Anything, mock.Anything). - Return(appPID, appserver.ErrAppAlreadyStarted) - pm.On("Exists", app.App).Return(true) + pm.On("Start", mock.Anything).Return(appPID, appserver.ErrAppAlreadyStarted) + pm.On("ProcByName", app.App).Return(new(appserver.Proc), true) visor.procM = pm From 6e22400722313f12015e8b6e820581c429d15697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Thu, 7 May 2020 23:22:42 +1200 Subject: [PATCH 11/17] Implemented proxy discovery conn count updating. --- pkg/app/appdisc/const.go | 7 +++++ pkg/app/appdisc/discovery_updater.go | 10 ++++++- pkg/app/appserver/proc.go | 24 ++++++++++++++++ pkg/app/idmanager/delta_informer.go | 41 ++++++++++++++++++++++++++++ pkg/app/idmanager/manager.go | 19 +++++++++++-- pkg/proxydisc/client.go | 36 +++++++++++++++++------- 6 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 pkg/app/appdisc/const.go create mode 100644 pkg/app/idmanager/delta_informer.go diff --git a/pkg/app/appdisc/const.go b/pkg/app/appdisc/const.go new file mode 100644 index 000000000..5849e0109 --- /dev/null +++ b/pkg/app/appdisc/const.go @@ -0,0 +1,7 @@ +package appdisc + +// ChangeValue keys. +const ( + ConnCountValue = "conn_count" + ListenerCountValue = "listener_count" +) diff --git a/pkg/app/appdisc/discovery_updater.go b/pkg/app/appdisc/discovery_updater.go index 01e6f7f04..551d70398 100644 --- a/pkg/app/appdisc/discovery_updater.go +++ b/pkg/app/appdisc/discovery_updater.go @@ -2,6 +2,7 @@ package appdisc import ( "context" + "strconv" "sync" "time" @@ -70,6 +71,13 @@ func (u *proxyUpdater) Stop() { } func (u *proxyUpdater) ChangeValue(name string, v []byte) error { - // TODO: Implement a way to change conn count in discovery entry. + switch name { + case ConnCountValue: + n, err := strconv.Atoi(string(v)) + if err != nil { + return err + } + go u.client.UpdateStats(proxydisc.Stats{ConnectedClients: n}) + } return nil } diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index d93744dc6..1ac55fc64 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -7,6 +7,7 @@ import ( "net/rpc" "os" "os/exec" + "strconv" "sync" "sync/atomic" @@ -92,11 +93,34 @@ func (p *Proc) awaitConn() bool { if _, ok := <-p.connCh; !ok { return false } + rpcS := rpc.NewServer() if err := rpcS.RegisterName(p.conf.ProcKey.String(), p.rpcGW); err != nil { panic(err) } + + connDelta := p.rpcGW.cm.AddDeltaInformer() + go func() { + for n := range connDelta.Chan() { + if err := p.disc.ChangeValue(appdisc.ConnCountValue, []byte(strconv.Itoa(n))); err != nil { + p.log.WithError(err).WithField("value", appdisc.ConnCountValue). + Error("Failed to change app discovery value.") + } + } + }() + + lisDelta := p.rpcGW.lm.AddDeltaInformer() + go func() { + for n := range lisDelta.Chan() { + if err := p.disc.ChangeValue(appdisc.ListenerCountValue, []byte(strconv.Itoa(n))); err != nil { + p.log.WithError(err).WithField("value", appdisc.ListenerCountValue). + Error("Failed to change app discovery value.") + } + } + }() + go rpcS.ServeConn(p.conn) + p.log.Info("Associated and serving proc conn.") return true } diff --git a/pkg/app/idmanager/delta_informer.go b/pkg/app/idmanager/delta_informer.go new file mode 100644 index 000000000..a51dc8b25 --- /dev/null +++ b/pkg/app/idmanager/delta_informer.go @@ -0,0 +1,41 @@ +package idmanager + +// DeltaInformer informs when there has been a change to the id-manager. +// .Trigger() and .Stop() can not be called concurrently to each other. +type DeltaInformer struct { + closed bool + ch chan int +} + +// NewDeltaInformer creates a new DeltaInformer. +func NewDeltaInformer() *DeltaInformer { + return &DeltaInformer{ch: make(chan int)} +} + +// Chan returns the internal chan that gets triggered when a change occurs. +func (di *DeltaInformer) Chan() <-chan int { + if di == nil { + return nil + } + return di.ch +} + +// Trigger should be called whenever the internal chan should be triggered. +func (di *DeltaInformer) Trigger(n int) { + if di == nil || di.closed { + return + } + select { + case di.ch <- n: + default: + } +} + +// Stop closes the internal chan. +func (di *DeltaInformer) Stop() { + if di == nil || di.closed { + return + } + di.closed = true + close(di.ch) +} diff --git a/pkg/app/idmanager/manager.go b/pkg/app/idmanager/manager.go index 6093618e0..34417afe4 100644 --- a/pkg/app/idmanager/manager.go +++ b/pkg/app/idmanager/manager.go @@ -10,8 +10,8 @@ import ( var ( // ErrNoMoreAvailableValues is returned when all the slots are reserved. ErrNoMoreAvailableValues = errors.New("no more available values") - // ErrValueAlreadyExists is returned when value associated with the specified - // key already exists. + + // ErrValueAlreadyExists is returned when value associated with the specified key already exists. ErrValueAlreadyExists = errors.New("value already exists") ) @@ -22,6 +22,8 @@ type Manager struct { values map[uint16]interface{} mx sync.RWMutex lstID uint16 + + di *DeltaInformer // optional } // New constructs new `Manager`. @@ -31,6 +33,15 @@ func New() *Manager { } } +// AddDeltaInformer adds a DeltaInformer to the id manager and returns the DeltaInformer. +func (m *Manager) AddDeltaInformer() *DeltaInformer { + di := NewDeltaInformer() + m.mx.Lock() + m.di = di + m.mx.Unlock() + return di +} + // ReserveNextID reserves next free slot for the value and returns the id for it. func (m *Manager) ReserveNextID() (id *uint16, free func() bool, err error) { m.mx.Lock() @@ -50,6 +61,7 @@ func (m *Manager) ReserveNextID() (id *uint16, free func() bool, err error) { m.values[nxtID] = nil m.lstID = nxtID + m.di.Trigger(len(m.values)) m.mx.Unlock() return &nxtID, m.constructFreeFunc(nxtID), nil @@ -73,6 +85,7 @@ func (m *Manager) Pop(id uint16) (interface{}, error) { delete(m.values, id) + m.di.Trigger(len(m.values)) m.mx.Unlock() return v, nil @@ -89,6 +102,7 @@ func (m *Manager) Add(id uint16, v interface{}) (free func() bool, err error) { m.values[id] = v + m.di.Trigger(len(m.values)) m.mx.Unlock() return m.constructFreeFunc(id), nil @@ -167,6 +181,7 @@ func (m *Manager) CloseAll() { wg.Done() }(c) } + m.di.Stop() m.mx.Unlock() wg.Wait() diff --git a/pkg/proxydisc/client.go b/pkg/proxydisc/client.go index 5442b6f2e..806dce63d 100644 --- a/pkg/proxydisc/client.go +++ b/pkg/proxydisc/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "net/http" + "sync" "time" "github.com/SkycoinProject/dmsg/cipher" @@ -23,11 +24,12 @@ type Config struct { // HTTPClient is responsible for interacting with the proxy-discovery type HTTPClient struct { - log logrus.FieldLogger - conf Config - entry Proxy - auth *httpauth.Client - client http.Client + log logrus.FieldLogger + conf Config + entry Proxy + entryMx sync.Mutex // only used if UpdateLoop && UpdateStats functions are used. + auth *httpauth.Client + client http.Client } // NewClient creates a new HTTPClient. @@ -36,7 +38,8 @@ func NewClient(log logrus.FieldLogger, conf Config) *HTTPClient { log: log, conf: conf, entry: Proxy{ - Addr: NewSWAddr(conf.PK, conf.Port), + Addr: NewSWAddr(conf.PK, conf.Port), + Stats: &Stats{ConnectedClients: 0}, }, client: http.Client{}, } @@ -126,6 +129,7 @@ func (c *HTTPClient) UpdateEntry(ctx context.Context) (*Proxy, error) { } return nil, &hErr } + err = json.NewDecoder(resp.Body).Decode(&c.entry) return &c.entry, err } @@ -170,21 +174,26 @@ func (c *HTTPClient) UpdateLoop(ctx context.Context, updateInterval time.Duratio update := func() { for { + c.entryMx.Lock() entry, err := c.UpdateEntry(ctx) + c.entryMx.Unlock() + if err != nil { c.log.WithError(err).Warn("Failed to update proxy entry in discovery. Retrying...") time.Sleep(time.Second * 10) // TODO(evanlinjin): Exponential backoff. continue } - if entry.Geo != nil { - c.entry.Geo = entry.Geo - } + + c.entryMx.Lock() j, err := json.Marshal(entry) + c.entryMx.Unlock() + if err != nil { panic(err) } + c.log.WithField("entry", string(j)).Debug("Entry updated.") - break + return } } @@ -202,3 +211,10 @@ func (c *HTTPClient) UpdateLoop(ctx context.Context, updateInterval time.Duratio } } } + +// UpdateStats updates the stats field of the internal proxy entry state. +func (c *HTTPClient) UpdateStats(stats Stats) { + c.entryMx.Lock() + c.entry.Stats = &stats + c.entryMx.Unlock() +} From 86655c7317197c590ccd2ce86a31c0910eac3da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Mon, 11 May 2020 03:56:34 +1200 Subject: [PATCH 12/17] Fix imports. --- cmd/apps/helloworld/helloworld.go | 3 +-- cmd/apps/skychat/chat.go | 3 +-- cmd/apps/skysocks-client/skysocks-client.go | 3 +-- cmd/apps/vpn-client/vpn-client.go | 3 +-- internal/skysocks/client.go | 3 +-- internal/skysocks/server.go | 3 +-- internal/vpn/client.go | 1 - internal/vpn/server.go | 1 - pkg/app/appcommon/log_store.go | 1 - pkg/app/client_test.go | 3 +-- pkg/hypervisor/hypervisor.go | 3 +-- 11 files changed, 8 insertions(+), 19 deletions(-) diff --git a/cmd/apps/helloworld/helloworld.go b/cmd/apps/helloworld/helloworld.go index 6bde5742a..a65957e9e 100644 --- a/cmd/apps/helloworld/helloworld.go +++ b/cmd/apps/helloworld/helloworld.go @@ -6,9 +6,8 @@ package main import ( "os" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/dmsg/cipher" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/app" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" diff --git a/cmd/apps/skychat/chat.go b/cmd/apps/skychat/chat.go index d902baf92..adc1281b2 100644 --- a/cmd/apps/skychat/chat.go +++ b/cmd/apps/skychat/chat.go @@ -15,9 +15,8 @@ import ( "sync" "time" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/dmsg/cipher" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/internal/netutil" "github.com/SkycoinProject/skywire-mainnet/pkg/app" diff --git a/cmd/apps/skysocks-client/skysocks-client.go b/cmd/apps/skysocks-client/skysocks-client.go index 2e48c3d8c..0cfa70935 100644 --- a/cmd/apps/skysocks-client/skysocks-client.go +++ b/cmd/apps/skysocks-client/skysocks-client.go @@ -9,9 +9,8 @@ import ( "net" "time" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/dmsg/cipher" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/internal/netutil" "github.com/SkycoinProject/skywire-mainnet/internal/skysocks" diff --git a/cmd/apps/vpn-client/vpn-client.go b/cmd/apps/vpn-client/vpn-client.go index 139ac6ca0..a844ca178 100644 --- a/cmd/apps/vpn-client/vpn-client.go +++ b/cmd/apps/vpn-client/vpn-client.go @@ -9,10 +9,9 @@ import ( "syscall" "time" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/netutil" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/internal/vpn" "github.com/SkycoinProject/skywire-mainnet/pkg/app" diff --git a/internal/skysocks/client.go b/internal/skysocks/client.go index b13fcaf49..adeda8a1c 100644 --- a/internal/skysocks/client.go +++ b/internal/skysocks/client.go @@ -7,10 +7,9 @@ import ( "sync" "time" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/yamux" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/router" ) diff --git a/internal/skysocks/server.go b/internal/skysocks/server.go index f5bca9b46..018bb2df8 100644 --- a/internal/skysocks/server.go +++ b/internal/skysocks/server.go @@ -5,10 +5,9 @@ import ( "net" "sync/atomic" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/yamux" "github.com/armon/go-socks5" + "github.com/sirupsen/logrus" ) // Server implements multiplexing proxy server using yamux. diff --git a/internal/vpn/client.go b/internal/vpn/client.go index 69333597b..cd0f3bcec 100644 --- a/internal/vpn/client.go +++ b/internal/vpn/client.go @@ -10,7 +10,6 @@ import ( "sync" "github.com/sirupsen/logrus" - "github.com/songgao/water" ) diff --git a/internal/vpn/server.go b/internal/vpn/server.go index b2d78abee..2ea1a4b20 100644 --- a/internal/vpn/server.go +++ b/internal/vpn/server.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/sirupsen/logrus" - "github.com/songgao/water" ) diff --git a/pkg/app/appcommon/log_store.go b/pkg/app/appcommon/log_store.go index 31f33bdf3..2ffdc94d3 100644 --- a/pkg/app/appcommon/log_store.go +++ b/pkg/app/appcommon/log_store.go @@ -10,7 +10,6 @@ import ( "time" "github.com/SkycoinProject/skycoin/src/util/logging" - "go.etcd.io/bbolt" ) diff --git a/pkg/app/client_test.go b/pkg/app/client_test.go index 2e1a946aa..7faa3e10c 100644 --- a/pkg/app/client_test.go +++ b/pkg/app/client_test.go @@ -4,12 +4,11 @@ import ( "errors" "testing" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/stretchr/testify/require" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" "github.com/SkycoinProject/skywire-mainnet/pkg/app/idmanager" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" diff --git a/pkg/hypervisor/hypervisor.go b/pkg/hypervisor/hypervisor.go index 5f75ac3fe..b4d2b7419 100644 --- a/pkg/hypervisor/hypervisor.go +++ b/pkg/hypervisor/hypervisor.go @@ -13,8 +13,6 @@ import ( "sync" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/dmsgpty" @@ -24,6 +22,7 @@ import ( "github.com/go-chi/chi/middleware" "github.com/google/uuid" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" From ee49ea7f82ff8f68904295d8fa73b603112ca104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Wed, 13 May 2020 02:14:26 +1200 Subject: [PATCH 13/17] App management, visor config, startup and shutdown improvements. * Separated app-associated functionality within /pkg/visor into /pkg/app/launcher. * Added the concept of visor.BaseConfig() to simplify obtaining default values. * Made startup/shutdown logic modular and introduced init and shutdown stacks. * Shutdown now uses module-based timeouts and wait groups. --- cmd/skywire-cli/commands/visor/app.go | 7 +- cmd/skywire-cli/commands/visor/gen-config.go | 154 +--- cmd/skywire-visor/commands/root.go | 206 ++--- pkg/app/appcommon/proc_config.go | 4 - pkg/app/appnet/skywire_networker.go | 7 +- pkg/app/appserver/proc.go | 10 +- pkg/app/appserver/proc_manager.go | 4 +- pkg/app/launcher/app_state.go | 18 + pkg/app/launcher/launcher.go | 369 ++++++++ pkg/hypervisor/config.go | 2 +- pkg/hypervisor/hypervisor.go | 18 +- pkg/skyenv/values.go | 22 +- pkg/snet/network.go | 12 +- pkg/snet/snettest/env.go | 3 +- .../client => transport/tpdclient}/client.go | 4 +- .../tpdclient}/client_test.go | 2 +- pkg/visor/config.go | 540 +++++------- pkg/visor/config_test.go | 113 --- pkg/visor/duration.go | 1 + pkg/visor/gateway.go | 22 +- pkg/visor/rpc.go | 68 +- pkg/visor/rpc_client.go | 24 +- pkg/visor/rpc_test.go | 294 +++---- pkg/visor/visor.go | 802 +++--------------- pkg/visor/visor_init.go | 418 +++++++++ pkg/visor/visor_test.go | 465 +++++----- 26 files changed, 1756 insertions(+), 1833 deletions(-) create mode 100644 pkg/app/launcher/app_state.go create mode 100644 pkg/app/launcher/launcher.go rename pkg/{transport-discovery/client => transport/tpdclient}/client.go (98%) rename pkg/{transport-discovery/client => transport/tpdclient}/client_test.go (99%) create mode 100644 pkg/visor/visor_init.go diff --git a/cmd/skywire-cli/commands/visor/app.go b/cmd/skywire-cli/commands/visor/app.go index 027199621..2d1c82a19 100644 --- a/cmd/skywire-cli/commands/visor/app.go +++ b/cmd/skywire-cli/commands/visor/app.go @@ -8,10 +8,11 @@ import ( "text/tabwriter" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/spf13/cobra" "github.com/SkycoinProject/skywire-mainnet/cmd/skywire-cli/internal" - "github.com/SkycoinProject/skywire-mainnet/pkg/visor" ) func init() { @@ -38,10 +39,10 @@ var lsAppsCmd = &cobra.Command{ for _, state := range states { status := "stopped" - if state.Status == visor.AppStatusRunning { + if state.Status == launcher.AppStatusRunning { status = "running" } - _, err = fmt.Fprintf(w, "%s\t%s\t%t\t%s\n", state.App, strconv.Itoa(int(state.Port)), state.AutoStart, status) + _, err = fmt.Fprintf(w, "%s\t%s\t%t\t%s\n", state.Name, strconv.Itoa(int(state.Port)), state.AutoStart, status) internal.Catch(err) } internal.Catch(w.Flush()) diff --git a/cmd/skywire-cli/commands/visor/gen-config.go b/cmd/skywire-cli/commands/visor/gen-config.go index 9c7905976..19e99fea4 100644 --- a/cmd/skywire-cli/commands/visor/gen-config.go +++ b/cmd/skywire-cli/commands/visor/gen-config.go @@ -7,11 +7,6 @@ import ( "path" "path/filepath" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/skywire-mainnet/pkg/restart" - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" - "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" - "github.com/SkycoinProject/dmsg/cipher" "github.com/spf13/cobra" @@ -37,8 +32,10 @@ func init() { genConfigCmd.Flags().StringVarP(&output, "output", "o", "", "path of output config file. Uses default of 'type' flag if unspecified.") genConfigCmd.Flags().BoolVarP(&replace, "replace", "r", false, "whether to allow rewrite of a file that already exists.") genConfigCmd.Flags().BoolVar(&retainKeys, "retain-keys", false, "retain current keys") - genConfigCmd.Flags().VarP(&configLocType, "type", "m", fmt.Sprintf("config generation mode. Valid values: %v", pathutil.AllConfigLocationTypes())) genConfigCmd.Flags().BoolVarP(&testenv, "testing-environment", "t", false, "whether to use production or test deployment service.") + + // TODO(evanlinjin): Re-implement this at a later stage. + //genConfigCmd.Flags().VarP(&configLocType, "type", "m", fmt.Sprintf("config generation mode. Valid values: %v", pathutil.AllConfigLocationTypes())) } var genConfigCmd = &cobra.Command{ @@ -56,13 +53,13 @@ var genConfigCmd = &cobra.Command{ }, Run: func(_ *cobra.Command, _ []string) { var conf *visor.Config + switch configLocType { case pathutil.WorkingDirLoc: - conf = defaultConfig() - case pathutil.HomeLoc: - conf = homeConfig() - case pathutil.LocalLoc: - conf = localConfig() + var err error + if conf, err = visor.DefaultConfig(nil, output, visor.NewKeyPair()); err != nil { + logger.WithError(err).Fatal("Failed to create default config.") + } default: logger.Fatalln("invalid config type:", configLocType) } @@ -90,138 +87,3 @@ func fillInOldKeys(confPath string, conf *visor.Config) error { return nil } - -func homeConfig() *visor.Config { - c := defaultConfig() - c.AppsPath = filepath.Join(pathutil.HomeDir(), ".skycoin/skywire/apps") - c.Transport.LogStore.Location = filepath.Join(pathutil.HomeDir(), ".skycoin/skywire/transport_logs") - return c -} - -func localConfig() *visor.Config { - c := defaultConfig() - c.AppsPath = "/usr/local/skycoin/skywire/apps" - c.Transport.LogStore.Location = "/usr/local/skycoin/skywire/transport_logs" - return c -} - -func defaultConfig() *visor.Config { - conf := &visor.Config{} - - if sk.Null() { - conf.KeyPair = visor.NewKeyPair() - } else { - conf.KeyPair = visor.RestoreKeyPair(sk) - } - - stcp, err := visor.DefaultSTCPConfig() - if err != nil { - logger.Warn(err) - } else { - conf.STCP = stcp - } - - conf.Dmsg = visor.DefaultDmsgConfig() - - ptyConf := defaultDmsgPtyConfig() - conf.DmsgPty = &ptyConf - - // TODO(evanlinjin): We have disabled skysocks passcode by default for now - We should make a cli arg for this. - //passcode := base64.StdEncoding.Strict().EncodeToString(cipher.RandByte(8)) - conf.Apps = []visor.AppConfig{ - defaultSkychatConfig(), - defaultSkysocksConfig(""), - defaultSkysocksClientConfig(), - defaultVPNServerConfig(), - defaultVPNClientConfig(), - } - - conf.TrustedVisors = []cipher.PubKey{} - - conf.Transport = visor.DefaultTransportConfig() - conf.Routing = visor.DefaultRoutingConfig() - - conf.Hypervisors = []visor.HypervisorConfig{} - - conf.UptimeTracker = visor.DefaultUptimeTrackerConfig() - - conf.AppDiscovery = visor.DefaultAppDiscConfig() - - conf.AppsPath = visor.DefaultAppsPath - conf.LocalPath = visor.DefaultLocalPath - - conf.LogLevel = visor.DefaultLogLevel - conf.ShutdownTimeout = visor.DefaultTimeout - - conf.Interfaces = &visor.InterfaceConfig{ - RPCAddress: "localhost:3435", - } - - conf.AppServerAddr = appcommon.DefaultAppSrvAddr - conf.RestartCheckDelay = restart.DefaultCheckDelay.String() - - if testenv { - conf.Dmsg.Discovery = skyenv.TestDmsgDiscAddr - conf.Transport.Discovery = skyenv.TestTpDiscAddr - conf.Routing.RouteFinder = skyenv.TestRouteFinderAddr - conf.Routing.SetupNodes = []cipher.PubKey{skyenv.MustPK(skyenv.TestSetupPK)} - conf.AppDiscovery.ProxyDisc = skyenv.TestProxyDiscAddr - } - - return conf -} - -func defaultDmsgPtyConfig() visor.DmsgPtyConfig { - return visor.DmsgPtyConfig{ - Port: skyenv.DmsgPtyPort, - AuthFile: "./skywire/dmsgpty/whitelist.json", - CLINet: skyenv.DefaultDmsgPtyCLINet, - CLIAddr: skyenv.DefaultDmsgPtyCLIAddr, - } -} - -func defaultSkychatConfig() visor.AppConfig { - return visor.AppConfig{ - App: skyenv.SkychatName, - AutoStart: true, - Port: routing.Port(skyenv.SkychatPort), - Args: []string{"-addr", skyenv.SkychatAddr}, - } -} - -func defaultSkysocksConfig(passcode string) visor.AppConfig { - var args []string - if passcode != "" { - args = []string{"-passcode", passcode} - } - return visor.AppConfig{ - App: skyenv.SkysocksName, - AutoStart: true, - Port: routing.Port(skyenv.SkysocksPort), - Args: args, - } -} - -func defaultSkysocksClientConfig() visor.AppConfig { - return visor.AppConfig{ - App: skyenv.SkysocksClientName, - AutoStart: false, - Port: routing.Port(skyenv.SkysocksClientPort), - } -} - -func defaultVPNServerConfig() visor.AppConfig { - return visor.AppConfig{ - App: skyenv.VPNServerName, - AutoStart: true, - Port: routing.Port(skyenv.VPNServerPort), - } -} - -func defaultVPNClientConfig() visor.AppConfig { - return visor.AppConfig{ - App: skyenv.VPNClientName, - AutoStart: false, - Port: routing.Port(skyenv.VPNClientPort), - } -} diff --git a/cmd/skywire-visor/commands/root.go b/cmd/skywire-visor/commands/root.go index 1b689f856..49283971d 100644 --- a/cmd/skywire-visor/commands/root.go +++ b/cmd/skywire-visor/commands/root.go @@ -3,7 +3,6 @@ package commands // NOTE: "net/http/pprof" is used for profiling. import ( "bufio" - "context" "encoding/json" "fmt" "io" @@ -15,7 +14,6 @@ import ( "os" "os/signal" "path/filepath" - "strings" "syscall" "time" @@ -24,7 +22,6 @@ import ( logrussyslog "github.com/sirupsen/logrus/hooks/syslog" "github.com/spf13/cobra" - "github.com/SkycoinProject/skywire-mainnet/internal/utclient" "github.com/SkycoinProject/skywire-mainnet/pkg/restart" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" "github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil" @@ -35,26 +32,25 @@ import ( //import _ "net/http/pprof" // used for HTTP profiling const configEnv = "SW_CONFIG" -const defaultShutdownTimeout = visor.Duration(10 * time.Second) -type runCfg struct { - syslogAddr string - tag string - cfgFromStdin bool - profileMode string - port string - startDelay string - args []string +type runConf struct { + syslogAddr string + tag string + confFromStdin bool + profileMode string + port string + startDelay string + args []string profileStop func() logger *logging.Logger masterLogger *logging.MasterLogger - conf visor.Config + conf *visor.Config visor *visor.Visor restartCtx *restart.Context } -var cfg *runCfg +var conf *runConf var rootCmd = &cobra.Command{ Use: "skywire-visor [config-path]", @@ -64,9 +60,9 @@ var rootCmd = &cobra.Command{ log.Printf("Failed to output build info: %v", err) } - cfg.args = args + conf.args = args - cfg.startProfiler(). + conf.startProfiler(). startLogger(). readConfig(). runVisor(). @@ -77,15 +73,15 @@ var rootCmd = &cobra.Command{ } func init() { - cfg = &runCfg{} - rootCmd.Flags().StringVarP(&cfg.syslogAddr, "syslog", "", "none", "syslog server address. E.g. localhost:514") - rootCmd.Flags().StringVarP(&cfg.tag, "tag", "", "skywire", "logging tag") - rootCmd.Flags().BoolVarP(&cfg.cfgFromStdin, "stdin", "i", false, "read config from STDIN") - rootCmd.Flags().StringVarP(&cfg.profileMode, "profile", "p", "none", "enable profiling with pprof. Mode: none or one of: [cpu, mem, mutex, block, trace, http]") - rootCmd.Flags().StringVarP(&cfg.port, "port", "", "6060", "port for http-mode of pprof") - rootCmd.Flags().StringVarP(&cfg.startDelay, "delay", "", "0ns", "delay before visor start") - - cfg.restartCtx = restart.CaptureContext() + conf = new(runConf) + rootCmd.Flags().StringVarP(&conf.syslogAddr, "syslog", "", "none", "syslog server address. E.g. localhost:514") + rootCmd.Flags().StringVarP(&conf.tag, "tag", "", "skywire", "logging tag") + rootCmd.Flags().BoolVarP(&conf.confFromStdin, "stdin", "i", false, "read config from STDIN") + rootCmd.Flags().StringVarP(&conf.profileMode, "profile", "p", "none", "enable profiling with pprof. Mode: none or one of: [cpu, mem, mutex, block, trace, http]") + rootCmd.Flags().StringVarP(&conf.port, "port", "", "6060", "port for http-mode of pprof") + rootCmd.Flags().StringVarP(&conf.startDelay, "delay", "", "0ns", "delay before visor start") + + conf.restartCtx = restart.CaptureContext() } // Execute executes root CLI command. @@ -95,21 +91,21 @@ func Execute() { } } -func (cfg *runCfg) startProfiler() *runCfg { +func (rc *runConf) startProfiler() *runConf { var option func(*profile.Profile) - switch cfg.profileMode { + switch rc.profileMode { case "none": - cfg.profileStop = func() {} - return cfg + rc.profileStop = func() {} + return rc case "http": go func() { - log.Println(http.ListenAndServe(fmt.Sprintf("localhost:%v", cfg.port), nil)) + log.Println(http.ListenAndServe(fmt.Sprintf("localhost:%v", rc.port), nil)) }() - cfg.profileStop = func() {} + rc.profileStop = func() {} - return cfg + return rc case "cpu": option = profile.CPUProfile case "mem": @@ -122,154 +118,118 @@ func (cfg *runCfg) startProfiler() *runCfg { option = profile.TraceProfile } - cfg.profileStop = profile.Start(profile.ProfilePath("./logs/"+cfg.tag), option).Stop + rc.profileStop = profile.Start(profile.ProfilePath("./logs/"+rc.tag), option).Stop - return cfg + return rc } -func (cfg *runCfg) startLogger() *runCfg { - cfg.masterLogger = logging.NewMasterLogger() - cfg.logger = cfg.masterLogger.PackageLogger(cfg.tag) +func (rc *runConf) startLogger() *runConf { + rc.masterLogger = logging.NewMasterLogger() + rc.logger = rc.masterLogger.PackageLogger(rc.tag) - if cfg.syslogAddr != "none" { - hook, err := logrussyslog.NewSyslogHook("udp", cfg.syslogAddr, syslog.LOG_INFO, cfg.tag) + if rc.syslogAddr != "none" { + hook, err := logrussyslog.NewSyslogHook("udp", rc.syslogAddr, syslog.LOG_INFO, rc.tag) if err != nil { - cfg.logger.Error("Unable to connect to syslog daemon:", err) + rc.logger.Error("Unable to connect to syslog daemon:", err) } else { - cfg.masterLogger.AddHook(hook) - cfg.masterLogger.Out = ioutil.Discard + rc.masterLogger.AddHook(hook) + rc.masterLogger.Out = ioutil.Discard } } - return cfg + return rc } -func (cfg *runCfg) readConfig() *runCfg { - var rdr io.Reader - var configPath *string +func (rc *runConf) readConfig() *runConf { + var reader io.Reader + var confPath string - if !cfg.cfgFromStdin { - cp := pathutil.FindConfigPath(cfg.args, 0, configEnv, pathutil.VisorDefaults()) + if !rc.confFromStdin { + cp := pathutil.FindConfigPath(rc.args, 0, configEnv, pathutil.VisorDefaults()) file, err := os.Open(filepath.Clean(cp)) if err != nil { - cfg.logger.Fatalf("Failed to open config: %s", err) + rc.logger.WithError(err).Fatal("Failed to open config file.") } - defer func() { if err := file.Close(); err != nil { - cfg.logger.Warnf("Failed to close config file: %v", err) + rc.logger.WithError(err).Warn("Failed to close config file.") } }() - cfg.logger.Infof("Reading config from %v", cp) - - rdr = file - configPath = &cp + rc.logger.WithField("file", cp).Info("Reading config from file...") + reader = file + confPath = cp } else { - cfg.logger.Info("Reading config from STDIN") - rdr = bufio.NewReader(os.Stdin) + rc.logger.Info("Reading config from STDIN...") + reader = bufio.NewReader(os.Stdin) + confPath = "STDIN" } - raw, err := ioutil.ReadAll(rdr) - if err != nil { - cfg.logger.Fatalf("Failed to read config: %v", err) - } + rc.conf = visor.BaseConfig(rc.masterLogger, confPath) + dec := json.NewDecoder(reader) + dec.DisallowUnknownFields() - if err := json.Unmarshal(raw, &cfg.conf); err != nil { - cfg.logger.WithField("raw", string(raw)).Fatalf("Failed to decode config: %s", err) + if err := dec.Decode(rc.conf); err != nil { + rc.logger.WithError(err).Fatal("Failed to decode config.") } - - cfg.logger.Infof("Config: %#v", &cfg.conf) - - cfg.conf.Path = configPath - - return cfg + if err := rc.conf.Flush(); err != nil { + rc.logger.WithError(err).Fatal("Failed to flush config.") + } + return rc } -func (cfg *runCfg) runVisor() *runCfg { - startDelay, err := time.ParseDuration(cfg.startDelay) +func (rc *runConf) runVisor() *runConf { + startDelay, err := time.ParseDuration(rc.startDelay) if err != nil { - cfg.logger.Warnf("Using no visor start delay due to parsing failure: %v", err) + rc.logger.Warnf("Using no visor start delay due to parsing failure: %v", err) startDelay = time.Duration(0) } if startDelay != 0 { - cfg.logger.Infof("Visor start delay is %v, waiting...", startDelay) + rc.logger.Infof("Visor start delay is %v, waiting...", startDelay) } time.Sleep(startDelay) - if cfg.conf.DmsgPty != nil { - if err := visor.UnlinkSocketFiles(cfg.conf.DmsgPty.CLIAddr); err != nil { - cfg.logger.Fatal("failed to unlink socket files: ", err) + if rc.conf.Dmsgpty != nil { + if err := visor.UnlinkSocketFiles(rc.conf.Dmsgpty.CLIAddr); err != nil { + rc.logger.Fatal("failed to unlink socket files: ", err) } } - vis, err := visor.NewVisor(&cfg.conf, cfg.masterLogger, cfg.restartCtx) - if err != nil { - cfg.logger.Fatal("Failed to initialize visor: ", err) + v, ok := visor.NewVisor(rc.conf, rc.restartCtx) + if !ok { + rc.logger.Fatal("Failed to start visor.") } - if cfg.conf.UptimeTracker != nil { - uptimeTracker, err := utclient.NewHTTP(cfg.conf.UptimeTracker.Addr, cfg.conf.Keys().PubKey, cfg.conf.Keys().SecKey) - if err != nil { - cfg.logger.Error("Failed to connect to uptime tracker: ", err) - } else { - ticker := time.NewTicker(1 * time.Second) - - go func() { - for range ticker.C { - ctx := context.Background() - if err := uptimeTracker.UpdateVisorUptime(ctx); err != nil { - cfg.logger.Error("Failed to update visor uptime: ", err) - } - } - }() - } - } - - go func() { - if err := vis.Start(); err != nil { - cfg.logger.Fatal("Failed to start visor: ", err) - } - }() - - if cfg.conf.ShutdownTimeout == 0 { - cfg.conf.ShutdownTimeout = defaultShutdownTimeout - } - - cfg.visor = vis - - return cfg + rc.visor = v + return rc } -func (cfg *runCfg) stopVisor() *runCfg { - defer cfg.profileStop() +func (rc *runConf) stopVisor() *runConf { + defer rc.profileStop() - if err := cfg.visor.Close(); err != nil { - if !strings.Contains(err.Error(), "closed") { - cfg.logger.Fatal("Failed to close visor: ", err) - } + if err := rc.visor.Close(); err != nil { + rc.logger.WithError(err).Fatal("Failed to close visor.") } - - return cfg + return rc } -func (cfg *runCfg) waitOsSignals() *runCfg { +func (rc *runConf) waitOsSignals() *runConf { ch := make(chan os.Signal, 2) signal.Notify(ch, []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT}...) <-ch go func() { select { - case <-time.After(time.Duration(cfg.conf.ShutdownTimeout)): - cfg.logger.Fatal("Timeout reached: terminating") + case <-time.After(time.Duration(rc.conf.ShutdownTimeout)): + rc.logger.Fatal("Timeout reached: terminating") case s := <-ch: - cfg.logger.Fatalf("Received signal %s: terminating", s) + rc.logger.Fatalf("Received signal %s: terminating", s) } }() - return cfg + return rc } diff --git a/pkg/app/appcommon/proc_config.go b/pkg/app/appcommon/proc_config.go index d760cc2b2..70f8bcb3d 100644 --- a/pkg/app/appcommon/proc_config.go +++ b/pkg/app/appcommon/proc_config.go @@ -14,10 +14,6 @@ import ( ) const ( - // DefaultAppSrvAddr is the default address to run the app server at. - // TODO: Move to 'pkg/skyenv' - DefaultAppSrvAddr = "localhost:5505" - // EnvProcConfig is the env name which contains a JSON-encoded proc config. EnvProcConfig = "PROC_CONFIG" ) diff --git a/pkg/app/appnet/skywire_networker.go b/pkg/app/appnet/skywire_networker.go index 59c68c450..9074ee6f1 100644 --- a/pkg/app/appnet/skywire_networker.go +++ b/pkg/app/appnet/skywire_networker.go @@ -9,8 +9,9 @@ import ( "sync" "sync/atomic" + "github.com/sirupsen/logrus" + "github.com/SkycoinProject/dmsg/netutil" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" @@ -23,14 +24,14 @@ var ( // SkywireNetworker implements `Networker` for skynet. type SkywireNetworker struct { - log *logging.Logger + log logrus.FieldLogger r router.Router porter *netutil.Porter isServing int32 } // NewSkywireNetworker constructs skywire networker. -func NewSkywireNetworker(l *logging.Logger, r router.Router) Networker { +func NewSkywireNetworker(l logrus.FieldLogger, r router.Router) Networker { return &SkywireNetworker{ log: l, r: r, diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 1ac55fc64..bb1ef232d 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "strconv" + "strings" "sync" "sync/atomic" @@ -70,6 +71,11 @@ func (p *Proc) Logs() appcommon.LogStore { return p.logDB } +// Cmd returns the internal cmd name. +func (p *Proc) Cmd() *exec.Cmd { + return p.cmd +} + // InjectConn introduces the connection to the Proc after it is started. // Only the first call will return true. // It also prepares the RPC gateway. @@ -154,8 +160,8 @@ func (p *Proc) Start() error { p.waitErr = p.cmd.Wait() // Close proc conn and associated listeners and connections. - if err := p.conn.Close(); err != nil { - p.log.WithError(err).Warn("Closing proc conn returned non-nil error.") + if err := p.conn.Close(); err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + p.log.WithError(err).Warn("Closing proc conn returned unexpected error.") } p.rpcGW.cm.CloseAll() p.rpcGW.lm.CloseAll() diff --git a/pkg/app/appserver/proc_manager.go b/pkg/app/appserver/proc_manager.go index 9778e424a..5462cfadf 100644 --- a/pkg/app/appserver/proc_manager.go +++ b/pkg/app/appserver/proc_manager.go @@ -260,8 +260,8 @@ func (m *procManager) Range(next func(name string, proc *Proc) bool) { func (m *procManager) stopAll() { for name, proc := range m.procs { log := m.log.WithField("app_name", name) - if err := proc.Stop(); err != nil && strings.Contains(err.Error(), "process already finished") { - log.WithError(err).Error("Failed to stop app.") + if err := proc.Stop(); err != nil && !strings.Contains(err.Error(), "process already finished") { + log.WithError(err).Error("App stopped with unexpected error.") continue } log.Infof("App stopped successfully.") diff --git a/pkg/app/launcher/app_state.go b/pkg/app/launcher/app_state.go new file mode 100644 index 000000000..14a3d4f3a --- /dev/null +++ b/pkg/app/launcher/app_state.go @@ -0,0 +1,18 @@ +package launcher + +// AppStatus defines running status of an App. +type AppStatus int + +const ( + // AppStatusStopped represents status of a stopped App. + AppStatusStopped AppStatus = iota + + // AppStatusRunning represents status of a running App. + AppStatusRunning +) + +// AppState defines state parameters for a registered App. +type AppState struct { + AppConfig + Status AppStatus `json:"status"` +} diff --git a/pkg/app/launcher/launcher.go b/pkg/app/launcher/launcher.go new file mode 100644 index 000000000..a31164107 --- /dev/null +++ b/pkg/app/launcher/launcher.go @@ -0,0 +1,369 @@ +package launcher + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "syscall" + + "github.com/SkycoinProject/dmsg" + "github.com/SkycoinProject/dmsg/cipher" + "github.com/sirupsen/logrus" + + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" + "github.com/SkycoinProject/skywire-mainnet/pkg/router" + "github.com/SkycoinProject/skywire-mainnet/pkg/routing" + "github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil" +) + +// Launcher associated errors. +var ( + ErrAppNotFound = errors.New("app not found") + ErrAppNotRunning = errors.New("app not running") +) + +// AppConfig defines app startup parameters. +type AppConfig struct { + Name string `json:"name"` + Args []string `json:"args,omitempty"` + AutoStart bool `json:"auto_start"` + Port routing.Port `json:"port"` +} + +// Config configures the launcher. +type Config struct { + VisorPK cipher.PubKey + Apps []AppConfig + ServerAddr string + BinPath string + LocalPath string +} + +// Launcher is responsible for launching and keeping track of app states. +type Launcher struct { + conf Config + log logrus.FieldLogger + r router.Router + procM appserver.ProcManager + apps map[string]AppConfig + mx sync.Mutex +} + +// NewLauncher creates a new launcher. +func NewLauncher(log logrus.FieldLogger, conf Config, dmsgC *dmsg.Client, r router.Router, procM appserver.ProcManager) (*Launcher, error) { + launcher := &Launcher{ + conf: conf, + log: log, + r: r, + procM: procM, + } + + // Ensure the existence of directories. + if err := ensureDir(&launcher.conf.BinPath); err != nil { + return nil, err + } + if err := ensureDir(&launcher.conf.LocalPath); err != nil { + return nil, err + } + + // Prepare networks. + skyN := appnet.NewSkywireNetworker(log.WithField("_", appnet.TypeSkynet), r) + if err := appnet.AddNetworker(appnet.TypeSkynet, skyN); err != nil { + return nil, err + } + dmsgN := appnet.NewDMSGNetworker(dmsgC) + if err := appnet.AddNetworker(appnet.TypeDmsg, dmsgN); err != nil { + return nil, err + } + + // Kill old processes. + if err := launcher.killHangingProcesses(); err != nil { + return nil, err + } + + // Prepare apps (autostart if necessary). + apps := make(map[string]AppConfig, len(conf.Apps)) + for _, ac := range conf.Apps { + apps[ac.Name] = ac + } + launcher.apps = apps + + return launcher, nil +} + +// ResetConfig resets the launcher config. +func (l *Launcher) ResetConfig(conf Config) { + l.mx.Lock() + defer l.mx.Unlock() + + apps := make(map[string]AppConfig, len(conf.Apps)) + for _, ac := range conf.Apps { + apps[ac.Name] = ac + } + l.apps = apps + l.conf = conf +} + +// AutoStart auto-starts marked apps. +func (l *Launcher) AutoStart(envMap map[string]func() []string) { + if envMap == nil { + envMap = make(map[string]func() []string) + } + log := l.log.WithField("func", "AutoStart") + + l.mx.Lock() + defer l.mx.Unlock() + + for name, ac := range l.apps { + if !ac.AutoStart { + continue + } + var envs []string + if makeEnvs, ok := envMap[name]; ok { + envs = makeEnvs() + } + if err := l.startApp(name, ac.Args, envs); err != nil { + log.WithError(err). + WithField("app_name", name). + WithField("args", ac.Args). + WithField("envs", envs). + Warn("Failed to start app.") + } + } +} + +// AppState returns a single app state of given name. +func (l *Launcher) AppState(name string) (*AppState, bool) { + l.mx.Lock() + defer l.mx.Unlock() + + ac, ok := l.apps[name] + if !ok { + return nil, false + } + state := &AppState{AppConfig: ac, Status: AppStatusStopped} + if _, ok := l.procM.ProcByName(ac.Name); ok { + state.Status = AppStatusRunning + } + return state, true +} + +// AppStates returns list of AppStates for all registered apps. +func (l *Launcher) AppStates() []*AppState { + l.mx.Lock() + defer l.mx.Unlock() + + var states []*AppState + for _, app := range l.apps { + state := &AppState{AppConfig: app, Status: AppStatusStopped} + if _, ok := l.procM.ProcByName(app.Name); ok { + state.Status = AppStatusRunning + } + states = append(states, state) + } + return states +} + +// StartApp starts cmd with given args and env. +// If 'args' is nil, default args will be used. +func (l *Launcher) StartApp(cmd string, args, envs []string) error { + l.mx.Lock() + defer l.mx.Unlock() + + return l.startApp(cmd, args, envs) +} + +func (l *Launcher) startApp(cmd string, args, envs []string) error { + log := l.log.WithField("func", "StartApp").WithField("cmd", cmd) + + // Obtain associated app config. + ac, ok := l.apps[cmd] + if !ok { + return ErrAppNotFound + } + if args != nil { + ac.Args = args + } + + // Make proc config. + procConf, err := makeProcConfig(l.conf, ac, envs) + if err != nil { + return err + } + + // Start proc and persist pid. + pid, err := l.procM.Start(procConf) + if err != nil { + return err + } + if err := l.persistPID(cmd, pid); err != nil { + log.WithError(err).Warn("Failed to persist pid.") + } + + return nil +} + +// StopApp stops running app. +func (l *Launcher) StopApp(name string) (*appserver.Proc, error) { + log := l.log.WithField("func", "StopApp").WithField("app_name", name) + + proc, ok := l.procM.ProcByName(name) + if !ok { + return nil, ErrAppNotRunning + } + + l.log.Info("Stopping app...") + + if err := l.procM.Stop(name); err != nil { + log.WithError(err).Warn("Failed to stop app.") + return proc, err + } + + return proc, nil +} + +// RestartApp restarts a running app. +func (l *Launcher) RestartApp(name string) error { + l.log.WithField("func", "RestartApp").WithField("app_name", name). + Info("Restarting app...") + + proc, err := l.StopApp(name) + if err != nil { + return fmt.Errorf("failed to stop %s: %w", name, err) + } + + cmd := proc.Cmd() + if err := l.StartApp(name, cmd.Args, cmd.Env); err != nil { + return fmt.Errorf("failed to start %s: %w", name, err) + } + + return nil +} + +func makeProcConfig(lc Config, ac AppConfig, envs []string) (appcommon.ProcConfig, error) { + procConf := appcommon.ProcConfig{ + AppName: ac.Name, + AppSrvAddr: lc.ServerAddr, + ProcKey: appcommon.RandProcKey(), + ProcArgs: ac.Args, + ProcEnvs: envs, + ProcWorkDir: filepath.Join(lc.LocalPath, ac.Name), + VisorPK: lc.VisorPK, + RoutingPort: ac.Port, + BinaryLoc: filepath.Join(lc.BinPath, ac.Name), + LogDBLoc: filepath.Join(lc.LocalPath, ac.Name+"_log.db"), + } + err := ensureDir(&procConf.ProcWorkDir) + return procConf, err +} + +func ensureDir(path *string) error { + var err error + if *path, err = filepath.Abs(*path); err != nil { + return fmt.Errorf("failed to expand path: %s", err) + } + if _, err := os.Stat(*path); !os.IsNotExist(err) { + return nil + } + if err := os.MkdirAll(*path, 0750); err != nil { + return fmt.Errorf("failed to create dir: %s", err) + } + return nil +} + +/* + <<< PID management >>> +*/ + +func (l *Launcher) pidFile() (*os.File, error) { + return os.OpenFile(filepath.Join(l.conf.LocalPath, "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) +} + +func (l *Launcher) persistPID(appName string, pid appcommon.ProcID) error { + log := l.log. + WithField("func", "persistPID"). + WithField("app_name", appName). + WithField("pid", pid) + + pidF, err := l.pidFile() + if err != nil { + return err + } + pidFName := pidF.Name() + log = log.WithField("pid_file", pidFName) + + if err := pidF.Close(); err != nil { + log.WithError(err).Warn("Failed to close PID file.") + } + + data := fmt.Sprintf("%s %d\n", appName, pid) + if err := pathutil.AtomicAppendToFile(pidFName, []byte(data)); err != nil { + log.WithError(err).Warn("Failed to save PID to file.") + } + + return nil +} + +func (l *Launcher) killHangingProcesses() error { + log := l.log.WithField("func", "killHangingProcesses") + + pidF, err := l.pidFile() + if err != nil { + return err + } + defer func() { + if err := pidF.Close(); err != nil { + log.WithError(err).Warn("Error closing PID file.") + } + }() + log = log.WithField("pid_file", pidF.Name()) + + scan := bufio.NewScanner(pidF) + for scan.Scan() { + appInfo := strings.Split(scan.Text(), " ") + if len(appInfo) != 2 { + err := errors.New("line should be: [app name] [pid]") + log.WithError(err).Fatal("Failed parsing pid file.") + } + + pid, err := strconv.Atoi(appInfo[1]) + if err != nil { + log.WithError(err).Fatal("Failed parsing pid file.") + } + + l.killHangingProc(appInfo[0], pid) + } + + // empty file + if err := pathutil.AtomicWriteFile(pidF.Name(), []byte{}); err != nil { + log.WithError(err).Error("Failed to empty pid file.") + } + + return nil +} + +func (l *Launcher) killHangingProc(appName string, pid int) { + log := l.log.WithField("app_name", appName).WithField("pid", pid) + + p, err := os.FindProcess(pid) + if err != nil { + if runtime.GOOS != "windows" { + log.Info("Process not found.") + } + return + } + + err = p.Signal(syscall.SIGKILL) + if err != nil { + return + } + log.Info("Killed hanging child process that ran previously with this visor.") +} diff --git a/pkg/hypervisor/config.go b/pkg/hypervisor/config.go index 7de1dcfd2..ff74013b0 100644 --- a/pkg/hypervisor/config.go +++ b/pkg/hypervisor/config.go @@ -48,7 +48,7 @@ func (hk *Key) UnmarshalText(text []byte) error { type Config struct { PK cipher.PubKey `json:"public_key"` SK cipher.SecKey `json:"secret_key"` - DBPath string `json:"db_path"` // Path to store database file. + DBPath string `json:"db_path"` // path to store database file. EnableAuth bool `json:"enable_auth"` // Whether to enable user management. Cookies CookieConfig `json:"cookies"` // Configures cookies (for session management). DmsgDiscovery string `json:"dmsg_discovery"` // Dmsg discovery address. diff --git a/pkg/hypervisor/hypervisor.go b/pkg/hypervisor/hypervisor.go index b4d2b7419..23e1d342d 100644 --- a/pkg/hypervisor/hypervisor.go +++ b/pkg/hypervisor/hypervisor.go @@ -13,6 +13,8 @@ import ( "sync" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/dmsgpty" @@ -388,7 +390,7 @@ func (hv *Hypervisor) putApp() http.HandlerFunc { if reqBody.AutoStart != nil { if *reqBody.AutoStart != ctx.App.AutoStart { - if err := ctx.RPC.SetAutoStart(ctx.App.App, *reqBody.AutoStart); err != nil { + if err := ctx.RPC.SetAutoStart(ctx.App.Name, *reqBody.AutoStart); err != nil { httputil.WriteJSON(w, r, http.StatusInternalServerError, err) return } @@ -400,14 +402,14 @@ func (hv *Hypervisor) putApp() http.HandlerFunc { skysocksClientName = "skysocks-client" ) - if reqBody.Passcode != nil && ctx.App.App == skysocksName { + if reqBody.Passcode != nil && ctx.App.Name == skysocksName { if err := ctx.RPC.SetSocksPassword(*reqBody.Passcode); err != nil { httputil.WriteJSON(w, r, http.StatusInternalServerError, err) return } } - if reqBody.PK != nil && ctx.App.App == skysocksClientName { + if reqBody.PK != nil && ctx.App.Name == skysocksClientName { log.Errorf("SETTING PK: %s", *reqBody.PK) if err := ctx.RPC.SetSocksClientPK(*reqBody.PK); err != nil { log.Errorf("ERROR SETTING PK") @@ -419,12 +421,12 @@ func (hv *Hypervisor) putApp() http.HandlerFunc { if reqBody.Status != nil { switch *reqBody.Status { case statusStop: - if err := ctx.RPC.StopApp(ctx.App.App); err != nil { + if err := ctx.RPC.StopApp(ctx.App.Name); err != nil { httputil.WriteJSON(w, r, http.StatusInternalServerError, err) return } case statusStart: - if err := ctx.RPC.StartApp(ctx.App.App); err != nil { + if err := ctx.RPC.StartApp(ctx.App.Name); err != nil { log.Errorf("ERROR STARTING APP") httputil.WriteJSON(w, r, http.StatusInternalServerError, err) return @@ -457,7 +459,7 @@ func (hv *Hypervisor) appLogsSince() http.HandlerFunc { t = time.Unix(0, 0) } - logs, err := ctx.RPC.LogsSince(t, ctx.App.App) + logs, err := ctx.RPC.LogsSince(t, ctx.App.Name) if err != nil { httputil.WriteJSON(w, r, http.StatusInternalServerError, err) return @@ -819,7 +821,7 @@ type httpCtx struct { VisorConn // App - App *visor.AppState + App *launcher.AppState // Transport Tp *visor.TransportSummary @@ -875,7 +877,7 @@ func (hv *Hypervisor) appCtx(w http.ResponseWriter, r *http.Request) (*httpCtx, } for _, a := range apps { - if a.App == appName { + if a.Name == appName { ctx.App = a return ctx, true } diff --git a/pkg/skyenv/values.go b/pkg/skyenv/values.go index 3b8036866..ea52de9b5 100644 --- a/pkg/skyenv/values.go +++ b/pkg/skyenv/values.go @@ -37,8 +37,14 @@ const ( const ( DmsgPtyPort = uint16(22) - DefaultDmsgPtyCLINet = "unix" - DefaultDmsgPtyCLIAddr = "/tmp/dmsgpty.sock" + DefaultDmsgPtyCLINet = "unix" + DefaultDmsgPtyCLIAddr = "/tmp/dmsgpty.sock" + DefaultDmsgPtyWhitelist = "./dmsgpty/whitelist.json" +) + +// Default STCP constants. +const ( + DefaultSTCPAddr = ":7777" ) // Default skywire app constants. @@ -64,12 +70,22 @@ const ( // RPC constants. const ( + DefaultRPCAddr = "localhost:3435" DefaultRPCTimeout = time.Second * 5 ) -// Default skywire app discovery constants +// Default skywire app server and discovery constants const ( + DefaultAppSrvAddr = "localhost:5505" AppDiscUpdateInterval = time.Second * 30 + DefaultAppLocalPath = "./local" + DefaultAppBinPath = "./apps" + DefaultLogLevel = "info" +) + +// Default routing constants +const ( + DefaultTpLogStore = "./transport_logs" ) // MustPK unmarshals string PK to cipher.PubKey. It panics if unmarshaling fails. diff --git a/pkg/snet/network.go b/pkg/snet/network.go index ba3e915dd..6dca166d1 100644 --- a/pkg/snet/network.go +++ b/pkg/snet/network.go @@ -55,8 +55,8 @@ func (c *DmsgConfig) Type() string { // STCPConfig defines config for STCP network. type STCPConfig struct { - PubKeyTable map[cipher.PubKey]string `json:"pk_table"` - LocalAddr string `json:"local_address"` + PKTable map[cipher.PubKey]string `json:"pk_table"` + LocalAddr string `json:"local_address"` } // Type returns STCPType. @@ -68,8 +68,8 @@ func (c *STCPConfig) Type() string { type Config struct { PubKey cipher.PubKey SecKey cipher.SecKey - Dmsg *DmsgConfig - STCP *STCPConfig + Dmsg *DmsgConfig // The dmsg service will not be started if nil. + STCP *STCPConfig // The stcp service will not be started if nil. } // Network represents a network between nodes in Skywire. @@ -95,7 +95,7 @@ func New(conf Config) *Network { } if conf.STCP != nil { - stcpC = stcp.NewClient(conf.PubKey, conf.SecKey, stcp.NewTable(conf.STCP.PubKeyTable)) + stcpC = stcp.NewClient(conf.PubKey, conf.SecKey, stcp.NewTable(conf.STCP.PKTable)) stcpC.SetLogger(logging.MustGetLogger("snet.stcpC")) } @@ -123,7 +123,7 @@ func NewRaw(conf Config, dmsgC *dmsg.Client, stcpC *stcp.Client) *Network { } // Init initiates server connections. -func (n *Network) Init(_ context.Context) error { +func (n *Network) Init() error { if n.dmsgC != nil { time.Sleep(200 * time.Millisecond) go n.dmsgC.Serve() diff --git a/pkg/snet/snettest/env.go b/pkg/snet/snettest/env.go index 65be604db..0ccc61f94 100644 --- a/pkg/snet/snettest/env.go +++ b/pkg/snet/snettest/env.go @@ -1,7 +1,6 @@ package snettest import ( - "context" "strconv" "testing" @@ -105,7 +104,7 @@ func NewEnv(t *testing.T, keys []KeyPair, networks []string) *Env { dmsgClient, stcpClient, ) - require.NoError(t, n.Init(context.TODO())) + require.NoError(t, n.Init()) ns[i] = n } diff --git a/pkg/transport-discovery/client/client.go b/pkg/transport/tpdclient/client.go similarity index 98% rename from pkg/transport-discovery/client/client.go rename to pkg/transport/tpdclient/client.go index 6748bf755..128863623 100644 --- a/pkg/transport-discovery/client/client.go +++ b/pkg/transport/tpdclient/client.go @@ -1,5 +1,5 @@ -// Package client implements transport discovery client -package client +// Package tpdclient implements transport discovery client +package tpdclient import ( "bytes" diff --git a/pkg/transport-discovery/client/client_test.go b/pkg/transport/tpdclient/client_test.go similarity index 99% rename from pkg/transport-discovery/client/client_test.go rename to pkg/transport/tpdclient/client_test.go index 6670630e1..d977f1714 100644 --- a/pkg/transport-discovery/client/client_test.go +++ b/pkg/transport/tpdclient/client_test.go @@ -1,4 +1,4 @@ -package client +package tpdclient import ( "bytes" diff --git a/pkg/visor/config.go b/pkg/visor/config.go index 1883c0b78..8ff8d509d 100644 --- a/pkg/visor/config.go +++ b/pkg/visor/config.go @@ -5,39 +5,30 @@ import ( "errors" "fmt" "io/ioutil" - "net" - "os" - "path/filepath" "sync" "time" - "github.com/SkycoinProject/dmsg" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/skywire-mainnet/pkg/restart" + "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/dmsg/dmsgpty" "github.com/SkycoinProject/skycoin/src/util/logging" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" - "github.com/SkycoinProject/skywire-mainnet/pkg/transport" - trClient "github.com/SkycoinProject/skywire-mainnet/pkg/transport-discovery/client" ) //go:generate readmegen -n Config -o ./README.md ./config.go const ( // DefaultTimeout is used for default config generation and if it is not set in config. + // TODO: Put this in skyenv. DefaultTimeout = Duration(10 * time.Second) - // DefaultLocalPath is used for default config generation and if it is not set in config. - DefaultLocalPath = "./local" - // DefaultAppsPath is used for default config generation and if it is not set in config. - DefaultAppsPath = "./apps" - // DefaultLogLevel is used for default config generation and if it is not set in config. - DefaultLogLevel = "info" - // DefaultSTCPPort ??? - // TODO: Define above or remove below. - DefaultSTCPPort = 7777 + + // ConfigVersion of the visor config. + // TODO: Put this in skyenv? + ConfigVersion = "v1.0.0" ) var ( @@ -47,29 +38,22 @@ var ( // Config defines configuration parameters for Visor. type Config struct { - Path *string `json:"-"` - log *logging.Logger - flushMu sync.Mutex + path string + log *logging.MasterLogger + mu sync.RWMutex Version string `json:"version"` KeyPair *KeyPair `json:"key_pair"` Dmsg *snet.DmsgConfig `json:"dmsg"` - DmsgPty *DmsgPtyConfig `json:"dmsg_pty,omitempty"` + Dmsgpty *DmsgptyConfig `json:"dmsgpty,omitempty"` STCP *snet.STCPConfig `json:"stcp,omitempty"` Transport *TransportConfig `json:"transport"` Routing *RoutingConfig `json:"routing"` UptimeTracker *UptimeTrackerConfig `json:"uptime_tracker,omitempty"` + Launcher *LauncherConfig `json:"launcher"` - AppDiscovery *AppDiscConfig `json:"app_discovery,omitempty"` - Apps []AppConfig `json:"apps"` - AppServerAddr string `json:"app_server_addr"` - AppsPath string `json:"apps_path"` - LocalPath string `json:"local_path"` - - TrustedVisors []cipher.PubKey `json:"trusted_visors"` - Hypervisors []HypervisorConfig `json:"hypervisors"` - - Interfaces *InterfaceConfig `json:"interfaces"` + Hypervisors []HypervisorConfig `json:"hypervisors"` + CLIAddr string `json:"cli_addr"` LogLevel string `json:"log_level"` ShutdownTimeout Duration `json:"shutdown_timeout,omitempty"` // time value, examples: 10s, 1m, etc @@ -77,228 +61,247 @@ type Config struct { } // Flush flushes config to file. -func (c *Config) flush() error { - c.flushMu.Lock() - defer c.flushMu.Unlock() +func (c *Config) Flush() error { + c.mu.Lock() + defer c.mu.Unlock() - if c.Path == nil { + return c.flush() +} + +func (c *Config) flush() error { + switch c.path { + case "": return ErrNoConfigPath + case "STDIN": + return nil + default: } - c.log.Infof("Updating visor config to %#v", c) + j, err := json.Marshal(c) + if err != nil { + panic(err) + } + c.log.Debugf("Updating visor config to: %s", string(j)) bytes, err := json.MarshalIndent(c, "", "\t") if err != nil { return err } - const filePerm = 0644 - return ioutil.WriteFile(*c.Path, bytes, filePerm) -} - -// Keys returns visor public and secret keys extracted from config. -// If they are not found, new keys are generated. -func (c *Config) Keys() *KeyPair { - // If both keys are set, no additional action is needed. - if c.KeyPair != nil && !c.KeyPair.SecKey.Null() && !c.KeyPair.PubKey.Null() { - return c.KeyPair + return ioutil.WriteFile(c.path, bytes, filePerm) +} + +// BaseConfig returns a visor config with 'enforced' fields only. +// This is used as default values if no config is given, or for missing *required* fields. +func BaseConfig(log *logging.MasterLogger, configPath string) *Config { + if log == nil { + log = logging.NewMasterLogger() + } + conf := &Config{ + path: configPath, + log: log, + Version: ConfigVersion, + Dmsg: &snet.DmsgConfig{ + Discovery: skyenv.DefaultDmsgDiscAddr, + SessionsCount: 1, + }, + Transport: &TransportConfig{ + Discovery: skyenv.DefaultTpDiscAddr, + LogStore: &LogStoreConfig{ + Type: LogStoreMemory, + }, + }, + Routing: &RoutingConfig{ + SetupNodes: []cipher.PubKey{skyenv.MustPK(skyenv.DefaultSetupPK)}, + RouteFinder: skyenv.DefaultRouteFinderAddr, + RouteFinderTimeout: DefaultTimeout, + }, + Launcher: &LauncherConfig{ + Discovery: nil, + Apps: nil, + ServerAddr: skyenv.DefaultAppSrvAddr, + BinPath: skyenv.DefaultAppBinPath, + LocalPath: skyenv.DefaultAppLocalPath, + }, + CLIAddr: skyenv.DefaultRPCAddr, + LogLevel: skyenv.DefaultLogLevel, + ShutdownTimeout: DefaultTimeout, + RestartCheckDelay: restart.DefaultCheckDelay.String(), // TODO: Use Duration type. + } + return conf +} + +// DefaultConfig returns the default visor config from a given key pair (if specified). +// The config's key_pair field will be nil if not specified. +// Generated config will be saved to 'configPath' +func DefaultConfig(log *logging.MasterLogger, configPath string, keys *KeyPair) (*Config, error) { + conf := BaseConfig(log, configPath) + conf.Dmsgpty = &DmsgptyConfig{ + Port: skyenv.DmsgPtyPort, + AuthFile: skyenv.DefaultDmsgPtyWhitelist, + CLINet: skyenv.DefaultDmsgPtyCLINet, + CLIAddr: skyenv.DefaultDmsgPtyCLIAddr, } - - // If either no keys are set or SecKey is not set, a new key pair is generated. - if c.KeyPair == nil || c.KeyPair.SecKey.Null() { - c.KeyPair = NewKeyPair() + conf.STCP = &snet.STCPConfig{ + LocalAddr: skyenv.DefaultSTCPAddr, + PKTable: nil, } - - // If SecKey is set and PubKey is not set, PubKey can be generated from SecKey. - if !c.KeyPair.SecKey.Null() && c.KeyPair.PubKey.Null() { - pk, err := c.KeyPair.SecKey.PubKey() - if err != nil { - // If generation of PubKey from SecKey fails, a new key pair is generated. - c.KeyPair = NewKeyPair() - } else { - c.KeyPair.PubKey = pk - } - } - - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") + conf.Transport.LogStore = &LogStoreConfig{ + Type: LogStoreFile, + Location: skyenv.DefaultTpLogStore, } - - return c.KeyPair -} - -// DmsgConfig extracts and returns DmsgConfig from Visor Config. -// If it is not found, it sets DefaultDmsgConfig() as RoutingConfig and returns it. -func (c *Config) DmsgConfig() *snet.DmsgConfig { - if c.Dmsg == nil { - c.Dmsg = DefaultDmsgConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } + conf.UptimeTracker = &UptimeTrackerConfig{ + Addr: skyenv.DefaultUptimeTrackerAddr, } - - return c.Dmsg -} - -// DmsgPtyHost extracts DmsgPtyConfig and returns *dmsgpty.Host based on the config. -// If DmsgPtyConfig is not found, DefaultDmsgPtyConfig() is used. -func (c *Config) DmsgPtyHost(dmsgC *dmsg.Client) (*dmsgpty.Host, error) { - if c.DmsgPty == nil { - c.DmsgPty = DefaultDmsgPtyConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } + conf.Launcher.Discovery = &AppDiscConfig{ + UpdateInterval: Duration(skyenv.AppDiscUpdateInterval), + ProxyDisc: skyenv.DefaultProxyDiscAddr, } - - var wl dmsgpty.Whitelist - if c.DmsgPty.AuthFile == "" { - wl = dmsgpty.NewMemoryWhitelist() - } else { - var err error - if wl, err = dmsgpty.NewJSONFileWhiteList(c.DmsgPty.AuthFile); err != nil { - return nil, err + conf.Launcher.Apps = []launcher.AppConfig{ + { + Name: skyenv.SkychatName, + AutoStart: true, + Port: routing.Port(skyenv.SkychatPort), + Args: []string{"-addr", skyenv.SkychatAddr}, + }, + { + Name: skyenv.SkysocksName, + AutoStart: true, + Port: routing.Port(skyenv.SkysocksPort), + }, + { + Name: skyenv.SkysocksClientName, + AutoStart: false, + Port: routing.Port(skyenv.SkysocksClientPort), + }, + { + Name: skyenv.VPNServerName, + AutoStart: true, + Port: routing.Port(skyenv.VPNServerPort), + }, + { + Name: skyenv.VPNClientName, + AutoStart: false, + Port: routing.Port(skyenv.VPNClientPort), + }, + } + if keys != nil { + conf.KeyPair = keys + conf.ensureKeys() + } + return conf, conf.Flush() +} + +// UpdateAppAutostart modifies a single app's autostart value within the config and also the given launcher. +// The updated config gets flushed to file is there are any changes. +func (c *Config) UpdateAppAutostart(launch *launcher.Launcher, appName string, autoStart bool) error { + c.mu.Lock() + defer c.mu.Unlock() + + conf := c.Launcher + + changed := false + for i := range conf.Apps { + if conf.Apps[i].Name == appName { + conf.Apps[i].AutoStart = autoStart + changed = true + break } } - // Whitelist hypervisor PKs. - hypervisorWL := dmsgpty.NewMemoryWhitelist() - for _, hv := range c.Hypervisors { - if err := hypervisorWL.Add(hv.PubKey); err != nil { - return nil, fmt.Errorf("failed to add hypervisor PK to whitelist: %v", err) - } + if !changed { + return nil } - host := dmsgpty.NewHost(dmsgC, dmsgpty.NewCombinedWhitelist(0, wl, hypervisorWL)) - return host, nil + launch.ResetConfig(launcher.Config{ + VisorPK: c.KeyPair.PubKey, + Apps: conf.Apps, + ServerAddr: conf.ServerAddr, + BinPath: conf.BinPath, + LocalPath: conf.LocalPath, + }) + return c.flush() } -// TransportDiscovery extracts TransportConfig and returns transport.DiscoveryClient based on the config. -// If TransportConfig is not found, DefaultTransportConfig() is used. -func (c *Config) TransportDiscovery() (transport.DiscoveryClient, error) { - if c.Transport == nil { - c.Transport = DefaultTransportConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } - } +// UpdateAppArg updates the cli flag of the specified app config and also within the launcher. +// The updated config gets flushed to file is there are any changes. +func (c *Config) UpdateAppArg(launch *launcher.Launcher, appName, argName, value string) error { + c.mu.Lock() + defer c.mu.Unlock() - return trClient.NewHTTP(c.Transport.Discovery, c.Keys().PubKey, c.Keys().SecKey) -} + conf := c.Launcher -// TransportLogStore extracts LogStoreConfig and returns transport.LogStore based on the config. -// If LogStoreConfig is not found, DefaultLogStoreConfig() is used. -func (c *Config) TransportLogStore() (transport.LogStore, error) { - if c.Transport == nil { - c.Transport = DefaultTransportConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } - } else if c.Transport.LogStore == nil { - c.Transport.LogStore = DefaultLogStoreConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } - } - - if c.Transport.LogStore.Type == LogStoreFile { - return transport.FileTransportLogStore(c.Transport.LogStore.Location) - } - - return transport.InMemoryTransportLogStore(), nil -} - -// RoutingConfig extracts and returns RoutingConfig from Visor Config. -// If it is not found, it sets DefaultRoutingConfig() as RoutingConfig and returns it. -func (c *Config) RoutingConfig() *RoutingConfig { - if c.Routing == nil { - c.Routing = DefaultRoutingConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } - } - - return c.Routing -} + configChanged := true + for i := range conf.Apps { + if conf.Apps[i].Name == appName { + configChanged = true -// AppDiscConfig extracts and returns AppDiscConfig from visor config. -// If it is not found, it sets it as DefaultAppDisConfig() and returns it. -func (c *Config) AppDiscConfig() *AppDiscConfig { - if c.AppDiscovery == nil { - c.AppDiscovery = DefaultAppDiscConfig() - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") + argChanged := false + for j := range conf.Apps[i].Args { + if conf.Apps[i].Args[j] == argName && j+1 < len(conf.Apps[i].Args) { + conf.Apps[i].Args[j+1] = value + argChanged = true + break + } + } + if !argChanged { + conf.Apps[i].Args = append(conf.Apps[i].Args, argName, value) + } } } - return c.AppDiscovery -} - -// AppsConfig decodes AppsConfig from a local json config file. -func (c *Config) AppsConfig() (map[string]AppConfig, error) { - apps := make(map[string]AppConfig) - for _, app := range c.Apps { - apps[app.App] = app + if !configChanged { + return nil } - return apps, nil + launch.ResetConfig(launcher.Config{ + VisorPK: c.KeyPair.PubKey, + Apps: conf.Apps, + ServerAddr: conf.ServerAddr, + BinPath: conf.BinPath, + LocalPath: conf.LocalPath, + }) + return c.flush() } -// AppsDir returns absolute path for directory with application binaries. -// Directory will be created if necessary. -// If it is not set in config, DefaultAppsPath is used. -func (c *Config) AppsDir() (string, error) { - if c.AppsPath == "" { - c.AppsPath = DefaultAppsPath - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } - } - - return ensureDir(c.AppsPath) -} +// Keys returns visor public and secret keys extracted from config. +// If they are not found, new keys are generated. +func (c *Config) Keys() *KeyPair { + c.mu.Lock() + defer c.mu.Unlock() -// LocalDir returns absolute path for app work directory. -// Directory will be created if necessary. -// If it is not set in config, DefaultLocalPath is used. -func (c *Config) LocalDir() (string, error) { - if c.LocalPath == "" { - c.LocalPath = DefaultLocalPath - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } + if changed := c.ensureKeys(); !changed { + return c.KeyPair } - - return ensureDir(c.LocalPath) -} - -// AppServerAddress extracts and returns AppServerAddr from Visor Config. -// If it is not found, it sets appcommon.DefaultAppSrvAddr as AppServerAddr and returns it. -func (c *Config) AppServerAddress() string { - if c.AppServerAddr == "" { - c.AppServerAddr = appcommon.DefaultAppSrvAddr - if err := c.flush(); err != nil && c.log != nil { - c.log.WithError(err).Errorf("Failed to flush config to disk") - } + if err := c.flush(); err != nil && c.log != nil { + c.log.WithError(err).Errorf("Failed to Flush config to disk") } - - return c.AppServerAddr + return c.KeyPair } -func ensureDir(path string) (string, error) { - absPath, err := filepath.Abs(path) - if err != nil { - return "", fmt.Errorf("failed to expand path: %s", err) +func (c *Config) ensureKeys() (changed bool) { + // If both keys are set, no additional action is needed. + if c.KeyPair != nil && !c.KeyPair.SecKey.Null() && !c.KeyPair.PubKey.Null() { + return false } - if _, err := os.Stat(absPath); !os.IsNotExist(err) { - return absPath, nil + // If either no keys are set or SecKey is not set, a new key pair is generated. + if c.KeyPair == nil || c.KeyPair.SecKey.Null() { + c.KeyPair = NewKeyPair() } - if err := os.MkdirAll(absPath, 0750); err != nil { - return "", fmt.Errorf("failed to create dir: %s", err) + // If SecKey is set and PubKey is not set, PubKey can be generated from SecKey. + if !c.KeyPair.SecKey.Null() && c.KeyPair.PubKey.Null() { + pk, err := c.KeyPair.SecKey.PubKey() + if err != nil { + // If generation of PubKey from SecKey fails, a new key pair is generated. + c.KeyPair = NewKeyPair() + } else { + c.KeyPair.PubKey = pk + } } - return absPath, nil + return true } // KeyPair defines Visor public and secret key pair. @@ -326,58 +329,19 @@ func RestoreKeyPair(sk cipher.SecKey) *KeyPair { return &KeyPair{PubKey: pk, SecKey: sk} } -// DefaultSTCPConfig returns default STCP config. -func DefaultSTCPConfig() (*snet.STCPConfig, error) { - lIPaddr, err := getLocalIPAddress() - if err != nil { - return nil, err - } - - c := &snet.STCPConfig{ - LocalAddr: lIPaddr, - } - - return c, nil -} - -// DefaultDmsgConfig returns default Dmsg config. -func DefaultDmsgConfig() *snet.DmsgConfig { - return &snet.DmsgConfig{ - Discovery: skyenv.DefaultDmsgDiscAddr, - SessionsCount: 1, - } -} - -// DmsgPtyConfig configures the dmsgpty-host. -type DmsgPtyConfig struct { +// DmsgptyConfig configures the dmsgpty-host. +type DmsgptyConfig struct { Port uint16 `json:"port"` AuthFile string `json:"authorization_file"` CLINet string `json:"cli_network"` CLIAddr string `json:"cli_address"` } -// DefaultDmsgPtyConfig returns default DmsgPty config. -func DefaultDmsgPtyConfig() *DmsgPtyConfig { - return &DmsgPtyConfig{ - Port: skyenv.DmsgPtyPort, - AuthFile: "./skywire/dmsgpty/whitelist.json", - CLINet: skyenv.DefaultDmsgPtyCLINet, - CLIAddr: skyenv.DefaultDmsgPtyCLIAddr, - } -} - // TransportConfig defines a transport config. type TransportConfig struct { - Discovery string `json:"discovery"` - LogStore *LogStoreConfig `json:"log_store"` -} - -// DefaultTransportConfig returns default transport config. -func DefaultTransportConfig() *TransportConfig { - return &TransportConfig{ - Discovery: skyenv.DefaultTpDiscAddr, - LogStore: DefaultLogStoreConfig(), - } + Discovery string `json:"discovery"` + LogStore *LogStoreConfig `json:"log_store"` + TrustedVisors []cipher.PubKey `json:"trusted_visors"` } // LogStoreType defines a type for LogStore. It may be either file or memory. @@ -396,14 +360,6 @@ type LogStoreConfig struct { Location string `json:"location"` } -// DefaultLogStoreConfig returns default LogStore config. -func DefaultLogStoreConfig() *LogStoreConfig { - return &LogStoreConfig{ - Type: LogStoreFile, - Location: "./skywire/transport_logs", - } -} - // RoutingConfig configures routing. type RoutingConfig struct { SetupNodes []cipher.PubKey `json:"setup_nodes,omitempty"` @@ -411,39 +367,24 @@ type RoutingConfig struct { RouteFinderTimeout Duration `json:"route_finder_timeout,omitempty"` } -// DefaultRoutingConfig returns default routing config. -func DefaultRoutingConfig() *RoutingConfig { - return &RoutingConfig{ - SetupNodes: []cipher.PubKey{skyenv.MustPK(skyenv.DefaultSetupPK)}, - RouteFinder: skyenv.DefaultRouteFinderAddr, - RouteFinderTimeout: DefaultTimeout, - } -} - // UptimeTrackerConfig configures uptime tracker. type UptimeTrackerConfig struct { Addr string `json:"addr"` } -// DefaultUptimeTrackerConfig returns default uptime tracker config. -func DefaultUptimeTrackerConfig() *UptimeTrackerConfig { - return &UptimeTrackerConfig{ - Addr: skyenv.DefaultUptimeTrackerAddr, - } -} - // AppDiscConfig configures Skywire App Discovery Clients. type AppDiscConfig struct { UpdateInterval Duration `json:"update_interval,omitempty"` ProxyDisc string `json:"proxy_discovery_addr"` } -// DefaultAppDiscConfig returns the default app discovery config. -func DefaultAppDiscConfig() *AppDiscConfig { - return &AppDiscConfig{ - UpdateInterval: Duration(skyenv.AppDiscUpdateInterval), - ProxyDisc: skyenv.DefaultProxyDiscAddr, - } +// LauncherConfig configures the app launcher. +type LauncherConfig struct { + Discovery *AppDiscConfig `json:"discovery"` + Apps []launcher.AppConfig `json:"apps"` + ServerAddr string `json:"server_addr"` + BinPath string `json:"bin_path"` + LocalPath string `json:"local_path"` } // HypervisorConfig represents hypervisor configuration. @@ -451,38 +392,7 @@ type HypervisorConfig struct { PubKey cipher.PubKey `json:"public_key"` } -// AppConfig defines app startup parameters. -type AppConfig struct { - App string `json:"app"` - AutoStart bool `json:"auto_start"` - Port routing.Port `json:"port"` - Args []string `json:"args,omitempty"` -} - // InterfaceConfig defines listening interfaces for skywire visor. type InterfaceConfig struct { RPCAddress string `json:"rpc"` // RPC address and port for command-line interface (leave blank to disable RPC interface). } - -// DefaultInterfaceConfig returns default server interface config. -func DefaultInterfaceConfig() *InterfaceConfig { - return &InterfaceConfig{ - RPCAddress: "localhost:3435", - } -} - -func getLocalIPAddress() (string, error) { - addrs, err := net.InterfaceAddrs() - if err != nil { - return "", err - } - - for _, a := range addrs { - if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return fmt.Sprintf("%s:%d", ipnet.IP.String(), DefaultSTCPPort), nil - } - } - } - return "", errors.New("could not find local IP address") -} diff --git a/pkg/visor/config_test.go b/pkg/visor/config_test.go index bbb5ba7dd..ec471fce1 100644 --- a/pkg/visor/config_test.go +++ b/pkg/visor/config_test.go @@ -1,114 +1 @@ package visor - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - - "github.com/SkycoinProject/dmsg/cipher" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/SkycoinProject/skywire-mainnet/internal/httpauth" - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" -) - -func TestTransportDiscovery(t *testing.T) { - pk, _ := cipher.GenerateKeyPair() - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.NoError(t, json.NewEncoder(w).Encode(&httpauth.NextNonceResponse{Edge: pk, NextNonce: 1})) - })) - - defer srv.Close() - - conf := Config{ - Transport: &TransportConfig{ - Discovery: srv.URL, - }, - } - - discovery, err := conf.TransportDiscovery() - require.NoError(t, err) - - assert.NotNil(t, discovery) -} - -func TestTransportLogStore(t *testing.T) { - dir := filepath.Join(os.TempDir(), "foo") - - defer func() { - require.NoError(t, os.RemoveAll(dir)) - }() - - conf := Config{ - Transport: &TransportConfig{ - LogStore: &LogStoreConfig{ - Type: LogStoreFile, - Location: dir, - }, - }, - } - - ls, err := conf.TransportLogStore() - require.NoError(t, err) - require.NotNil(t, ls) - - conf.Transport.LogStore.Type = LogStoreMemory - conf.Transport.LogStore.Location = "" - - ls, err = conf.TransportLogStore() - require.NoError(t, err) - require.NotNil(t, ls) -} - -func TestAppsConfig(t *testing.T) { - conf := Config{ - Apps: []AppConfig{ - {App: "foo", Port: 1}, - {App: "bar", AutoStart: true, Port: 2}, - }, - } - - appsConf, err := conf.AppsConfig() - require.NoError(t, err) - - app1 := appsConf["foo"] - assert.Equal(t, "foo", app1.App) - assert.Equal(t, routing.Port(1), app1.Port) - assert.False(t, app1.AutoStart) - - app2 := appsConf["bar"] - assert.Equal(t, "bar", app2.App) - assert.Equal(t, routing.Port(2), app2.Port) - assert.True(t, app2.AutoStart) -} - -func TestAppsDir(t *testing.T) { - conf := Config{AppsPath: "apps"} - - dir, err := conf.AppsDir() - require.NoError(t, err) - - defer func() { - require.NoError(t, os.Remove(dir)) - }() - - _, err = os.Stat(dir) - assert.NoError(t, err) -} - -func TestLocalDir(t *testing.T) { - conf := Config{LocalPath: "local"} - dir, err := conf.LocalDir() - require.NoError(t, err) - - defer func() { - require.NoError(t, os.Remove(dir)) - }() - - _, err = os.Stat(dir) - assert.NoError(t, err) -} diff --git a/pkg/visor/duration.go b/pkg/visor/duration.go index 4ac6eeca0..3ccf32dff 100644 --- a/pkg/visor/duration.go +++ b/pkg/visor/duration.go @@ -7,6 +7,7 @@ import ( ) // Duration wraps around time.Duration to allow parsing from and to JSON +// TODO: Put this in a suitable module. type Duration time.Duration // MarshalJSON implements json marshaling diff --git a/pkg/visor/gateway.go b/pkg/visor/gateway.go index 9124412a7..17f495d5e 100644 --- a/pkg/visor/gateway.go +++ b/pkg/visor/gateway.go @@ -196,7 +196,7 @@ package visor // //func handleTransportTypes(v *Visor) http.HandlerFunc { // return func(w http.ResponseWriter, r *http.Request) { -// httputil.WriteJSON(w, r, http.StatusOK, v.tm.Networks()) +// httputil.WriteJSON(w, r, http.StatusOK, v.tpM.Networks()) // } //} // @@ -207,7 +207,7 @@ package visor // return // } // httputil.WriteJSON(w, r, http.StatusOK, -// newTransportSummary(v.tm, tp, true, v.router.SetupIsTrusted(tp.Remote()))) +// newTransportSummary(v.tpM, tp, true, v.router.SetupIsTrusted(tp.Remote()))) // } //} // @@ -257,13 +257,13 @@ package visor // fmt.Errorf("failed to read JSON from http request body: %v", err)) // return // } -// mTp, err := v.tm.SaveTransport(r.Context(), reqB.Remote, reqB.TpType) +// mTp, err := v.tpM.SaveTransport(r.Context(), reqB.Remote, reqB.TpType) // if err != nil { // httputil.WriteJSON(w, r, http.StatusInternalServerError, err) // return // } // httputil.WriteJSON(w, r, http.StatusOK, -// newTransportSummary(v.tm, mTp, false, v.router.SetupIsTrusted(mTp.Remote()))) +// newTransportSummary(v.tpM, mTp, false, v.router.SetupIsTrusted(mTp.Remote()))) // } //} // @@ -273,7 +273,7 @@ package visor // if !ok { // return // } -// v.tm.DeleteTransport(tp.Entry.ID) +// v.tpM.DeleteTransport(tp.Entry.ID) // } //} // @@ -304,7 +304,7 @@ package visor // httputil.WriteJSON(w, r, http.StatusBadRequest, err) // return nil, false // } -// tp := v.tm.Transport(tid) +// tp := v.tpM.Transport(tid) // if tp == nil { // httputil.WriteJSON(w, r, http.StatusNotFound, // fmt.Errorf("transport of ID %v is not found", tid)) @@ -324,9 +324,9 @@ package visor // //func makeVisorSummary(v *Visor) *Summary { // var tpSums []*TransportSummary -// v.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { +// v.tpM.WalkTransports(func(tp *transport.ManagedTransport) bool { // isSetup := v.router.SetupIsTrusted(tp.Remote()) -// tpSums = append(tpSums, newTransportSummary(v.tm, tp, true, isSetup)) +// tpSums = append(tpSums, newTransportSummary(v.tpM, tp, true, isSetup)) // return true // }) // return &Summary{ @@ -404,9 +404,9 @@ package visor // return true // } // var tps []*TransportSummary -// v.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { -// if typeIncluded(tp.Type()) && pkIncluded(v.tm.Local(), tp.Remote()) { -// tps = append(tps, newTransportSummary(v.tm, tp, in.ShowLogs, v.router.SetupIsTrusted(tp.Remote()))) +// v.tpM.WalkTransports(func(tp *transport.ManagedTransport) bool { +// if typeIncluded(tp.Type()) && pkIncluded(v.tpM.Local(), tp.Remote()) { +// tps = append(tps, newTransportSummary(v.tpM, tp, in.ShowLogs, v.router.SetupIsTrusted(tp.Remote()))) // } // return true // }) diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index 15996f7fa..c9b64928a 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -9,6 +9,8 @@ import ( "os" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/dmsg/cipher" "github.com/google/uuid" "github.com/sirupsen/logrus" @@ -46,7 +48,7 @@ func newRPCServer(v *Visor, remoteName string) (*rpc.Server, error) { rpcS := rpc.NewServer() rpcG := &RPC{ visor: v, - log: v.Logger.PackageLogger("visor_rpc:" + remoteName), + log: v.MasterLogger().PackageLogger("visor_rpc:" + remoteName), } if err := rpcS.RegisterName(RPCPrefix, rpcG); err != nil { @@ -75,15 +77,18 @@ func (r *RPC) Health(_ *struct{}, out *HealthInfo) (err error) { out.RouteFinder = http.StatusOK out.SetupNode = http.StatusOK - if _, err = r.visor.conf.TransportDiscovery(); err != nil { + _, err = r.visor.TpDiscClient().GetTransportsByEdge(context.Background(), r.visor.conf.KeyPair.PubKey) + if err != nil { out.TransportDiscovery = http.StatusNotFound } - if r.visor.conf.RoutingConfig().RouteFinder == "" { + // TODO(evanlinjin): This should actually poll the route finder service. + if r.visor.conf.Routing.RouteFinder == "" { out.RouteFinder = http.StatusNotFound } - if len(r.visor.conf.RoutingConfig().SetupNodes) == 0 { + // TODO(evanlinjin): This should actually poll the setup nodes services. + if len(r.visor.conf.Routing.SetupNodes) == 0 { out.SetupNode = http.StatusNotFound } @@ -164,12 +169,12 @@ func newTransportSummary(tm *transport.Manager, tp *transport.ManagedTransport, // Summary provides a summary of a Skywire Visor. type Summary struct { - PubKey cipher.PubKey `json:"local_pk"` - BuildInfo *buildinfo.Info `json:"build_info"` - AppProtoVersion string `json:"app_protocol_version"` - Apps []*AppState `json:"apps"` - Transports []*TransportSummary `json:"transports"` - RoutesCount int `json:"routes_count"` + PubKey cipher.PubKey `json:"local_pk"` + BuildInfo *buildinfo.Info `json:"build_info"` + AppProtoVersion string `json:"app_protocol_version"` + Apps []*launcher.AppState `json:"apps"` + Transports []*TransportSummary `json:"transports"` + RoutesCount int `json:"routes_count"` } // Summary provides a summary of the AppNode. @@ -177,16 +182,16 @@ func (r *RPC) Summary(_ *struct{}, out *Summary) (err error) { defer rpcutil.LogCall(r.log, "Summary", nil)(out, &err) var summaries []*TransportSummary - r.visor.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { + r.visor.tpM.WalkTransports(func(tp *transport.ManagedTransport) bool { summaries = append(summaries, - newTransportSummary(r.visor.tm, tp, false, r.visor.router.SetupIsTrusted(tp.Remote()))) + newTransportSummary(r.visor.tpM, tp, false, r.visor.router.SetupIsTrusted(tp.Remote()))) return true }) *out = Summary{ PubKey: r.visor.conf.Keys().PubKey, BuildInfo: buildinfo.Get(), AppProtoVersion: supportedProtocolVersion, - Apps: r.visor.Apps(), + Apps: r.visor.appL.AppStates(), Transports: summaries, RoutesCount: r.visor.router.RoutesCount(), } @@ -198,10 +203,10 @@ func (r *RPC) Summary(_ *struct{}, out *Summary) (err error) { */ // Apps returns list of Apps registered on the Visor. -func (r *RPC) Apps(_ *struct{}, reply *[]*AppState) (err error) { +func (r *RPC) Apps(_ *struct{}, reply *[]*launcher.AppState) (err error) { defer rpcutil.LogCall(r.log, "Apps", nil)(reply, &err) - *reply = r.visor.Apps() + *reply = r.visor.appL.AppStates() return nil } @@ -209,14 +214,15 @@ func (r *RPC) Apps(_ *struct{}, reply *[]*AppState) (err error) { func (r *RPC) StartApp(name *string, _ *struct{}) (err error) { defer rpcutil.LogCall(r.log, "StartApp", name)(nil, &err) - return r.visor.StartApp(*name) + return r.visor.appL.StartApp(*name, nil, nil) } // StopApp stops App with provided name. func (r *RPC) StopApp(name *string, _ *struct{}) (err error) { defer rpcutil.LogCall(r.log, "StopApp", name)(nil, &err) - return r.visor.StopApp(*name) + _, err = r.visor.appL.StopApp(*name) + return err } // SetAutoStartIn is input for SetAutoStart. @@ -254,7 +260,7 @@ func (r *RPC) SetSocksClientPK(in *cipher.PubKey, _ *struct{}) (err error) { func (r *RPC) TransportTypes(_ *struct{}, out *[]string) (err error) { defer rpcutil.LogCall(r.log, "TransportTypes", nil)(out, &err) - *out = r.visor.tm.Networks() + *out = r.visor.tpM.Networks() return nil } @@ -291,9 +297,9 @@ func (r *RPC) Transports(in *TransportsIn, out *[]*TransportSummary) (err error) } return true } - r.visor.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { - if typeIncluded(tp.Type()) && pkIncluded(r.visor.tm.Local(), tp.Remote()) { - *out = append(*out, newTransportSummary(r.visor.tm, tp, in.ShowLogs, r.visor.router.SetupIsTrusted(tp.Remote()))) + r.visor.tpM.WalkTransports(func(tp *transport.ManagedTransport) bool { + if typeIncluded(tp.Type()) && pkIncluded(r.visor.tpM.Local(), tp.Remote()) { + *out = append(*out, newTransportSummary(r.visor.tpM, tp, in.ShowLogs, r.visor.router.SetupIsTrusted(tp.Remote()))) } return true }) @@ -304,11 +310,11 @@ func (r *RPC) Transports(in *TransportsIn, out *[]*TransportSummary) (err error) func (r *RPC) Transport(in *uuid.UUID, out *TransportSummary) (err error) { defer rpcutil.LogCall(r.log, "Transport", in)(out, &err) - tp := r.visor.tm.Transport(*in) + tp := r.visor.tpM.Transport(*in) if tp == nil { return ErrNotFound } - *out = *newTransportSummary(r.visor.tm, tp, true, r.visor.router.SetupIsTrusted(tp.Remote())) + *out = *newTransportSummary(r.visor.tpM, tp, true, r.visor.router.SetupIsTrusted(tp.Remote())) return nil } @@ -332,12 +338,12 @@ func (r *RPC) AddTransport(in *AddTransportIn, out *TransportSummary) (err error defer cancel() } - tp, err := r.visor.tm.SaveTransport(ctx, in.RemotePK, in.TpType) + tp, err := r.visor.tpM.SaveTransport(ctx, in.RemotePK, in.TpType) if err != nil { return err } - *out = *newTransportSummary(r.visor.tm, tp, false, r.visor.router.SetupIsTrusted(tp.Remote())) + *out = *newTransportSummary(r.visor.tpM, tp, false, r.visor.router.SetupIsTrusted(tp.Remote())) return nil } @@ -345,7 +351,7 @@ func (r *RPC) AddTransport(in *AddTransportIn, out *TransportSummary) (err error func (r *RPC) RemoveTransport(tid *uuid.UUID, _ *struct{}) (err error) { defer rpcutil.LogCall(r.log, "RemoveTransport", tid)(nil, &err) - r.visor.tm.DeleteTransport(*tid) + r.visor.tpM.DeleteTransport(*tid) return nil } @@ -357,10 +363,7 @@ func (r *RPC) RemoveTransport(tid *uuid.UUID, _ *struct{}) (err error) { func (r *RPC) DiscoverTransportsByPK(pk *cipher.PubKey, out *[]*transport.EntryWithStatus) (err error) { defer rpcutil.LogCall(r.log, "DiscoverTransportsByPK", pk)(out, &err) - tpD, err := r.visor.conf.TransportDiscovery() - if err != nil { - return err - } + tpD := r.visor.TpDiscClient() entries, err := tpD.GetTransportsByEdge(context.Background(), *pk) if err != nil { @@ -375,10 +378,7 @@ func (r *RPC) DiscoverTransportsByPK(pk *cipher.PubKey, out *[]*transport.EntryW func (r *RPC) DiscoverTransportByID(id *uuid.UUID, out *transport.EntryWithStatus) (err error) { defer rpcutil.LogCall(r.log, "DiscoverTransportByID", id)(out, &err) - tpD, err := r.visor.conf.TransportDiscovery() - if err != nil { - return err - } + tpD := r.visor.TpDiscClient() entry, err := tpD.GetTransportByID(context.Background(), *id) if err != nil { diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index 2d6e7b2fb..cfcdec5ac 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -11,6 +11,8 @@ import ( "sync" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" "github.com/SkycoinProject/dmsg/cipher" @@ -42,7 +44,7 @@ type RPCClient interface { Health() (*HealthInfo, error) Uptime() (float64, error) - Apps() ([]*AppState, error) + Apps() ([]*launcher.AppState, error) StartApp(appName string) error StopApp(appName string) error SetAutoStart(appName string, autostart bool) error @@ -134,8 +136,8 @@ func (rc *rpcClient) Uptime() (float64, error) { } // Apps calls Apps. -func (rc *rpcClient) Apps() ([]*AppState, error) { - states := make([]*AppState, 0) +func (rc *rpcClient) Apps() ([]*launcher.AppState, error) { + states := make([]*launcher.AppState, 0) err := rc.Call("Apps", &struct{}{}, &states) return states, err } @@ -385,9 +387,9 @@ func NewMockRPCClient(r *rand.Rand, maxTps int, maxRules int) (cipher.PubKey, RP PubKey: localPK, BuildInfo: buildinfo.Get(), AppProtoVersion: supportedProtocolVersion, - Apps: []*AppState{ - {AppConfig: AppConfig{App: "foo.v1.0", AutoStart: false, Port: 10}}, - {AppConfig: AppConfig{App: "bar.v2.0", AutoStart: false, Port: 20}}, + Apps: []*launcher.AppState{ + {AppConfig: launcher.AppConfig{Name: "foo.v1.0", AutoStart: false, Port: 10}}, + {AppConfig: launcher.AppConfig{Name: "bar.v2.0", AutoStart: false, Port: 20}}, }, Transports: tps, RoutesCount: rt.Count(), @@ -447,8 +449,8 @@ func (mc *mockRPCClient) Uptime() (float64, error) { } // Apps implements RPCClient. -func (mc *mockRPCClient) Apps() ([]*AppState, error) { - var apps []*AppState +func (mc *mockRPCClient) Apps() ([]*launcher.AppState, error) { + var apps []*launcher.AppState err := mc.do(false, func() error { for _, a := range mc.s.Apps { a := a @@ -473,7 +475,7 @@ func (*mockRPCClient) StopApp(string) error { func (mc *mockRPCClient) SetAutoStart(appName string, autostart bool) error { return mc.do(true, func() error { for _, a := range mc.s.Apps { - if a.App == appName { + if a.Name == appName { a.AutoStart = autostart return nil } @@ -488,7 +490,7 @@ func (mc *mockRPCClient) SetSocksPassword(string) error { const socksName = "skysocks" for i := range mc.s.Apps { - if mc.s.Apps[i].App == socksName { + if mc.s.Apps[i].Name == socksName { return nil } } @@ -503,7 +505,7 @@ func (mc *mockRPCClient) SetSocksClientPK(cipher.PubKey) error { const socksName = "skysocks-client" for i := range mc.s.Apps { - if mc.s.Apps[i].App == socksName { + if mc.s.Apps[i].Name == socksName { return nil } } diff --git a/pkg/visor/rpc_test.go b/pkg/visor/rpc_test.go index cc29e3a91..9ab798895 100644 --- a/pkg/visor/rpc_test.go +++ b/pkg/visor/rpc_test.go @@ -2,24 +2,16 @@ package visor import ( "fmt" - "io/ioutil" "net/http" - "os" "testing" "time" + "github.com/SkycoinProject/skywire-mainnet/pkg/transport" + "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - - "github.com/SkycoinProject/skywire-mainnet/internal/testhelpers" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" - "github.com/SkycoinProject/skywire-mainnet/pkg/router" - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" ) func TestHealth(t *testing.T) { @@ -36,7 +28,15 @@ func TestHealth(t *testing.T) { c.Routing.SetupNodes = []cipher.PubKey{c.KeyPair.PubKey} t.Run("Report all the services as available", func(t *testing.T) { - rpc := &RPC{visor: &Visor{conf: c}, log: logrus.New()} + v := &Visor{ + conf: c, + tpM: &transport.Manager{ + Conf: &transport.ManagerConfig{ + DiscoveryClient: transport.NewDiscoveryMock(), + }, + }, + } + rpc := &RPC{visor: v, log: logrus.New()} h := &HealthInfo{} err := rpc.Health(nil, h) require.NoError(t, err) @@ -48,10 +48,19 @@ func TestHealth(t *testing.T) { t.Run("Report as unavailable", func(t *testing.T) { conf := &Config{ + KeyPair: NewKeyPair(), Routing: &RoutingConfig{}, } + v := &Visor{ + conf: conf, + tpM: &transport.Manager{ + Conf: &transport.ManagerConfig{ + DiscoveryClient: transport.NewDiscoveryMock(), + }, + }, + } - rpc := &RPC{visor: &Visor{conf: conf}, log: logrus.New()} + rpc := &RPC{visor: v, log: logrus.New()} h := &HealthInfo{} err := rpc.Health(nil, h) require.NoError(t, err) @@ -72,131 +81,132 @@ func TestUptime(t *testing.T) { assert.Contains(t, fmt.Sprintf("%f", res), "1.0") } -func TestListApps(t *testing.T) { - apps := make(map[string]AppConfig) - appCfg := []AppConfig{ - { - App: "foo", - AutoStart: false, - Port: 10, - }, - { - App: "bar", - AutoStart: true, - Port: 11, - }, - } - - for _, app := range appCfg { - apps[app.App] = app - } - - pm := &appserver.MockProcManager{} - pm.On("ProcByName", apps["foo"].App).Return(new(appserver.Proc), false) - pm.On("ProcByName", apps["bar"].App).Return(new(appserver.Proc), true) - - n := Visor{ - appsConf: apps, - procM: pm, - } - - rpc := &RPC{visor: &n, log: logrus.New()} - - var reply []*AppState - require.NoError(t, rpc.Apps(nil, &reply)) - require.Len(t, reply, 2) - - app1, app2 := reply[0], reply[1] - if app1.App != "foo" { - // apps inside visor are stored inside a map, so their order - // is not deterministic, we should be ready for this and - // rearrange the outer array to check values correctly - app1, app2 = reply[1], reply[0] - } - - assert.Equal(t, "foo", app1.App) - assert.False(t, app1.AutoStart) - assert.Equal(t, routing.Port(10), app1.Port) - assert.Equal(t, AppStatusStopped, app1.Status) - - assert.Equal(t, "bar", app2.App) - assert.True(t, app2.AutoStart) - assert.Equal(t, routing.Port(11), app2.Port) - assert.Equal(t, AppStatusRunning, app2.Status) -} - -func TestStartStopApp(t *testing.T) { - tempDir, err := ioutil.TempDir(os.TempDir(), "") - require.NoError(t, err) - defer func() { require.NoError(t, os.RemoveAll(tempDir)) }() - - r := &router.MockRouter{} - r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) - r.On("Close").Return(testhelpers.NoErr) - - defer func() { - require.NoError(t, os.RemoveAll("skychat")) - require.NoError(t, os.RemoveAll("apps-pid.txt")) - }() - - appCfg := []AppConfig{ - { - App: "foo", - AutoStart: false, - Port: 10, - }, - } - apps := map[string]AppConfig{ - "foo": appCfg[0], - } - - unknownApp := "bar" - app := apps["foo"].App - - keyPair := NewKeyPair() - - visorCfg := Config{ - KeyPair: keyPair, - AppServerAddr: appcommon.DefaultAppSrvAddr, - } - - visor := &Visor{ - router: r, - appsConf: apps, - logger: logging.MustGetLogger("test"), - conf: &visorCfg, - } - - appPID1 := appcommon.ProcID(10) - - pm := &appserver.MockProcManager{} - pm.On("Start", mock.Anything).Return(appPID1, testhelpers.NoErr) - pm.On("Wait", app).Return(testhelpers.NoErr) - pm.On("Stop", app).Return(testhelpers.NoErr) - pm.On("ProcByName", app).Return(new(appserver.Proc), true) - pm.On("ProcByName", unknownApp).Return(new(appserver.Proc), false) - - visor.procM = pm - - rpc := &RPC{visor: visor, log: logrus.New()} - - err = rpc.StartApp(&unknownApp, nil) - require.Error(t, err) - assert.Equal(t, ErrAppProcNotRunning, err) - - require.NoError(t, rpc.StartApp(&app, nil)) - time.Sleep(100 * time.Millisecond) - - err = rpc.StopApp(&unknownApp, nil) - require.Error(t, err) - assert.Equal(t, ErrAppProcNotRunning, err) - - require.NoError(t, rpc.StopApp(&app, nil)) - time.Sleep(100 * time.Millisecond) - - // remove files - require.NoError(t, os.RemoveAll("foo")) -} +// TODO(evanlinjin): These should be moved to /pkg/app/launcher +//func TestListApps(t *testing.T) { +// apps := make(map[string]AppConfig) +// appCfg := []AppConfig{ +// { +// App: "foo", +// AutoStart: false, +// Port: 10, +// }, +// { +// App: "bar", +// AutoStart: true, +// Port: 11, +// }, +// } +// +// for _, app := range appCfg { +// apps[app.App] = app +// } +// +// pm := &appserver.MockProcManager{} +// pm.On("ProcByName", apps["foo"].App).Return(new(appserver.Proc), false) +// pm.On("ProcByName", apps["bar"].App).Return(new(appserver.Proc), true) +// +// n := Visor{ +// appsConf: apps, +// procM: pm, +// } +// +// rpc := &RPC{visor: &n, log: logrus.New()} +// +// var reply []*AppState +// require.NoError(t, rpc.Apps(nil, &reply)) +// require.Len(t, reply, 2) +// +// app1, app2 := reply[0], reply[1] +// if app1.App != "foo" { +// // apps inside visor are stored inside a map, so their order +// // is not deterministic, we should be ready for this and +// // rearrange the outer array to check values correctly +// app1, app2 = reply[1], reply[0] +// } +// +// assert.Equal(t, "foo", app1.App) +// assert.False(t, app1.AutoStart) +// assert.Equal(t, routing.Port(10), app1.Port) +// assert.Equal(t, AppStatusStopped, app1.Status) +// +// assert.Equal(t, "bar", app2.App) +// assert.True(t, app2.AutoStart) +// assert.Equal(t, routing.Port(11), app2.Port) +// assert.Equal(t, AppStatusRunning, app2.Status) +//} +// +//func TestStartStopApp(t *testing.T) { +// tempDir, err := ioutil.TempDir(os.TempDir(), "") +// require.NoError(t, err) +// defer func() { require.NoError(t, os.RemoveAll(tempDir)) }() +// +// r := &router.MockRouter{} +// r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) +// r.On("Close").Return(testhelpers.NoErr) +// +// defer func() { +// require.NoError(t, os.RemoveAll("skychat")) +// require.NoError(t, os.RemoveAll("apps-pid.txt")) +// }() +// +// appCfg := []AppConfig{ +// { +// App: "foo", +// AutoStart: false, +// Port: 10, +// }, +// } +// apps := map[string]AppConfig{ +// "foo": appCfg[0], +// } +// +// unknownApp := "bar" +// app := apps["foo"].App +// +// keyPair := NewKeyPair() +// +// visorCfg := Config{ +// KeyPair: keyPair, +// AppServerAddr: appcommon.DefaultAppSrvAddr, +// } +// +// visor := &Visor{ +// router: r, +// appsConf: apps, +// log: logging.MustGetLogger("test"), +// conf: &visorCfg, +// } +// +// appPID1 := appcommon.ProcID(10) +// +// pm := &appserver.MockProcManager{} +// pm.On("Start", mock.Anything).Return(appPID1, testhelpers.NoErr) +// pm.On("Wait", app).Return(testhelpers.NoErr) +// pm.On("Stop", app).Return(testhelpers.NoErr) +// pm.On("ProcByName", app).Return(new(appserver.Proc), true) +// pm.On("ProcByName", unknownApp).Return(new(appserver.Proc), false) +// +// visor.procM = pm +// +// rpc := &RPC{visor: visor, log: logrus.New()} +// +// err = rpc.StartApp(&unknownApp, nil) +// require.Error(t, err) +// assert.Equal(t, ErrAppProcNotRunning, err) +// +// require.NoError(t, rpc.StartApp(&app, nil)) +// time.Sleep(100 * time.Millisecond) +// +// err = rpc.StopApp(&unknownApp, nil) +// require.Error(t, err) +// assert.Equal(t, ErrAppProcNotRunning, err) +// +// require.NoError(t, rpc.StopApp(&app, nil)) +// time.Sleep(100 * time.Millisecond) +// +// // remove files +// require.NoError(t, os.RemoveAll("foo")) +//} /* TODO(evanlinjin): Fix these tests. @@ -234,12 +244,12 @@ These tests have been commented out for the following reasons: // visor := &Visor{ // config: conf, // router: r, -// tm: tm1, +// tpM: tm1, // rt: routing.New(), // executer: executer, // appsConf: apps, // startedApps: map[string]*appBind{}, -// logger: logging.MustGetLogger("test"), +// log: logging.MustGetLogger("test"), // } // pathutil.EnsureDir(visor.dir()) // defer func() { @@ -297,13 +307,13 @@ These tests have been commented out for the following reasons: // t.Run("RPCServer", func(t *testing.T) { // var out []byte // require.NoError(t, gateway.Exec(&command, &out)) -// assert.Equal(t, []byte("1\n"), out) +// assert.Equal(t, []byte("1\net"), out) // }) // // t.Run("RPCClient", func(t *testing.T) { // out, err := client.Exec(command) // require.NoError(t, err) -// assert.Equal(t, []byte("1\n"), out) +// assert.Equal(t, []byte("1\net"), out) // }) // }) // @@ -381,7 +391,7 @@ These tests have been commented out for the following reasons: // // t.Run("Transport", func(t *testing.T) { // var ids []uuid.UUID -// visor.tm.WalkTransports(func(tp *transport.ManagedTransport) bool { +// visor.tpM.WalkTransports(func(tp *transport.ManagedTransport) bool { // ids = append(ids, tp.RuleEntry.ID) // return true // }) diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index e1e87925b..483199dd1 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -2,53 +2,32 @@ package visor import ( - "bufio" - "context" "errors" "fmt" - "net" - "os" "os/exec" "path/filepath" + "reflect" "runtime" - "strconv" "strings" - "sync" "syscall" "time" - "github.com/SkycoinProject/dmsg" + "github.com/sirupsen/logrus" + + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/dmsgpty" "github.com/SkycoinProject/skycoin/src/util/logging" - "github.com/SkycoinProject/skywire-mainnet/internal/vpn" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appdisc" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appnet" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" "github.com/SkycoinProject/skywire-mainnet/pkg/restart" - "github.com/SkycoinProject/skywire-mainnet/pkg/routefinder/rfclient" "github.com/SkycoinProject/skywire-mainnet/pkg/router" - "github.com/SkycoinProject/skywire-mainnet/pkg/routing" - "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" "github.com/SkycoinProject/skywire-mainnet/pkg/transport" - "github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil" "github.com/SkycoinProject/skywire-mainnet/pkg/util/updater" ) -// AppStatus defines running status of an App. -type AppStatus int - -const ( - // AppStatusStopped represents status of a stopped App. - AppStatusStopped AppStatus = iota - - // AppStatusRunning represents status of a running App. - AppStatusRunning -) - var ( // ErrAppProcNotRunning represents lookup error for App related calls. ErrAppProcNotRunning = errors.New("no process of given app is running") @@ -60,619 +39,182 @@ const ( shortHashLen = 6 ) -var reservedPorts = map[routing.Port]string{0: "router", 1: "skychat", 3: "skysocks"} - -// AppState defines state parameters for a registered App. -type AppState struct { - AppConfig - Status AppStatus `json:"status"` -} - // Visor provides messaging runtime for Apps by setting up all // necessary connections and performing messaging gateway functions. type Visor struct { - conf *Config - router router.Router - n *snet.Network - tm *transport.Manager - pty *dmsgpty.Host + reportCh chan vReport + closeStack []closeElem - Logger *logging.MasterLogger - logger *logging.Logger - - appsPath string - localPath string - appsConf map[string]AppConfig + conf *Config + log *logging.Logger startedAt time.Time restartCtx *restart.Context updater *updater.Updater - pidMu sync.Mutex - - cliLis net.Listener - hvErrs map[cipher.PubKey]chan error // errors returned when the associated hypervisor ServeRPCClient returns - - procM appserver.ProcManager + net *snet.Network + pty *dmsgpty.Host + tpM *transport.Manager + router router.Router - // cancel is to be called when visor.Close is triggered. - cancel context.CancelFunc + procM appserver.ProcManager // proc manager + appL *launcher.Launcher // app launcher } -// NewVisor constructs new Visor. -func NewVisor(cfg *Config, logger *logging.MasterLogger, restartCtx *restart.Context) (*Visor, error) { - ctx := context.Background() - - visor := &Visor{ - conf: cfg, - } - - visor.Logger = logger - visor.logger = visor.Logger.PackageLogger("skywire") - visor.conf.log = visor.logger - - pk := cfg.Keys().PubKey - sk := cfg.Keys().SecKey - - logger.WithField("PK", pk).Infof("Starting visor") - - restartCheckDelay, err := time.ParseDuration(cfg.RestartCheckDelay) - if err == nil { - restartCtx.SetCheckDelay(restartCheckDelay) - } - - restartCtx.RegisterLogger(visor.logger) - - visor.restartCtx = restartCtx - - visor.n = snet.New(snet.Config{ - PubKey: pk, - SecKey: sk, - Dmsg: cfg.DmsgConfig(), - STCP: cfg.STCP, - }) - if err := visor.n.Init(ctx); err != nil { - return nil, fmt.Errorf("failed to init network: %w", err) - } - - if cfg.DmsgPty != nil { - pty, err := cfg.DmsgPtyHost(visor.n.Dmsg()) - if err != nil { - return nil, fmt.Errorf("failed to setup pty: %w", err) - } - visor.pty = pty - } else { - logger.Info("'dmsgpty' is not configured, skipping...") - } - - trDiscovery, err := cfg.TransportDiscovery() - if err != nil { - return nil, fmt.Errorf("invalid transport discovery config: %w", err) - } - - logStore, err := cfg.TransportLogStore() - if err != nil { - return nil, fmt.Errorf("invalid TransportLogStore: %w", err) - } - - tmConfig := &transport.ManagerConfig{ - PubKey: pk, - SecKey: sk, - DefaultVisors: cfg.TrustedVisors, - DiscoveryClient: trDiscovery, - LogStore: logStore, - } - - visor.tm, err = transport.NewManager(visor.n, tmConfig) - if err != nil { - return nil, fmt.Errorf("transport manager: %w", err) - } - - rConfig := &router.Config{ - Logger: visor.Logger.PackageLogger("router"), - PubKey: pk, - SecKey: sk, - TransportManager: visor.tm, - RouteFinder: rfclient.NewHTTP(cfg.RoutingConfig().RouteFinder, time.Duration(cfg.RoutingConfig().RouteFinderTimeout)), - SetupNodes: cfg.RoutingConfig().SetupNodes, - } - - r, err := router.New(visor.n, rConfig) - if err != nil { - return nil, fmt.Errorf("failed to setup router: %w", err) - } - visor.router = r - - visor.appsConf, err = cfg.AppsConfig() - if err != nil { - return nil, fmt.Errorf("invalid AppsConfig: %w", err) - } - - visor.appsPath, err = cfg.AppsDir() - if err != nil { - return nil, fmt.Errorf("invalid AppsPath: %w", err) - } - - visor.localPath, err = cfg.LocalDir() - if err != nil { - return nil, fmt.Errorf("invalid LocalPath: %w", err) - } - if err := pathutil.EnsureDir(visor.localPath); err != nil { - return nil, fmt.Errorf("failed to ensure 'local_path': %w", err) - } - - if lvl, err := logging.LevelFromString(cfg.LogLevel); err == nil { - visor.Logger.SetLevel(lvl) - } - - if cfg.Interfaces != nil { - l, err := net.Listen("tcp", cfg.Interfaces.RPCAddress) - if err != nil { - return nil, fmt.Errorf("failed to setup RPC listener: %w", err) - } - - visor.cliLis = l - } - - visor.hvErrs = make(map[cipher.PubKey]chan error, len(cfg.Hypervisors)) - for _, hv := range cfg.Hypervisors { - visor.hvErrs[hv.PubKey] = make(chan error, 1) - } - - appDiscF := &appdisc.Factory{ - PK: pk, - SK: sk, - UpdateInterval: time.Duration(cfg.AppDiscConfig().UpdateInterval), - ProxyDisc: cfg.AppDiscConfig().ProxyDisc, - } - - logProcM := logging.MustGetLogger("proc_manager") - visor.procM, err = appserver.NewProcManager(logProcM, appDiscF, visor.conf.AppServerAddr) - if err != nil { - return nil, fmt.Errorf("failed to start proc manager: %w", err) - } - - visor.updater = updater.New(visor.logger, visor.restartCtx, visor.appsPath) - - return visor, err +type vReport struct { + src string + err error } -// Start spawns auto-started Apps, starts router and RPC interfaces . -func (visor *Visor) Start() error { - skywireNetworker := appnet.NewSkywireNetworker(logging.MustGetLogger("skynet"), visor.router) - if err := appnet.AddNetworker(appnet.TypeSkynet, skywireNetworker); err != nil { - return fmt.Errorf("failed to add skywire networker: %v", err) - } - - ctx, cancel := context.WithCancel(context.Background()) - visor.cancel = cancel - defer cancel() - - visor.startedAt = time.Now() - - if err := visor.startApps(); err != nil { - return err - } - - if err := visor.startDmsgPty(ctx); err != nil { - return err - } - - visor.startRPC(ctx) - - visor.logger.Info("Starting packet router") +type reportFunc func(err error) bool - if err := visor.router.Serve(ctx); err != nil { - return fmt.Errorf("failed to start Visor: %s", err) +func (v *Visor) makeReporter(src string) reportFunc { + return func(err error) bool { + v.reportCh <- vReport{src: src, err: err} + return err == nil } - - return nil } -func (visor *Visor) startApps() error { - if err := visor.closePreviousApps(); err != nil { - return err - } - - for _, ac := range visor.appsConf { - if !ac.AutoStart { - continue - } - - go func(a AppConfig) { - if err := visor.SpawnApp(&a, nil); err != nil { - visor.logger. - WithError(err). - WithField("app_name", a.App). - Warn("App stopped.") +func (v *Visor) processReports(log logrus.FieldLogger, ok *bool) { + if log == nil { + // nolint:ineffassign + log = v.log + } + for { + select { + case report := <-v.reportCh: + if report.err != nil { + v.log.WithError(report.err).WithField("_src", report.src).Error() + if ok != nil { + *ok = false + } } - }(ac) + default: + return + } } - - return nil } -func (visor *Visor) startDmsgPty(ctx context.Context) error { - if visor.pty == nil { - return nil - } - - log := visor.Logger.PackageLogger("dmsgpty") - - err2 := visor.serveDmsgPtyCLI(ctx, log) - if err2 != nil { - return err2 - } - - go visor.serveDmsgPty(ctx, log) - - return nil +type closeElem struct { + src string + fn func() bool } -func (visor *Visor) serveDmsgPtyCLI(ctx context.Context, log *logging.Logger) error { - if visor.conf.DmsgPty.CLINet == "unix" { - if err := os.MkdirAll(filepath.Dir(visor.conf.DmsgPty.CLIAddr), ownerRWX); err != nil { - log.WithError(err).Debug("Failed to prepare unix file dir.") - } - } - - ptyL, err := net.Listen(visor.conf.DmsgPty.CLINet, visor.conf.DmsgPty.CLIAddr) - if err != nil { - return fmt.Errorf("failed to start dmsgpty cli listener: %v", err) - } - - go func() { - log.WithField("net", visor.conf.DmsgPty.CLINet). - WithField("addr", visor.conf.DmsgPty.CLIAddr). - Info("Serving dmsgpty CLI.") - - if err := visor.pty.ServeCLI(ctx, ptyL); err != nil { - log.WithError(err). - WithField("entity", "dmsgpty-host"). - WithField("func", ".ServeCLI()"). - Error() - - visor.cancel() - } - }() - - return nil +func (v *Visor) pushCloseStack(src string, fn func() bool) { + v.closeStack = append(v.closeStack, closeElem{src: src, fn: fn}) } -func (visor *Visor) serveDmsgPty(ctx context.Context, log *logging.Logger) { - log.WithField("dmsg_port", visor.conf.DmsgPty.Port). - Info("Serving dmsg.") - - if err := visor.pty.ListenAndServe(ctx, visor.conf.DmsgPty.Port); err != nil { - log.WithError(err). - WithField("entity", "dmsgpty-host"). - WithField("func", ".ListenAndServe()"). - Error() - - visor.cancel() - } +// MasterLogger returns the underlying master logger (currently contained in visor config). +func (v *Visor) MasterLogger() *logging.MasterLogger { + return v.conf.log } -func (visor *Visor) startRPC(ctx context.Context) { - if visor.cliLis != nil { - visor.logger.Info("Starting RPC interface on ", visor.cliLis.Addr()) - - srv, err := newRPCServer(visor, "CLI") - if err != nil { - visor.logger.WithError(err).Errorf("Failed to start RPC server") - return - } - - go srv.Accept(visor.cliLis) - } - - if visor.hvErrs != nil { - for hvPK, hvErrs := range visor.hvErrs { - log := visor.Logger.PackageLogger("hypervisor_client"). - WithField("hypervisor_pk", hvPK) - - addr := dmsg.Addr{PK: hvPK, Port: skyenv.DmsgHypervisorPort} - rpcS, err := newRPCServer(visor, addr.PK.String()[:shortHashLen]) - if err != nil { - visor.logger.WithError(err).Errorf("Failed to start RPC server") - return - } +// NewVisor constructs new Visor. +func NewVisor(conf *Config, restartCtx *restart.Context) (v *Visor, ok bool) { + ok = true - go ServeRPCClient(ctx, log, visor.n, rpcS, addr, hvErrs) - } + v = &Visor{ + reportCh: make(chan vReport, 100), + log: conf.log.PackageLogger("visor"), + conf: conf, + restartCtx: restartCtx, } -} -func (visor *Visor) appsPIDFile() (*os.File, error) { - f, err := os.OpenFile(filepath.Join(visor.localPath, "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - return nil, err + if lvl, err := logging.LevelFromString(conf.LogLevel); err == nil { + v.conf.log.SetLevel(lvl) } - return f, nil -} - -func (visor *Visor) closePreviousApps() error { - visor.logger.Info("killing previously ran apps if any...") + log := v.MasterLogger().PackageLogger("visor:startup") + log.WithField("public_key", conf.KeyPair.PubKey).Info("Begin startup.") + v.startedAt = time.Now() - pids, err := visor.appsPIDFile() - if err != nil { - return err + startStack := []startFunc{ + initUpdater, + initSNet, + initDmsgpty, + initTransport, + initRouter, + initLauncher, + initCLI, + initHypervisors, + initUptimeTracker, } - defer func() { - if err := pids.Close(); err != nil { - visor.logger.Warnf("error closing PID file: %s", err) - } - }() + for i, startFn := range startStack { + name := strings.ToLower(strings.TrimPrefix(filepath.Base(runtime.FuncForPC(reflect.ValueOf(startFn).Pointer()).Name()), "visor.init")) + start := time.Now() - scanner := bufio.NewScanner(pids) - for scanner.Scan() { - appInfo := strings.Split(scanner.Text(), " ") - if len(appInfo) != 2 { - visor.logger.Fatalf("error parsing %s. Err: %s", pids.Name(), errors.New("line should be: [app name] [pid]")) - } + log := v.MasterLogger().PackageLogger(fmt.Sprintf("visor:startup:%s", name)). + WithField("func", fmt.Sprintf("[%d/%d]", i+1, len(startStack))) + log.Info("Starting module...") - pid, err := strconv.Atoi(appInfo[1]) - if err != nil { - visor.logger.Fatalf("error parsing %s. Err: %s", pids.Name(), err) + if ok := startFn(v); !ok { + log.WithField("elapsed", time.Since(start)).Error("Failed to start module.") + v.processReports(log, nil) + return v, ok } - visor.stopUnhandledApp(appInfo[0], pid) - } - - // empty file - if err := pathutil.AtomicWriteFile(pids.Name(), []byte{}); err != nil { - visor.logger.WithError(err).Errorf("Failed to empty file %s", pids.Name()) - } - - return nil -} - -func (visor *Visor) stopUnhandledApp(name string, pid int) { - p, err := os.FindProcess(pid) - if err != nil { - if runtime.GOOS != "windows" { - visor.logger.Infof("Previous app %s ran by this visor with pid: %d not found", name, pid) - } - return + log.WithField("elapsed", time.Since(start)).Info("Module started successfully.") } - err = p.Signal(syscall.SIGKILL) - if err != nil { - return + if v.processReports(log, &ok); !ok { + log.Error("Failed to startup visor.") + return v, ok } - visor.logger.Infof("Found and killed hanged app %s with pid %d previously ran by this visor", name, pid) + log.Info("Startup complete!") + return v, ok } // Close safely stops spawned Apps and Visor. -func (visor *Visor) Close() (err error) { - if visor == nil { +func (v *Visor) Close() error { + if v == nil { return nil } - if visor.cancel != nil { - visor.cancel() - } - - if visor.cliLis != nil { - if err = visor.cliLis.Close(); err != nil { - visor.logger.WithError(err).Error("failed to close CLI listener") - } else { - visor.logger.Info("CLI listener closed successfully") - } - } - if visor.hvErrs != nil { - for hvPK, hvErr := range visor.hvErrs { - visor.logger. - WithError(<-hvErr). - WithField("hypervisor_pk", hvPK). - Info("Closed hypervisor connection.") - } - } - - if err := visor.procM.Close(); err != nil { - visor.logger.WithError(err).Error("Proc manager closed with unexpected error.") - } else { - visor.logger.Info("Proc manager closed cleanly.") - } - - if err = visor.router.Close(); err != nil { - visor.logger.WithError(err).Error("Router closed with unexpected error.") - } else { - visor.logger.Info("Router closed cleanly.") - } - - return err -} - -// App returns a single app state of given name. -func (visor *Visor) App(name string) (*AppState, bool) { - app, ok := visor.appsConf[name] - if !ok { - return nil, false - } - state := &AppState{AppConfig: app, Status: AppStatusStopped} - if _, ok := visor.procM.ProcByName(app.App); ok { - state.Status = AppStatusRunning - } - return state, true -} - -// Apps returns list of AppStates for all registered apps. -func (visor *Visor) Apps() []*AppState { - // TODO: move app states to the app module - res := make([]*AppState, 0) + log := v.MasterLogger().PackageLogger("visor:shutdown") + log.Info("Begin shutdown.") - for _, app := range visor.appsConf { - state := &AppState{AppConfig: app, Status: AppStatusStopped} + for i, ce := range v.closeStack { - if _, ok := visor.procM.ProcByName(app.App); ok { - state.Status = AppStatusRunning - } - - res = append(res, state) - } + start := time.Now() + done := make(chan bool, 1) + t := time.NewTimer(time.Second * 2) - return res -} + log := v.MasterLogger().PackageLogger(fmt.Sprintf("visor:shutdown:%s", ce.src)). + WithField("func", fmt.Sprintf("[%d/%d]", i+1, len(v.closeStack))) + log.Info("Shutting down module...") -// StartApp starts registered App. -func (visor *Visor) StartApp(appName string) error { - for _, app := range visor.appsConf { - if app.App == appName { - startCh := make(chan struct{}) - - go func(app AppConfig) { - if err := visor.SpawnApp(&app, startCh); err != nil { - visor.logger. - WithError(err). - WithField("app_name", appName). - Warn("App stopped.") - } - }(app) + go func(ce closeElem) { + done <- ce.fn() + close(done) + }(ce) - <-startCh - return nil + select { + case ok := <-done: + if !ok { + log.WithField("elapsed", time.Since(start)).Warn("Module stopped with unexpected result.") + v.processReports(log, nil) + continue + } + log.WithField("elapsed", time.Since(start)).Info("Module stopped cleanly.") + case <-t.C: + log.WithField("elapsed", time.Since(start)).Error("Module timed out.") } } - return ErrAppProcNotRunning -} - -// SpawnApp configures and starts new App. -func (visor *Visor) SpawnApp(config *AppConfig, startCh chan<- struct{}) (err error) { - visor.logger.WithField("app_name", config.App).WithField("args", config.Args).Info("Spawning app.") - - if app, ok := reservedPorts[config.Port]; ok && app != config.App { - return fmt.Errorf("can't bind to reserved port %d", config.Port) - } - - procConf := appcommon.ProcConfig{ - AppName: config.App, - AppSrvAddr: visor.conf.AppServerAddr, - ProcKey: appcommon.RandProcKey(), - ProcArgs: config.Args, - ProcWorkDir: filepath.Join(visor.localPath, config.App), - VisorPK: visor.conf.Keys().PubKey, - RoutingPort: config.Port, - BinaryLoc: filepath.Join(visor.appsPath, config.App), - LogDBLoc: appLogLoc(visor.localPath, config.App), - } - if _, err := ensureDir(procConf.ProcWorkDir); err != nil { - return err - } - switch procConf.AppName { - case skyenv.VPNClientName, skyenv.VPNServerName: - procConf.ProcEnvs = makeVPNEnvs(visor.conf, visor.n) - } - - pid, err := visor.procM.Start(procConf) - if err != nil { - return fmt.Errorf("failed to start app %s: %w", config.App, err) - } - - if startCh != nil { - startCh <- struct{}{} - } - - visor.pidMu.Lock() - visor.logger.WithField("app_name", procConf.AppName).WithField("pid", pid).Debugf("Persisting app pid.") - if err := visor.persistPID(config.App, pid); err != nil { - visor.pidMu.Unlock() - return err - } - visor.pidMu.Unlock() - - return visor.procM.Wait(config.App) -} - -func appLogLoc(localPath, appName string) string { - return filepath.Join(localPath, appName+"_log.db") -} - -func makeVPNEnvs(visorConf *Config, n *snet.Network) []string { - var envCfg vpn.DirectRoutesEnvConfig - - if visorConf.Dmsg != nil { - envCfg.DmsgDiscovery = visorConf.Dmsg.Discovery - envCfg.DmsgServers = n.Dmsg().ConnectedServers() - } - if visorConf.Transport != nil { - envCfg.TPDiscovery = visorConf.Transport.Discovery - } - if visorConf.Routing != nil { - envCfg.RF = visorConf.Routing.RouteFinder - } - if visorConf.UptimeTracker != nil { - envCfg.UptimeTracker = visorConf.UptimeTracker.Addr - } - if visorConf.STCP != nil && len(visorConf.STCP.PubKeyTable) != 0 { - envCfg.STCPTable = visorConf.STCP.PubKeyTable - } - - envMap := vpn.AppEnvArgs(envCfg) - - envs := make([]string, 0, len(envMap)) - for k, v := range vpn.AppEnvArgs(envCfg) { - envs = append(envs, fmt.Sprintf("%s=%s", k, v)) - } - return envs -} - -func (visor *Visor) persistPID(name string, pid appcommon.ProcID) error { - pidF, err := visor.appsPIDFile() - if err != nil { - return err - } - - pidFName := pidF.Name() - if err := pidF.Close(); err != nil { - visor.logger.WithError(err).Warn("Failed to close PID file") - } - - data := fmt.Sprintf("%s %d\n", name, pid) - if err := pathutil.AtomicAppendToFile(pidFName, []byte(data)); err != nil { - visor.logger.WithError(err).Warn("Failed to save PID to file") - } - - return nil -} - -// StopApp stops running App. -func (visor *Visor) StopApp(appName string) error { - if _, ok := visor.procM.ProcByName(appName); !ok { - return ErrAppProcNotRunning - } - - visor.logger.Infof("Stopping app %s and closing ports", appName) - - if err := visor.procM.Stop(appName); err != nil { - visor.logger.Warn("Failed to stop app: ", err) - return err - } - + v.processReports(v.log, nil) + log.Info("Shutdown complete. Goodbye!") return nil } -// RestartApp restarts running App. -func (visor *Visor) RestartApp(name string) error { - visor.logger.Infof("Restarting app %v", name) - - if err := visor.StopApp(name); err != nil { - return fmt.Errorf("stop app %v: %w", name, err) - } - - if err := visor.StartApp(name); err != nil { - return fmt.Errorf("start app %v: %w", name, err) - } - - return nil +// TpDiscClient is a convenience function to obtain transport discovery client. +func (v *Visor) TpDiscClient() transport.DiscoveryClient { + return v.tpM.Conf.DiscoveryClient } // Exec executes a shell command. It returns combined stdout and stderr output and an error. -func (visor *Visor) Exec(command string) ([]byte, error) { +func (v *Visor) Exec(command string) ([]byte, error) { args := strings.Split(command, " ") cmd := exec.Command(args[0], args[1:]...) // nolint: gosec return cmd.CombinedOutput() @@ -681,10 +223,10 @@ func (visor *Visor) Exec(command string) ([]byte, error) { // Update updates visor. // It checks if visor update is available. // If it is, the method downloads a new visor versions, starts it and kills the current process. -func (visor *Visor) Update() (bool, error) { - updated, err := visor.updater.Update() +func (v *Visor) Update() (bool, error) { + updated, err := v.updater.Update() if err != nil { - visor.logger.Errorf("Failed to update visor: %v", err) + v.log.Errorf("Failed to update visor: %v", err) return false, err } @@ -692,127 +234,65 @@ func (visor *Visor) Update() (bool, error) { } // UpdateAvailable checks if visor update is available. -func (visor *Visor) UpdateAvailable() (*updater.Version, error) { - version, err := visor.updater.UpdateAvailable() +func (v *Visor) UpdateAvailable() (*updater.Version, error) { + version, err := v.updater.UpdateAvailable() if err != nil { - visor.logger.Errorf("Failed to check if visor update is available: %v", err) + v.log.Errorf("Failed to check if visor update is available: %v", err) return nil, err } return version, nil } -func (visor *Visor) setAutoStart(appName string, autoStart bool) error { - appConf, ok := visor.appsConf[appName] - if !ok { +func (v *Visor) setAutoStart(appName string, autoStart bool) error { + if _, ok := v.appL.AppState(appName); !ok { return ErrAppProcNotRunning } - appConf.AutoStart = autoStart - visor.appsConf[appName] = appConf - - visor.logger.Infof("Saving auto start = %v for app %v to config", autoStart, appName) - - return visor.updateAppAutoStart(appName, autoStart) + v.log.Infof("Saving auto start = %v for app %v to config", autoStart, appName) + return v.conf.UpdateAppAutostart(v.appL, appName, autoStart) } -func (visor *Visor) setSocksPassword(password string) error { - visor.logger.Infof("Changing skysocks password to %q", password) +func (v *Visor) setSocksPassword(password string) error { + v.log.Infof("Changing skysocks password to %q", password) const ( socksName = "skysocks" passcodeArgName = "-passcode" ) - if err := visor.updateAppArg(socksName, passcodeArgName, password); err != nil { + if err := v.conf.UpdateAppArg(v.appL, socksName, passcodeArgName, password); err != nil { return err } - if _, ok := visor.procM.ProcByName(socksName); ok { - visor.logger.Infof("Updated %v password, restarting it", socksName) - return visor.RestartApp(socksName) + if _, ok := v.procM.ProcByName(socksName); ok { + v.log.Infof("Updated %v password, restarting it", socksName) + return v.appL.RestartApp(socksName) } - visor.logger.Infof("Updated %v password", socksName) + v.log.Infof("Updated %v password", socksName) return nil } -func (visor *Visor) setSocksClientPK(pk cipher.PubKey) error { - visor.logger.Infof("Changing skysocks-client PK to %q", pk) +func (v *Visor) setSocksClientPK(pk cipher.PubKey) error { + v.log.Infof("Changing skysocks-client PK to %q", pk) const ( socksClientName = "skysocks-client" pkArgName = "-srv" ) - if err := visor.updateAppArg(socksClientName, pkArgName, pk.String()); err != nil { + if err := v.conf.UpdateAppArg(v.appL, socksClientName, pkArgName, pk.String()); err != nil { return err } - if _, ok := visor.procM.ProcByName(socksClientName); ok { - visor.logger.Infof("Updated %v PK, restarting it", socksClientName) - return visor.RestartApp(socksClientName) - } - - visor.logger.Infof("Updated %v PK", socksClientName) - - return nil -} - -func (visor *Visor) updateAppAutoStart(appName string, autoStart bool) error { - changed := false - - for i := range visor.conf.Apps { - if visor.conf.Apps[i].App == appName { - visor.conf.Apps[i].AutoStart = autoStart - if v, ok := visor.appsConf[appName]; ok { - v.AutoStart = autoStart - visor.appsConf[appName] = v - } - - changed = true - break - } - } - - if !changed { - return nil - } - - return visor.conf.flush() -} - -func (visor *Visor) updateAppArg(appName, argName, value string) error { - configChanged := true - - for i := range visor.conf.Apps { - argChanged := false - if visor.conf.Apps[i].App == appName { - configChanged = true - - for j := range visor.conf.Apps[i].Args { - if visor.conf.Apps[i].Args[j] == argName && j+1 < len(visor.conf.Apps[i].Args) { - visor.conf.Apps[i].Args[j+1] = value - argChanged = true - break - } - } - - if !argChanged { - visor.conf.Apps[i].Args = append(visor.conf.Apps[i].Args, argName, value) - } - - if v, ok := visor.appsConf[appName]; ok { - v.Args = visor.conf.Apps[i].Args - visor.appsConf[appName] = v - } - } + if _, ok := v.procM.ProcByName(socksClientName); ok { + v.log.Infof("Updated %v PK, restarting it", socksClientName) + return v.appL.RestartApp(socksClientName) } - if configChanged { - return visor.conf.flush() - } + v.log.Infof("Updated %v PK", socksClientName) return nil } diff --git a/pkg/visor/visor_init.go b/pkg/visor/visor_init.go new file mode 100644 index 000000000..c46319e74 --- /dev/null +++ b/pkg/visor/visor_init.go @@ -0,0 +1,418 @@ +package visor + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "path/filepath" + "sync" + "time" + + "github.com/SkycoinProject/dmsg" + "github.com/SkycoinProject/dmsg/cipher" + "github.com/SkycoinProject/dmsg/dmsgpty" + + "github.com/SkycoinProject/skywire-mainnet/internal/utclient" + "github.com/SkycoinProject/skywire-mainnet/internal/vpn" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appdisc" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/skywire-mainnet/pkg/routefinder/rfclient" + "github.com/SkycoinProject/skywire-mainnet/pkg/router" + "github.com/SkycoinProject/skywire-mainnet/pkg/setup/setupclient" + "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" + "github.com/SkycoinProject/skywire-mainnet/pkg/snet" + "github.com/SkycoinProject/skywire-mainnet/pkg/transport" + "github.com/SkycoinProject/skywire-mainnet/pkg/transport/tpdclient" + "github.com/SkycoinProject/skywire-mainnet/pkg/util/updater" +) + +type startFunc func(v *Visor) bool + +func initUpdater(v *Visor) bool { + report := v.makeReporter("updater") + + restartCheckDelay, err := time.ParseDuration(v.conf.RestartCheckDelay) + if err != nil { + return report(err) + } + + v.restartCtx.SetCheckDelay(restartCheckDelay) + v.restartCtx.RegisterLogger(v.log) + v.updater = updater.New(v.log, v.restartCtx, v.conf.Launcher.BinPath) + return report(nil) +} + +func initSNet(v *Visor) bool { + report := v.makeReporter("snet") + + n := snet.New(snet.Config{ + PubKey: v.conf.KeyPair.PubKey, + SecKey: v.conf.KeyPair.SecKey, + Dmsg: v.conf.Dmsg, + STCP: v.conf.STCP, + }) + if err := n.Init(); err != nil { + return report(err) + } + v.pushCloseStack("snet", func() bool { + return report(n.Close()) + }) + + v.net = n + return report(nil) +} + +func initDmsgpty(v *Visor) bool { + report := v.makeReporter("dmsgpty") + conf := v.conf.Dmsgpty + + if conf == nil { + v.log.Info("'dmsgpty' is not configured, skipping.") + return report(nil) + } + + var wl dmsgpty.Whitelist + if conf.AuthFile == "" { + wl = dmsgpty.NewMemoryWhitelist() + } else { + var err error + if wl, err = dmsgpty.NewJSONFileWhiteList(v.conf.Dmsgpty.AuthFile); err != nil { + return report(err) + } + } + + dmsgC := v.net.Dmsg() + if dmsgC == nil { + return report(errors.New("cannot create dmsgpty with nil dmsg client")) + } + + pty := dmsgpty.NewHost(dmsgC, wl) + + if ptyPort := conf.Port; ptyPort != 0 { + + ctx, cancel := context.WithCancel(context.Background()) + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + defer wg.Done() + if err := pty.ListenAndServe(ctx, ptyPort); err != nil { + report(fmt.Errorf("listen and serve stopped: %w", err)) + } + }() + + v.pushCloseStack("dmsgpty.serve", func() bool { + cancel() + wg.Wait() + return report(nil) + }) + } + + if conf.CLINet != "" { + if conf.CLINet == "unix" { + if err := os.MkdirAll(filepath.Dir(conf.CLIAddr), ownerRWX); err != nil { + return report(fmt.Errorf("failed to prepare unix file for dmsgpty cli listener: %w", err)) + } + } + + cliL, err := net.Listen(conf.CLINet, conf.CLIAddr) + if err != nil { + return report(fmt.Errorf("failed to start dmsgpty cli listener: %w", err)) + } + + ctx, cancel := context.WithCancel(context.Background()) + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + defer wg.Done() + if err := pty.ServeCLI(ctx, cliL); err != nil { + report(fmt.Errorf("serve cli stopped: %w", err)) + } + }() + + v.pushCloseStack("dmsgpty.cli", func() bool { + cancel() + ok := report(cliL.Close()) + wg.Wait() + return ok + }) + } + + v.pty = pty + return report(nil) +} + +func initTransport(v *Visor) bool { + report := v.makeReporter("transport") + conf := v.conf.Transport + + tpdC, err := tpdclient.NewHTTP(conf.Discovery, v.conf.KeyPair.PubKey, v.conf.KeyPair.SecKey) + if err != nil { + return report(fmt.Errorf("failed to create transport discovery client: %w", err)) + } + + var logS transport.LogStore + switch conf.LogStore.Type { + case LogStoreFile: + logS, err = transport.FileTransportLogStore(conf.LogStore.Location) + if err != nil { + return report(fmt.Errorf("failed to create %s log store: %w", LogStoreFile, err)) + } + case LogStoreMemory: + logS = transport.InMemoryTransportLogStore() + default: + return report(fmt.Errorf("invalid log store type: %s", conf.LogStore.Type)) + } + + tpMConf := transport.ManagerConfig{ + PubKey: v.conf.KeyPair.PubKey, + SecKey: v.conf.KeyPair.SecKey, + DefaultVisors: conf.TrustedVisors, + DiscoveryClient: tpdC, + LogStore: logS, + } + + tpM, err := transport.NewManager(v.net, &tpMConf) + if err != nil { + return report(fmt.Errorf("failed to start transport manager: %w", err)) + } + + ctx, cancel := context.WithCancel(context.Background()) + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + defer wg.Done() + tpM.Serve(ctx) + }() + + v.pushCloseStack("transport.manager", func() bool { + cancel() + ok := report(tpM.Close()) + wg.Wait() + return ok + }) + + v.tpM = tpM + return report(nil) +} + +func initRouter(v *Visor) bool { + report := v.makeReporter("router") + conf := v.conf.Routing + + rConf := router.Config{ + Logger: v.MasterLogger().PackageLogger("router"), + PubKey: v.conf.KeyPair.PubKey, + SecKey: v.conf.KeyPair.SecKey, + TransportManager: v.tpM, + RouteFinder: rfclient.NewHTTP(conf.RouteFinder, time.Duration(conf.RouteFinderTimeout)), + RouteGroupDialer: setupclient.NewSetupNodeDialer(), + SetupNodes: conf.SetupNodes, + RulesGCInterval: 0, // TODO + } + r, err := router.New(v.net, &rConf) + if err != nil { + return report(fmt.Errorf("failed to create router: %w", err)) + } + + ctx, cancel := context.WithCancel(context.Background()) + wg := new(sync.WaitGroup) + wg.Add(1) + + go func() { + defer wg.Done() + if err := r.Serve(ctx); err != nil { + report(fmt.Errorf("serve router stopped: %w", err)) + } + }() + + v.pushCloseStack("router.serve", func() bool { + cancel() + ok := report(r.Close()) + wg.Wait() + return ok + }) + + v.router = r + return report(nil) +} + +func initLauncher(v *Visor) bool { + report := v.makeReporter("launcher") + conf := v.conf.Launcher + + // Prepare app discovery factory. + factory := appdisc.Factory{Log: v.MasterLogger().PackageLogger("app_disc")} + if conf.Discovery != nil { + factory.PK = v.conf.KeyPair.PubKey + factory.SK = v.conf.KeyPair.SecKey + factory.UpdateInterval = time.Duration(conf.Discovery.UpdateInterval) + factory.ProxyDisc = conf.Discovery.ProxyDisc + } + + // Prepare proc manager. + procMLog := v.MasterLogger().PackageLogger("proc_manager") + procM, err := appserver.NewProcManager(procMLog, &factory, conf.ServerAddr) + if err != nil { + return report(fmt.Errorf("failed to start proc_manager: %w", err)) + } + + v.pushCloseStack("launcher.proc_manager", func() bool { + return report(procM.Close()) + }) + + // Prepare launcher. + launchConf := launcher.Config{ + VisorPK: v.conf.KeyPair.PubKey, + Apps: conf.Apps, + ServerAddr: conf.ServerAddr, + BinPath: conf.BinPath, + LocalPath: conf.LocalPath, + } + launchLog := v.MasterLogger().PackageLogger("launcher") + launch, err := launcher.NewLauncher(launchLog, launchConf, v.net.Dmsg(), v.router, procM) + if err != nil { + return report(fmt.Errorf("failed to start launcher: %w", err)) + } + launch.AutoStart(map[string]func() []string{ + skyenv.VPNClientName: func() []string { return makeVPNEnvs(v.conf, v.net) }, + skyenv.VPNServerName: func() []string { return makeVPNEnvs(v.conf, v.net) }, + }) + + v.procM = procM + v.appL = launch + return report(nil) +} + +func makeVPNEnvs(conf *Config, n *snet.Network) []string { + var envCfg vpn.DirectRoutesEnvConfig + + if conf.Dmsg != nil { + envCfg.DmsgDiscovery = conf.Dmsg.Discovery + envCfg.DmsgServers = n.Dmsg().ConnectedServers() + } + if conf.Transport != nil { + envCfg.TPDiscovery = conf.Transport.Discovery + } + if conf.Routing != nil { + envCfg.RF = conf.Routing.RouteFinder + } + if conf.UptimeTracker != nil { + envCfg.UptimeTracker = conf.UptimeTracker.Addr + } + if conf.STCP != nil && len(conf.STCP.PKTable) != 0 { + envCfg.STCPTable = conf.STCP.PKTable + } + + envMap := vpn.AppEnvArgs(envCfg) + + envs := make([]string, 0, len(envMap)) + for k, v := range vpn.AppEnvArgs(envCfg) { + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) + } + return envs +} + +func initCLI(v *Visor) bool { + report := v.makeReporter("cli") + + if v.conf.CLIAddr == "" { + v.log.Info("'cli_addr' is not configured, skipping.") + return report(nil) + } + + cliL, err := net.Listen("tcp", v.conf.CLIAddr) + if err != nil { + return report(err) + } + + v.pushCloseStack("cli.listener", func() bool { + return report(cliL.Close()) + }) + + rpcS, err := newRPCServer(v, "CLI") + if err != nil { + return report(fmt.Errorf("failed to start rpc server for cli: %w", err)) + } + go rpcS.Accept(cliL) // We not not use sync.WaitGroup here as it will never return anyway. + + return report(nil) +} + +func initHypervisors(v *Visor) bool { + report := v.makeReporter("hypervisors") + + hvErrs := make(map[cipher.PubKey]chan error, len(v.conf.Hypervisors)) + for _, hv := range v.conf.Hypervisors { + hvErrs[hv.PubKey] = make(chan error, 1) + } + + for hvPK, hvErrs := range hvErrs { + log := v.MasterLogger().PackageLogger("hypervisor_client").WithField("hypervisor_pk", hvPK) + + addr := dmsg.Addr{PK: hvPK, Port: skyenv.DmsgHypervisorPort} + rpcS, err := newRPCServer(v, addr.PK.String()[:shortHashLen]) + if err != nil { + return report(fmt.Errorf("failed to start RPC server for hypervisor %s: %w", hvPK, err)) + } + + ctx, cancel := context.WithCancel(context.Background()) + wg := new(sync.WaitGroup) + wg.Add(1) + + go func(hvErrs chan error) { + defer wg.Done() + ServeRPCClient(ctx, log, v.net, rpcS, addr, hvErrs) + }(hvErrs) + + v.pushCloseStack("hypervisor."+hvPK.String()[:shortHashLen], func() bool { + cancel() + wg.Wait() + return true + }) + } + + return report(nil) +} + +func initUptimeTracker(v *Visor) bool { + report := v.makeReporter("uptime_tracker") + conf := v.conf.UptimeTracker + + if conf == nil { + v.log.Info("'uptime_tracker' is not configured, skipping.") + return true + } + + ut, err := utclient.NewHTTP(conf.Addr, v.conf.KeyPair.PubKey, v.conf.KeyPair.SecKey) + if err != nil { + // TODO(evanlinjin): We should design utclient to retry automatically instead of returning error. + //return report(err) + v.log.WithError(err).Warn("Failed to connect to uptime tracker.") + return true + } + + log := v.MasterLogger().PackageLogger("uptime_tracker") + ticker := time.NewTicker(1 * time.Second) + + go func() { + for range ticker.C { + ctx := context.Background() + if err := ut.UpdateVisorUptime(ctx); err != nil { + log.WithError(err).Warn("Failed to update visor uptime.") + } + } + }() + + v.pushCloseStack("uptime_tracker", func() bool { + ticker.Stop() + return report(nil) + }) + + return true +} diff --git a/pkg/visor/visor_test.go b/pkg/visor/visor_test.go index cc705e1e4..42023096b 100644 --- a/pkg/visor/visor_test.go +++ b/pkg/visor/visor_test.go @@ -1,28 +1,12 @@ package visor import ( - "fmt" "io/ioutil" "log" "os" - "path/filepath" "testing" - "time" - "github.com/SkycoinProject/dmsg" - "github.com/SkycoinProject/dmsg/cipher" - "github.com/SkycoinProject/dmsg/disc" "github.com/SkycoinProject/skycoin/src/util/logging" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/SkycoinProject/skywire-mainnet/internal/testhelpers" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" - "github.com/SkycoinProject/skywire-mainnet/pkg/router" - "github.com/SkycoinProject/skywire-mainnet/pkg/snet" - "github.com/SkycoinProject/skywire-mainnet/pkg/transport" ) var masterLogger *logging.MasterLogger @@ -51,7 +35,7 @@ func TestMain(m *testing.M) { // })) // defer srv.Close() // -// conf := Config{LocalPath: "local", AppsPath: "apps"} +// conf := Config{AppLocalPath: "local", AppBinPath: "apps"} // conf.Visor.PubKey = pk // conf.Visor.SecKey = sk // conf.Dmsg.Discovery = "http://skywire.skycoin.com:8002" @@ -76,226 +60,227 @@ func TestMain(m *testing.M) { // assert.NotNil(t, visor.startedApps) //} -func TestVisorStartClose(t *testing.T) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - require.NoError(t, os.RemoveAll("apps-pid.txt")) - }() - - r := &router.MockRouter{} - r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) - r.On("Close").Return(testhelpers.NoErr) - - apps := make(map[string]AppConfig) - appCfg := []AppConfig{ - { - App: "skychat", - AutoStart: true, - Port: 1, - }, - { - App: "foo", - AutoStart: false, - }, - } - - for _, app := range appCfg { - apps[app.App] = app - } - - defer func() { - require.NoError(t, os.RemoveAll("skychat")) - }() - - visorCfg := Config{ - KeyPair: NewKeyPair(), - AppServerAddr: appcommon.DefaultAppSrvAddr, - } - - logger := logging.MustGetLogger("test") - - visor := &Visor{ - conf: &visorCfg, - router: r, - appsConf: apps, - logger: logger, - } - - appPID1 := appcommon.ProcID(10) - - pm := &appserver.MockProcManager{} - pm.On("Start", mock.Anything).Return(appPID1, testhelpers.NoErr) - pm.On("Wait", apps["skychat"].App).Return(testhelpers.NoErr) - pm.On("Close").Return(testhelpers.NoErr) - visor.procM = pm - - dmsgC := dmsg.NewClient(cipher.PubKey{}, cipher.SecKey{}, disc.NewMock(), nil) - go dmsgC.Serve() - - var netConf snet.Config - - network := snet.NewRaw(netConf, dmsgC, nil) - tmConf := &transport.ManagerConfig{ - PubKey: cipher.PubKey{}, - DiscoveryClient: transport.NewDiscoveryMock(), - } - - tm, err := transport.NewManager(network, tmConf) - visor.tm = tm - require.NoError(t, err) - - errCh := make(chan error) - go func() { - errCh <- visor.Start() - }() - - require.NoError(t, <-errCh) - time.Sleep(100 * time.Millisecond) - require.NoError(t, visor.Close()) -} - -func TestVisorSpawnApp(t *testing.T) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "") - require.NoError(t, err) - defer func() { - require.NoError(t, os.RemoveAll(tmpDir)) - require.NoError(t, os.RemoveAll("apps-pid.txt")) - }() - - r := &router.MockRouter{} - r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) - r.On("Close").Return(testhelpers.NoErr) - - defer func() { - require.NoError(t, os.RemoveAll("skychat")) - }() - - app := AppConfig{ - App: "skychat", - AutoStart: false, - Port: 10, - Args: []string{"foo"}, - } - - apps := make(map[string]AppConfig) - apps["skychat"] = app - - visorCfg := Config{ - KeyPair: NewKeyPair(), - AppServerAddr: appcommon.DefaultAppSrvAddr, - } - - visor := &Visor{ - router: r, - appsConf: apps, - logger: logging.MustGetLogger("test"), - conf: &visorCfg, - } - - appPID := appcommon.ProcID(10) - - pm := &appserver.MockProcManager{} - pm.On("Wait", app.App).Return(testhelpers.NoErr) - pm.On("Start", mock.Anything).Return(appPID, testhelpers.NoErr) - pm.On("ProcByName", app.App).Return(new(appserver.Proc), true) - pm.On("Stop", app.App).Return(testhelpers.NoErr) - - visor.procM = pm - - require.NoError(t, visor.StartApp(app.App)) - time.Sleep(100 * time.Millisecond) - - _, ok := visor.procM.ProcByName(app.App) - require.True(t, ok) - - require.NoError(t, visor.StopApp(app.App)) -} - -func TestVisorSpawnAppValidations(t *testing.T) { - tmpDir, err := ioutil.TempDir(os.TempDir(), "") - require.NoError(t, err) - defer func() { require.NoError(t, os.RemoveAll(tmpDir)) }() - - r := &router.MockRouter{} - r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) - r.On("Close").Return(testhelpers.NoErr) - - defer func() { - require.NoError(t, os.RemoveAll("skychat")) - }() - - c := &Config{ - KeyPair: NewKeyPair(), - AppServerAddr: appcommon.DefaultAppSrvAddr, - } - - visor := &Visor{ - router: r, - logger: logging.MustGetLogger("test"), - conf: c, - } - - t.Run("fail - can't bind to reserved port", func(t *testing.T) { - app := AppConfig{ - App: "skychat", - Port: 3, - } - - appCfg := appcommon.ProcConfig{ - AppName: app.App, - AppSrvAddr: appcommon.DefaultAppSrvAddr, - VisorPK: c.Keys().PubKey, - RoutingPort: app.Port, - ProcWorkDir: filepath.Join(tmpDir, app.App), - } - - appPID := appcommon.ProcID(10) - - pm := &appserver.MockProcManager{} - pm.On("Run", mock.Anything, appCfg, app.Args, mock.Anything, mock.Anything).Return(appPID, testhelpers.NoErr) - pm.On("ProcByName", app.App).Return(new(appserver.Proc), false) - - visor.procM = pm - - errCh := make(chan error) - go func() { - errCh <- visor.SpawnApp(&app, nil) - }() - - time.Sleep(100 * time.Millisecond) - - err := <-errCh - require.Error(t, err) - - wantErr := "can't bind to reserved port 3" - assert.Equal(t, wantErr, err.Error()) - }) - - t.Run("fail - app already started", func(t *testing.T) { - app := AppConfig{ - App: "skychat", - Port: 10, - } - wantErr := fmt.Sprintf("failed to start app skychat: %s", appserver.ErrAppAlreadyStarted) - - appPID := appcommon.ProcID(10) - - pm := &appserver.MockProcManager{} - pm.On("Start", mock.Anything).Return(appPID, appserver.ErrAppAlreadyStarted) - pm.On("ProcByName", app.App).Return(new(appserver.Proc), true) - - visor.procM = pm - - errCh := make(chan error) - go func() { - errCh <- visor.SpawnApp(&app, nil) - }() - - time.Sleep(100 * time.Millisecond) - err := <-errCh - require.Error(t, err) - assert.Equal(t, wantErr, err.Error()) - }) -} +// TODO(evanlinjin): Move to /pkg/app/launcher +//func TestVisorStartClose(t *testing.T) { +// tmpDir, err := ioutil.TempDir(os.TempDir(), "") +// require.NoError(t, err) +// defer func() { +// require.NoError(t, os.RemoveAll(tmpDir)) +// require.NoError(t, os.RemoveAll("apps-pid.txt")) +// }() +// +// r := &router.MockRouter{} +// r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) +// r.On("Close").Return(testhelpers.NoErr) +// +// apps := make(map[string]AppConfig) +// appCfg := []AppConfig{ +// { +// App: "skychat", +// AutoStart: true, +// Port: 1, +// }, +// { +// App: "foo", +// AutoStart: false, +// }, +// } +// +// for _, app := range appCfg { +// apps[app.App] = app +// } +// +// defer func() { +// require.NoError(t, os.RemoveAll("skychat")) +// }() +// +// visorCfg := Config{ +// KeyPair: NewKeyPair(), +// AppServerAddr: appcommon.DefaultAppSrvAddr, +// } +// +// logger := logging.MustGetLogger("test") +// +// visor := &Visor{ +// conf: &visorCfg, +// router: r, +// appsConf: apps, +// log: logger, +// } +// +// appPID1 := appcommon.ProcID(10) +// +// pm := &appserver.MockProcManager{} +// pm.On("Start", mock.Anything).Return(appPID1, testhelpers.NoErr) +// pm.On("Wait", apps["skychat"].App).Return(testhelpers.NoErr) +// pm.On("Close").Return(testhelpers.NoErr) +// visor.procM = pm +// +// dmsgC := dmsg.NewClient(cipher.PubKey{}, cipher.SecKey{}, disc.NewMock(), nil) +// go dmsgC.Serve() +// +// var netConf snet.Config +// +// network := snet.NewRaw(netConf, dmsgC, nil) +// tmConf := &transport.ManagerConfig{ +// PubKey: cipher.PubKey{}, +// DiscoveryClient: transport.NewDiscoveryMock(), +// } +// +// tm, err := transport.NewManager(network, tmConf) +// visor.tpM = tm +// require.NoError(t, err) +// +// errCh := make(chan error) +// go func() { +// errCh <- visor.Start() +// }() +// +// require.NoError(t, <-errCh) +// time.Sleep(100 * time.Millisecond) +// require.NoError(t, visor.Close()) +//} +// +//func TestVisorSpawnApp(t *testing.T) { +// tmpDir, err := ioutil.TempDir(os.TempDir(), "") +// require.NoError(t, err) +// defer func() { +// require.NoError(t, os.RemoveAll(tmpDir)) +// require.NoError(t, os.RemoveAll("apps-pid.txt")) +// }() +// +// r := &router.MockRouter{} +// r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) +// r.On("Close").Return(testhelpers.NoErr) +// +// defer func() { +// require.NoError(t, os.RemoveAll("skychat")) +// }() +// +// app := AppConfig{ +// App: "skychat", +// AutoStart: false, +// Port: 10, +// Args: []string{"foo"}, +// } +// +// apps := make(map[string]AppConfig) +// apps["skychat"] = app +// +// visorCfg := Config{ +// KeyPair: NewKeyPair(), +// AppServerAddr: appcommon.DefaultAppSrvAddr, +// } +// +// visor := &Visor{ +// router: r, +// appsConf: apps, +// log: logging.MustGetLogger("test"), +// conf: &visorCfg, +// } +// +// appPID := appcommon.ProcID(10) +// +// pm := &appserver.MockProcManager{} +// pm.On("Wait", app.App).Return(testhelpers.NoErr) +// pm.On("Start", mock.Anything).Return(appPID, testhelpers.NoErr) +// pm.On("ProcByName", app.App).Return(new(appserver.Proc), true) +// pm.On("Stop", app.App).Return(testhelpers.NoErr) +// +// visor.procM = pm +// +// require.NoError(t, visor.StartApp(app.App)) +// time.Sleep(100 * time.Millisecond) +// +// _, ok := visor.procM.ProcByName(app.App) +// require.True(t, ok) +// +// require.NoError(t, visor.StopApp(app.App)) +//} +// +//func TestVisorSpawnAppValidations(t *testing.T) { +// tmpDir, err := ioutil.TempDir(os.TempDir(), "") +// require.NoError(t, err) +// defer func() { require.NoError(t, os.RemoveAll(tmpDir)) }() +// +// r := &router.MockRouter{} +// r.On("Serve", mock.Anything /* context */).Return(testhelpers.NoErr) +// r.On("Close").Return(testhelpers.NoErr) +// +// defer func() { +// require.NoError(t, os.RemoveAll("skychat")) +// }() +// +// c := &Config{ +// KeyPair: NewKeyPair(), +// AppServerAddr: appcommon.DefaultAppSrvAddr, +// } +// +// visor := &Visor{ +// router: r, +// log: logging.MustGetLogger("test"), +// conf: c, +// } +// +// t.Run("fail - can't bind to reserved port", func(t *testing.T) { +// app := AppConfig{ +// App: "skychat", +// Port: 3, +// } +// +// appCfg := appcommon.ProcConfig{ +// AppName: app.App, +// AppSrvAddr: appcommon.DefaultAppSrvAddr, +// VisorPK: c.Keys().PubKey, +// RoutingPort: app.Port, +// ProcWorkDir: filepath.Join(tmpDir, app.App), +// } +// +// appPID := appcommon.ProcID(10) +// +// pm := &appserver.MockProcManager{} +// pm.On("Run", mock.Anything, appCfg, app.Args, mock.Anything, mock.Anything).Return(appPID, testhelpers.NoErr) +// pm.On("ProcByName", app.App).Return(new(appserver.Proc), false) +// +// visor.procM = pm +// +// errCh := make(chan error) +// go func() { +// errCh <- visor.SpawnApp(&app, nil) +// }() +// +// time.Sleep(100 * time.Millisecond) +// +// err := <-errCh +// require.Error(t, err) +// +// wantErr := "can't bind to reserved port 3" +// assert.Equal(t, wantErr, err.Error()) +// }) +// +// t.Run("fail - app already started", func(t *testing.T) { +// app := AppConfig{ +// App: "skychat", +// Port: 10, +// } +// wantErr := fmt.Sprintf("failed to start app skychat: %s", appserver.ErrAppAlreadyStarted) +// +// appPID := appcommon.ProcID(10) +// +// pm := &appserver.MockProcManager{} +// pm.On("Start", mock.Anything).Return(appPID, appserver.ErrAppAlreadyStarted) +// pm.On("ProcByName", app.App).Return(new(appserver.Proc), true) +// +// visor.procM = pm +// +// errCh := make(chan error) +// go func() { +// errCh <- visor.SpawnApp(&app, nil) +// }() +// +// time.Sleep(100 * time.Millisecond) +// err := <-errCh +// require.Error(t, err) +// assert.Equal(t, wantErr, err.Error()) +// }) +//} From 8adb30a5fb144abf4eab6a1b5e41b3dd68117bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 15 May 2020 16:58:55 +1200 Subject: [PATCH 14/17] Changes as suggested by @Darkren --- pkg/app/launcher/launcher.go | 6 +++++- pkg/hypervisor/config.go | 2 +- pkg/visor/config.go | 4 ++-- pkg/visor/{visor_init.go => init.go} | 16 +++++++++++++++- pkg/visor/visor.go | 16 ++-------------- 5 files changed, 25 insertions(+), 19 deletions(-) rename pkg/visor/{visor_init.go => init.go} (97%) diff --git a/pkg/app/launcher/launcher.go b/pkg/app/launcher/launcher.go index a31164107..bdc8e3ff0 100644 --- a/pkg/app/launcher/launcher.go +++ b/pkg/app/launcher/launcher.go @@ -24,6 +24,10 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/util/pathutil" ) +const ( + appsPIDFileName = "apps-pid.txt" +) + // Launcher associated errors. var ( ErrAppNotFound = errors.New("app not found") @@ -284,7 +288,7 @@ func ensureDir(path *string) error { */ func (l *Launcher) pidFile() (*os.File, error) { - return os.OpenFile(filepath.Join(l.conf.LocalPath, "apps-pid.txt"), os.O_RDWR|os.O_CREATE, 0600) + return os.OpenFile(filepath.Join(l.conf.LocalPath, appsPIDFileName), os.O_RDWR|os.O_CREATE, 0600) } func (l *Launcher) persistPID(appName string, pid appcommon.ProcID) error { diff --git a/pkg/hypervisor/config.go b/pkg/hypervisor/config.go index ff74013b0..7de1dcfd2 100644 --- a/pkg/hypervisor/config.go +++ b/pkg/hypervisor/config.go @@ -48,7 +48,7 @@ func (hk *Key) UnmarshalText(text []byte) error { type Config struct { PK cipher.PubKey `json:"public_key"` SK cipher.SecKey `json:"secret_key"` - DBPath string `json:"db_path"` // path to store database file. + DBPath string `json:"db_path"` // Path to store database file. EnableAuth bool `json:"enable_auth"` // Whether to enable user management. Cookies CookieConfig `json:"cookies"` // Configures cookies (for session management). DmsgDiscovery string `json:"dmsg_discovery"` // Dmsg discovery address. diff --git a/pkg/visor/config.go b/pkg/visor/config.go index 8ff8d509d..59856c367 100644 --- a/pkg/visor/config.go +++ b/pkg/visor/config.go @@ -193,7 +193,7 @@ func DefaultConfig(log *logging.MasterLogger, configPath string, keys *KeyPair) } // UpdateAppAutostart modifies a single app's autostart value within the config and also the given launcher. -// The updated config gets flushed to file is there are any changes. +// The updated config gets flushed to file if there are any changes. func (c *Config) UpdateAppAutostart(launch *launcher.Launcher, appName string, autoStart bool) error { c.mu.Lock() defer c.mu.Unlock() @@ -224,7 +224,7 @@ func (c *Config) UpdateAppAutostart(launch *launcher.Launcher, appName string, a } // UpdateAppArg updates the cli flag of the specified app config and also within the launcher. -// The updated config gets flushed to file is there are any changes. +// The updated config gets flushed to file if there are any changes. func (c *Config) UpdateAppArg(launch *launcher.Launcher, appName, argName, value string) error { c.mu.Lock() defer c.mu.Unlock() diff --git a/pkg/visor/visor_init.go b/pkg/visor/init.go similarity index 97% rename from pkg/visor/visor_init.go rename to pkg/visor/init.go index c46319e74..d354ad4bc 100644 --- a/pkg/visor/visor_init.go +++ b/pkg/visor/init.go @@ -29,7 +29,21 @@ import ( "github.com/SkycoinProject/skywire-mainnet/pkg/util/updater" ) -type startFunc func(v *Visor) bool +type initFunc func(v *Visor) bool + +func initStack() []initFunc { + return []initFunc{ + initUpdater, + initSNet, + initDmsgpty, + initTransport, + initRouter, + initLauncher, + initCLI, + initHypervisors, + initUptimeTracker, + } +} func initUpdater(v *Visor) bool { report := v.makeReporter("updater") diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index 483199dd1..a7360bb05 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -128,24 +128,12 @@ func NewVisor(conf *Config, restartCtx *restart.Context) (v *Visor, ok bool) { log.WithField("public_key", conf.KeyPair.PubKey).Info("Begin startup.") v.startedAt = time.Now() - startStack := []startFunc{ - initUpdater, - initSNet, - initDmsgpty, - initTransport, - initRouter, - initLauncher, - initCLI, - initHypervisors, - initUptimeTracker, - } - - for i, startFn := range startStack { + for i, startFn := range initStack() { name := strings.ToLower(strings.TrimPrefix(filepath.Base(runtime.FuncForPC(reflect.ValueOf(startFn).Pointer()).Name()), "visor.init")) start := time.Now() log := v.MasterLogger().PackageLogger(fmt.Sprintf("visor:startup:%s", name)). - WithField("func", fmt.Sprintf("[%d/%d]", i+1, len(startStack))) + WithField("func", fmt.Sprintf("[%d/%d]", i+1, len(initStack()))) log.Info("Starting module...") if ok := startFn(v); !ok { From 912fe091da858e3db3add8f26178c0df557cfb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 15 May 2020 17:10:22 +1200 Subject: [PATCH 15/17] More changes as suggested by @Darkren --- cmd/skywire-cli/commands/visor/gen-config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/skywire-cli/commands/visor/gen-config.go b/cmd/skywire-cli/commands/visor/gen-config.go index 19e99fea4..578f8e0db 100644 --- a/cmd/skywire-cli/commands/visor/gen-config.go +++ b/cmd/skywire-cli/commands/visor/gen-config.go @@ -54,6 +54,8 @@ var genConfigCmd = &cobra.Command{ Run: func(_ *cobra.Command, _ []string) { var conf *visor.Config + // TODO(evanlinjin): Decide whether we still need this feature in the future. + // https://github.com/SkycoinProject/skywire-mainnet/pull/360#discussion_r425080223 switch configLocType { case pathutil.WorkingDirLoc: var err error From 19bee9457d99841e7013e5de1f534c4901c8baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Fri, 15 May 2020 19:49:14 +1200 Subject: [PATCH 16/17] Updated comments as suggested by @Darkren --- pkg/app/appdisc/const.go | 7 +++++-- pkg/app/appserver/proc.go | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/app/appdisc/const.go b/pkg/app/appdisc/const.go index 5849e0109..35ed12c08 100644 --- a/pkg/app/appdisc/const.go +++ b/pkg/app/appdisc/const.go @@ -1,7 +1,10 @@ package appdisc -// ChangeValue keys. +// ChangeValue keys. Each key changes a different value for proxyUpdater.ChangeValue const ( - ConnCountValue = "conn_count" + // ConnCountValue represents the number of remote connections to a given proc. + ConnCountValue = "conn_count" + + // ListenerCountValue represents the number of listeners used by a given proc. ListenerCountValue = "listener_count" ) diff --git a/pkg/app/appserver/proc.go b/pkg/app/appserver/proc.go index 1ac55fc64..c63d3e47d 100644 --- a/pkg/app/appserver/proc.go +++ b/pkg/app/appserver/proc.go @@ -22,9 +22,9 @@ var ( errProcNotStarted = errors.New("process is not started") ) -// Proc is a wrapper for a skywire app. Encapsulates -// the running process itself and the RPC server for -// app/visor communication. +// Proc is an instance of a skywire app. It encapsulates the running process itself and the RPC server for app/visor +// communication. +// TODO(evanlinjin): In the future, we will implement the ability to run multiple instances (procs) of a single app. type Proc struct { disc appdisc.Updater // app discovery client conf appcommon.ProcConfig From c586d020d3a05417f83d344122452f848e2e3db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sat, 16 May 2020 00:24:46 +1200 Subject: [PATCH 17/17] Changes as suggested by @nkryuchkov --- cmd/skywire-cli/commands/visor/app.go | 3 +-- cmd/skywire-visor/commands/root.go | 3 ++- pkg/app/appnet/skywire_networker.go | 3 +-- pkg/hypervisor/hypervisor.go | 3 +-- pkg/visor/config.go | 11 ++++++----- pkg/visor/init.go | 6 ++++-- pkg/visor/rpc.go | 3 +-- pkg/visor/rpc_client.go | 6 ++---- pkg/visor/visor.go | 17 ++++++++++++----- 9 files changed, 30 insertions(+), 25 deletions(-) diff --git a/cmd/skywire-cli/commands/visor/app.go b/cmd/skywire-cli/commands/visor/app.go index 2d1c82a19..f8a539388 100644 --- a/cmd/skywire-cli/commands/visor/app.go +++ b/cmd/skywire-cli/commands/visor/app.go @@ -8,11 +8,10 @@ import ( "text/tabwriter" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" - "github.com/spf13/cobra" "github.com/SkycoinProject/skywire-mainnet/cmd/skywire-cli/internal" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" ) func init() { diff --git a/cmd/skywire-visor/commands/root.go b/cmd/skywire-visor/commands/root.go index 49283971d..427956b54 100644 --- a/cmd/skywire-visor/commands/root.go +++ b/cmd/skywire-visor/commands/root.go @@ -160,10 +160,11 @@ func (rc *runConf) readConfig() *runConf { rc.logger.WithField("file", cp).Info("Reading config from file...") reader = file confPath = cp + } else { rc.logger.Info("Reading config from STDIN...") reader = bufio.NewReader(os.Stdin) - confPath = "STDIN" + confPath = visor.StdinName } rc.conf = visor.BaseConfig(rc.masterLogger, confPath) diff --git a/pkg/app/appnet/skywire_networker.go b/pkg/app/appnet/skywire_networker.go index 9074ee6f1..30f0201f2 100644 --- a/pkg/app/appnet/skywire_networker.go +++ b/pkg/app/appnet/skywire_networker.go @@ -9,9 +9,8 @@ import ( "sync" "sync/atomic" - "github.com/sirupsen/logrus" - "github.com/SkycoinProject/dmsg/netutil" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" diff --git a/pkg/hypervisor/hypervisor.go b/pkg/hypervisor/hypervisor.go index 23e1d342d..3f4cbcd62 100644 --- a/pkg/hypervisor/hypervisor.go +++ b/pkg/hypervisor/hypervisor.go @@ -13,8 +13,6 @@ import ( "sync" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" - "github.com/SkycoinProject/dmsg" "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/dmsgpty" @@ -25,6 +23,7 @@ import ( "github.com/google/uuid" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" diff --git a/pkg/visor/config.go b/pkg/visor/config.go index 59856c367..7cd568293 100644 --- a/pkg/visor/config.go +++ b/pkg/visor/config.go @@ -8,12 +8,11 @@ import ( "sync" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" - "github.com/SkycoinProject/skywire-mainnet/pkg/restart" - "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" + "github.com/SkycoinProject/skywire-mainnet/pkg/restart" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/skyenv" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" @@ -29,6 +28,9 @@ const ( // ConfigVersion of the visor config. // TODO: Put this in skyenv? ConfigVersion = "v1.0.0" + + // StdinName is the path name used to identify STDIN. + StdinName = "STDIN" ) var ( @@ -72,9 +74,8 @@ func (c *Config) flush() error { switch c.path { case "": return ErrNoConfigPath - case "STDIN": + case StdinName: return nil - default: } j, err := json.Marshal(c) diff --git a/pkg/visor/init.go b/pkg/visor/init.go index d354ad4bc..523f7edda 100644 --- a/pkg/visor/init.go +++ b/pkg/visor/init.go @@ -353,7 +353,7 @@ func initCLI(v *Visor) bool { if err != nil { return report(fmt.Errorf("failed to start rpc server for cli: %w", err)) } - go rpcS.Accept(cliL) // We not not use sync.WaitGroup here as it will never return anyway. + go rpcS.Accept(cliL) // We do not use sync.WaitGroup here as it will never return anyway. return report(nil) } @@ -395,6 +395,8 @@ func initHypervisors(v *Visor) bool { } func initUptimeTracker(v *Visor) bool { + const tickDuration = time.Second + report := v.makeReporter("uptime_tracker") conf := v.conf.UptimeTracker @@ -412,7 +414,7 @@ func initUptimeTracker(v *Visor) bool { } log := v.MasterLogger().PackageLogger("uptime_tracker") - ticker := time.NewTicker(1 * time.Second) + ticker := time.NewTicker(tickDuration) go func() { for range ticker.C { diff --git a/pkg/visor/rpc.go b/pkg/visor/rpc.go index c9b64928a..dd749227b 100644 --- a/pkg/visor/rpc.go +++ b/pkg/visor/rpc.go @@ -9,12 +9,11 @@ import ( "os" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" - "github.com/SkycoinProject/dmsg/cipher" "github.com/google/uuid" "github.com/sirupsen/logrus" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/transport" "github.com/SkycoinProject/skywire-mainnet/pkg/util/buildinfo" diff --git a/pkg/visor/rpc_client.go b/pkg/visor/rpc_client.go index cfcdec5ac..e9e2cebc4 100644 --- a/pkg/visor/rpc_client.go +++ b/pkg/visor/rpc_client.go @@ -11,15 +11,13 @@ import ( "sync" "time" - "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" - - "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" - "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/skycoin/src/util/logging" "github.com/google/uuid" "github.com/sirupsen/logrus" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/appcommon" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/routing" "github.com/SkycoinProject/skywire-mainnet/pkg/snet/snettest" diff --git a/pkg/visor/visor.go b/pkg/visor/visor.go index a7360bb05..417017e37 100644 --- a/pkg/visor/visor.go +++ b/pkg/visor/visor.go @@ -12,15 +12,13 @@ import ( "syscall" "time" - "github.com/sirupsen/logrus" - - "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" - "github.com/SkycoinProject/dmsg/cipher" "github.com/SkycoinProject/dmsg/dmsgpty" "github.com/SkycoinProject/skycoin/src/util/logging" + "github.com/sirupsen/logrus" "github.com/SkycoinProject/skywire-mainnet/pkg/app/appserver" + "github.com/SkycoinProject/skywire-mainnet/pkg/app/launcher" "github.com/SkycoinProject/skywire-mainnet/pkg/restart" "github.com/SkycoinProject/skywire-mainnet/pkg/router" "github.com/SkycoinProject/skywire-mainnet/pkg/snet" @@ -39,6 +37,12 @@ const ( shortHashLen = 6 ) +const ( + // moduleShutdownTimeout is the timeout given to a module to shutdown cleanly. + // Otherwise the shutdown logic will continue and report a timeout error. + moduleShutdownTimeout = time.Second * 2 +) + // Visor provides messaging runtime for Apps by setting up all // necessary connections and performing messaging gateway functions. type Visor struct { @@ -167,7 +171,7 @@ func (v *Visor) Close() error { start := time.Now() done := make(chan bool, 1) - t := time.NewTimer(time.Second * 2) + t := time.NewTimer(moduleShutdownTimeout) log := v.MasterLogger().PackageLogger(fmt.Sprintf("visor:shutdown:%s", ce.src)). WithField("func", fmt.Sprintf("[%d/%d]", i+1, len(v.closeStack))) @@ -180,12 +184,15 @@ func (v *Visor) Close() error { select { case ok := <-done: + t.Stop() + if !ok { log.WithField("elapsed", time.Since(start)).Warn("Module stopped with unexpected result.") v.processReports(log, nil) continue } log.WithField("elapsed", time.Since(start)).Info("Module stopped cleanly.") + case <-t.C: log.WithField("elapsed", time.Since(start)).Error("Module timed out.") }