@@ -20,7 +20,6 @@ import (
20
20
"fmt"
21
21
"os"
22
22
"path"
23
- "regexp"
24
23
"runtime/debug"
25
24
"strings"
26
25
"time"
@@ -36,91 +35,102 @@ import (
36
35
)
37
36
38
37
const (
39
- KICKSTART_MONITOR_TIMEOUT = 30 * time .Minute
40
- SERIAL_DEVICE_CHECK_INTERVAL = 5 * time .Second
41
- SERIAL_DEVICE_CHECK_MAX_ATTEMPTS = 10
38
+ KICKSTART_MONITOR_TIMEOUT = 30 * time .Minute
39
+ SERIAL_FILE_CHECK_INTERVAL = 5 * time .Second
40
+ KICKSTART_SERIAL_FILE_PREFIX = "/tmp/kickstart-serial"
42
41
)
43
42
44
43
type SKickstartSerialMonitor struct {
45
- serverId string
46
- logFilePath string
47
- serialDevice string
48
-
49
- scanner * bufio.Scanner
50
- file * os.File
44
+ serverId string
45
+ serialFilePath string
51
46
52
47
ctx context.Context
53
48
cancel context.CancelFunc
54
49
}
55
50
56
51
// NewKickstartSerialMonitor creates a new kickstart serial monitor
57
- func NewKickstartSerialMonitor (serverId , logFilePath string ) * SKickstartSerialMonitor {
52
+ func NewKickstartSerialMonitor (serverId string ) * SKickstartSerialMonitor {
58
53
ctx , cancel := context .WithTimeout (context .Background (), KICKSTART_MONITOR_TIMEOUT )
54
+ serialFilePath := fmt .Sprintf ("%s-%s.log" , KICKSTART_SERIAL_FILE_PREFIX , serverId )
59
55
60
56
return & SKickstartSerialMonitor {
61
- serverId : serverId ,
62
- logFilePath : logFilePath ,
63
- ctx : ctx ,
64
- cancel : cancel ,
57
+ serverId : serverId ,
58
+ serialFilePath : serialFilePath ,
59
+ ctx : ctx ,
60
+ cancel : cancel ,
65
61
}
66
62
}
67
63
68
- // waitForSerialDevice waits for the serial device to become available
69
- func ( m * SKickstartSerialMonitor ) waitForSerialDevice () ( string , error ) {
70
- pattern := regexp . MustCompile ( `char device redirected to (/dev/pts/\d+) \(label charserial0\)` )
64
+ func ( m * SKickstartSerialMonitor ) GetSerialFilePath () string {
65
+ return m . serialFilePath
66
+ }
71
67
72
- for attempts := 0 ; attempts < SERIAL_DEVICE_CHECK_MAX_ATTEMPTS ; attempts ++ {
73
- select {
74
- case <- m . ctx . Done ():
75
- return "" , errors . Errorf ( "context cancelled while waiting for serial device" )
76
- default :
68
+ func ( m * SKickstartSerialMonitor ) ensureSerialFile () error {
69
+ if _ , err := os . Stat ( m . serialFilePath ); os . IsNotExist ( err ) {
70
+ file , err := os . Create ( m . serialFilePath )
71
+ if err != nil {
72
+ return errors . Wrapf ( err , "create serial file %s" , m . serialFilePath )
77
73
}
74
+ file .Close ()
75
+ log .Infof ("Created kickstart serial file %s for server %s" , m .serialFilePath , m .serverId )
76
+ }
77
+ return nil
78
+ }
78
79
79
- if content , err := os .ReadFile (m .logFilePath ); err == nil {
80
- if matches := pattern .FindStringSubmatch (string (content )); len (matches ) >= 2 {
81
- devicePath := matches [1 ]
82
- if _ , err := os .Stat (devicePath ); err == nil {
83
- return devicePath , nil
84
- }
85
- }
80
+ func (m * SKickstartSerialMonitor ) monitorSerialFile () {
81
+ defer func () {
82
+ if r := recover (); r != nil {
83
+ log .Errorf ("KickstartSerialMonitor monitor %v %s" , r , debug .Stack ())
86
84
}
85
+ }()
87
86
88
- time .Sleep (SERIAL_DEVICE_CHECK_INTERVAL )
89
- }
87
+ var lastSize int64 = 0
90
88
91
- return "" , errors .Errorf ("serial device not available after %d attempts" , SERIAL_DEVICE_CHECK_MAX_ATTEMPTS )
89
+ ticker := time .NewTicker (SERIAL_FILE_CHECK_INTERVAL )
90
+ defer ticker .Stop ()
91
+
92
+ for {
93
+ select {
94
+ case <- m .ctx .Done ():
95
+ return
96
+ case <- ticker .C :
97
+ if err := m .scanSerialForStatus (& lastSize ); err != nil {
98
+ log .Errorf ("Failed to scan serial file for status for server %s: %v" , m .serverId , err )
99
+ }
100
+ }
101
+ }
92
102
}
93
103
94
- // connect establishes connection to the serial device
95
- func (m * SKickstartSerialMonitor ) connect () error {
96
- devicePath , err := m .waitForSerialDevice ()
104
+ // scanSerialForStatus scans the serial file for a kickstart status update.
105
+ // It reads new content since the last check, parses it for status keywords,
106
+ // and triggers status updates and cleanup when a final status is detected.
107
+ func (m * SKickstartSerialMonitor ) scanSerialForStatus (lastSize * int64 ) error {
108
+ fileInfo , err := os .Stat (m .serialFilePath )
109
+ if os .IsNotExist (err ) {
110
+ return nil
111
+ }
97
112
if err != nil {
98
113
return err
99
114
}
100
115
101
- file , err := os . Open ( devicePath )
102
- if err != nil {
103
- return errors . Wrapf ( err , "open serial device %s" , devicePath )
116
+ currentSize := fileInfo . Size ( )
117
+ if currentSize <= * lastSize {
118
+ return nil
104
119
}
105
120
106
- m .file = file
107
- m .serialDevice = devicePath
108
- m .scanner = bufio .NewScanner (file )
109
-
110
- log .Infof ("Kickstart monitor connected to serial device %s for server %s" , devicePath , m .serverId )
111
- return nil
112
- }
121
+ // Read new content
122
+ file , err := os .Open (m .serialFilePath )
123
+ if err != nil {
124
+ return err
125
+ }
126
+ defer file .Close ()
113
127
114
- // read reads messages from the serial device
115
- func (m * SKickstartSerialMonitor ) read () {
116
- defer func () {
117
- if r := recover (); r != nil {
118
- log .Errorf ("KickstartSerialMonitor read %v %s" , r , debug .Stack ())
119
- }
120
- }()
128
+ _ , err = file .Seek (* lastSize , 0 )
129
+ if err != nil {
130
+ return err
131
+ }
121
132
122
- // process messages by line
123
- scanner := m .scanner
133
+ scanner := bufio .NewScanner (file )
124
134
for scanner .Scan () {
125
135
line := strings .TrimSpace (scanner .Text ())
126
136
if len (line ) == 0 {
@@ -151,29 +161,29 @@ func (m *SKickstartSerialMonitor) read() {
151
161
} else {
152
162
log .Infof ("Kickstart status for server %s updated to %s" , m .serverId , status )
153
163
m .Close ()
164
+ return nil
154
165
}
155
166
}
156
167
}
157
168
158
- if err := scanner .Err (); err != nil {
159
- log .Debugf ("Kickstart serial monitor disconnected %s: %s" , m .serverId , err )
160
- }
169
+ * lastSize = currentSize
170
+ return scanner .Err ()
161
171
}
162
172
163
- // Close closes the serial monitor connection
164
173
func (m * SKickstartSerialMonitor ) Close () error {
165
174
if m .cancel != nil {
166
175
m .cancel ()
167
176
}
168
177
169
- if m .file != nil {
170
- err := m . file . Close ()
171
- m . file = nil
172
- m . scanner = nil
173
- log .Infof ( "Kickstart monitor closed for server %s" , m .serverId )
174
- return err
178
+ if m .serialFilePath != "" {
179
+ if err := os . Remove ( m . serialFilePath ); err != nil && ! os . IsNotExist ( err ) {
180
+ log . Warningf ( "Failed to remove serial file %s: %v" , m . serialFilePath , err )
181
+ } else {
182
+ log .Debugf ( "Removed serial file %s" , m .serialFilePath )
183
+ }
175
184
}
176
185
186
+ log .Infof ("Kickstart monitor closed for server %s" , m .serverId )
177
187
return nil
178
188
}
179
189
@@ -199,13 +209,13 @@ func (m *SKickstartSerialMonitor) updateKickstartStatus(status string) error {
199
209
200
210
// Start starts the kickstart serial monitor
201
211
func (m * SKickstartSerialMonitor ) Start () error {
202
- log .Infof ("Starting kickstart monitor for server %s" , m .serverId )
212
+ log .Infof ("Starting kickstart monitor for server %s, serial file: %s " , m .serverId , m . serialFilePath )
203
213
204
- if err := m .connect (); err != nil {
205
- return errors .Wrap (err , "connect to serial device " )
214
+ if err := m .ensureSerialFile (); err != nil {
215
+ return errors .Wrap (err , "ensure serial file " )
206
216
}
207
217
208
- go m .read ()
218
+ go m .monitorSerialFile ()
209
219
210
220
// Setup timeout handler
211
221
go func () {
0 commit comments