summaryrefslogtreecommitdiff
path: root/nodecontrol/NodeController.go
blob: e9ce178b8e19ccd1e3a2128896197f930b612296 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package nodecontrol

import (
	"path/filepath"
	"syscall"
	"time"

	"github.com/algorand/go-algorand/util"
)

// NodeController provides an object for controlling a specific algod node instance
type NodeController struct {
	algod              string
	algoh              string
	algodDataDir       string
	algodPidFile       string
	algodNetFile       string
	algodNetListenFile string

	KMDController
}

// MakeNodeController creates a NodeController representing a
// specific data directory (and an associated binary directory)
func MakeNodeController(binDir, algodDataDir string) NodeController {
	nc := NodeController{
		algod:              filepath.Join(binDir, "algod"),
		algoh:              filepath.Join(binDir, "algoh"),
		algodDataDir:       algodDataDir,
		algodPidFile:       filepath.Join(algodDataDir, "algod.pid"),
		algodNetFile:       filepath.Join(algodDataDir, "algod.net"),
		algodNetListenFile: filepath.Join(algodDataDir, "algod-listen.net"),
	}
	nc.SetKMDBinDir(binDir)
	return nc
}

// AlgodExitErrorCallback is the callback function from the node controller that reports upstream
// in case there was a change with the algod running state.
type AlgodExitErrorCallback func(*NodeController, error)

// AlgodStartArgs are the possible arguments for starting algod
type AlgodStartArgs struct {
	PeerAddress       string
	ListenIP          string
	RedirectOutput    bool
	RunUnderHost      bool
	TelemetryOverride string
	ExitErrorCallback AlgodExitErrorCallback
}

// KMDStartArgs are the possible arguments for starting kmd
type KMDStartArgs struct {
	TimeoutSecs uint64
}

// NodeStartArgs represents the possible arguments for starting the node processes
type NodeStartArgs struct {
	AlgodStartArgs
	KMDStartArgs
}

// FullStart will start the kmd and algod, reporting of either process is already running
func (nc *NodeController) FullStart(args NodeStartArgs) (algodAlreadyRunning, kmdAlreadyRunning bool, err error) {
	// Start algod
	algodAlreadyRunning, err = nc.StartAlgod(args.AlgodStartArgs)
	if err != nil {
		return
	}

	// Start kmd
	kmdAlreadyRunning, err = nc.StartKMD(args.KMDStartArgs)
	if err != nil {
		return
	}

	return
}

// FullStop stops both algod and kmd, if they're running
func (nc NodeController) FullStop() error {
	_, err := nc.stopProcesses()
	return err
}

// stopProcesses attempts to read PID files for algod and kmd and kill the
// corresponding processes. If it can't read a PID file, it doesn't return an
// error, but if it reads a PID file and the process doesn't die, it does.
func (nc NodeController) stopProcesses() (kmdAlreadyStopped bool, err error) {
	errAlgod := nc.StopAlgod()
	kmdAlreadyStopped, err = nc.StopKMD()
	if errAlgod != nil {
		err = errAlgod
	}
	return
}

func killPID(pid int) (killed bool, err error) {
	process, err := util.FindProcess(pid)
	if process == nil || err != nil {
		return false, err
	}

	err = util.KillProcess(pid, syscall.SIGTERM)
	if err != nil {
		return false, err
	}
	waitLong := time.After(time.Second * 30)
	for {
		// Send null signal - if process still exists, it'll return nil
		// So when we get an error, assume it's gone.
		if err = process.Signal(syscall.Signal(0)); err != nil {
			return false, nil
		}
		select {
		case <-waitLong:
			return true, util.KillProcess(pid, syscall.SIGKILL)
		case <-time.After(time.Millisecond * 100):
		}
	}
}