Skip to content

Commit 6bbb7a7

Browse files
committed
Deduplicate arguments building
1 parent 3afbd74 commit 6bbb7a7

File tree

1 file changed

+48
-97
lines changed

1 file changed

+48
-97
lines changed

lib/hypervisor/qemu/process.go

Lines changed: 48 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -105,33 +105,34 @@ func (s *Starter) GetVersion(p *paths.Paths) (string, error) {
105105
return "", fmt.Errorf("could not parse QEMU version from: %s", string(output))
106106
}
107107

108-
// StartVM launches QEMU with the VM configuration and returns a Hypervisor client.
109-
// QEMU receives all configuration via command-line arguments at process start.
110-
func (s *Starter) StartVM(ctx context.Context, p *paths.Paths, version string, socketPath string, config hypervisor.VMConfig) (int, hypervisor.Hypervisor, error) {
108+
// buildQMPArgs returns the base QMP socket arguments for QEMU.
109+
func buildQMPArgs(socketPath string) []string {
110+
return []string{
111+
"-chardev", fmt.Sprintf("socket,id=qmp,path=%s,server=on,wait=off", socketPath),
112+
"-mon", "chardev=qmp,mode=control",
113+
}
114+
}
115+
116+
// startQEMUProcess handles the common QEMU process startup logic.
117+
// Returns the PID, hypervisor client, and a cleanup function.
118+
// The cleanup function must be called on error; call cleanup.Release() on success.
119+
func (s *Starter) startQEMUProcess(ctx context.Context, p *paths.Paths, version string, socketPath string, args []string) (int, *QEMU, *cleanup.Cleanup, error) {
111120
log := logger.FromContext(ctx)
112121

113122
// Get binary path
114123
binaryPath, err := s.GetBinaryPath(p, version)
115124
if err != nil {
116-
return 0, nil, fmt.Errorf("get binary: %w", err)
125+
return 0, nil, nil, fmt.Errorf("get binary: %w", err)
117126
}
118127

119128
// Check if socket is already in use
120129
if isSocketInUse(socketPath) {
121-
return 0, nil, fmt.Errorf("socket already in use, QEMU may be running at %s", socketPath)
130+
return 0, nil, nil, fmt.Errorf("socket already in use, QEMU may be running at %s", socketPath)
122131
}
123132

124133
// Remove stale socket if exists
125134
os.Remove(socketPath)
126135

127-
// Build command arguments: QMP socket + VM configuration
128-
args := []string{
129-
"-chardev", fmt.Sprintf("socket,id=qmp,path=%s,server=on,wait=off", socketPath),
130-
"-mon", "chardev=qmp,mode=control",
131-
}
132-
// Append VM configuration as command-line arguments
133-
args = append(args, BuildArgs(config)...)
134-
135136
// Create command
136137
cmd := exec.Command(binaryPath, args...)
137138

@@ -144,7 +145,7 @@ func (s *Starter) StartVM(ctx context.Context, p *paths.Paths, version string, s
144145
instanceDir := filepath.Dir(socketPath)
145146
logsDir := filepath.Join(instanceDir, "logs")
146147
if err := os.MkdirAll(logsDir, 0755); err != nil {
147-
return 0, nil, fmt.Errorf("create logs directory: %w", err)
148+
return 0, nil, nil, fmt.Errorf("create logs directory: %w", err)
148149
}
149150

150151
vmmLogFile, err := os.OpenFile(
@@ -153,48 +154,71 @@ func (s *Starter) StartVM(ctx context.Context, p *paths.Paths, version string, s
153154
0644,
154155
)
155156
if err != nil {
156-
return 0, nil, fmt.Errorf("create vmm log: %w", err)
157+
return 0, nil, nil, fmt.Errorf("create vmm log: %w", err)
157158
}
158159
defer vmmLogFile.Close()
159160

160161
cmd.Stdout = vmmLogFile
161162
cmd.Stderr = vmmLogFile
162163

164+
processStartTime := time.Now()
163165
if err := cmd.Start(); err != nil {
164-
return 0, nil, fmt.Errorf("start qemu: %w", err)
166+
return 0, nil, nil, fmt.Errorf("start qemu: %w", err)
165167
}
166168

167169
pid := cmd.Process.Pid
170+
log.DebugContext(ctx, "QEMU process started", "pid", pid, "duration_ms", time.Since(processStartTime).Milliseconds())
168171

169172
// Setup cleanup to kill the process if subsequent steps fail
170173
cu := cleanup.Make(func() {
171174
syscall.Kill(pid, syscall.SIGKILL)
172175
})
173-
defer cu.Clean()
174176

175177
// Wait for socket to be ready
178+
socketWaitStart := time.Now()
176179
if err := waitForSocket(socketPath, socketWaitTimeout); err != nil {
180+
cu.Clean()
177181
vmmLogPath := filepath.Join(logsDir, "vmm.log")
178182
if logData, readErr := os.ReadFile(vmmLogPath); readErr == nil && len(logData) > 0 {
179-
return 0, nil, fmt.Errorf("%w; vmm.log: %s", err, string(logData))
183+
return 0, nil, nil, fmt.Errorf("%w; vmm.log: %s", err, string(logData))
180184
}
181-
return 0, nil, err
185+
return 0, nil, nil, err
182186
}
187+
log.DebugContext(ctx, "QMP socket ready", "duration_ms", time.Since(socketWaitStart).Milliseconds())
183188

184189
// Create QMP client
185190
hv, err := New(socketPath)
186191
if err != nil {
187-
return 0, nil, fmt.Errorf("create client: %w", err)
192+
cu.Clean()
193+
return 0, nil, nil, fmt.Errorf("create client: %w", err)
188194
}
189195

196+
return pid, hv, &cu, nil
197+
}
198+
199+
// StartVM launches QEMU with the VM configuration and returns a Hypervisor client.
200+
// QEMU receives all configuration via command-line arguments at process start.
201+
func (s *Starter) StartVM(ctx context.Context, p *paths.Paths, version string, socketPath string, config hypervisor.VMConfig) (int, hypervisor.Hypervisor, error) {
202+
log := logger.FromContext(ctx)
203+
204+
// Build command arguments: QMP socket + VM configuration
205+
args := buildQMPArgs(socketPath)
206+
args = append(args, BuildArgs(config)...)
207+
208+
pid, hv, cu, err := s.startQEMUProcess(ctx, p, version, socketPath, args)
209+
if err != nil {
210+
return 0, nil, err
211+
}
212+
defer cu.Clean()
213+
190214
// Save config for potential restore later
191215
// QEMU migration files only contain memory state, not device config
216+
instanceDir := filepath.Dir(socketPath)
192217
if err := saveVMConfig(instanceDir, config); err != nil {
193218
// Non-fatal - restore just won't work
194219
log.WarnContext(ctx, "failed to save VM config for restore", "error", err)
195220
}
196221

197-
// Success - release cleanup to prevent killing the process
198222
cu.Release()
199223
return pid, hv, nil
200224
}
@@ -205,20 +229,6 @@ func (s *Starter) RestoreVM(ctx context.Context, p *paths.Paths, version string,
205229
log := logger.FromContext(ctx)
206230
startTime := time.Now()
207231

208-
// Get binary path
209-
binaryPath, err := s.GetBinaryPath(p, version)
210-
if err != nil {
211-
return 0, nil, fmt.Errorf("get binary: %w", err)
212-
}
213-
214-
// Check if socket is already in use
215-
if isSocketInUse(socketPath) {
216-
return 0, nil, fmt.Errorf("socket already in use, QEMU may be running at %s", socketPath)
217-
}
218-
219-
// Remove stale socket if exists
220-
os.Remove(socketPath)
221-
222232
// Load saved VM config from snapshot directory
223233
// QEMU requires exact same command-line args as when snapshot was taken
224234
configLoadStart := time.Now()
@@ -228,14 +238,8 @@ func (s *Starter) RestoreVM(ctx context.Context, p *paths.Paths, version string,
228238
}
229239
log.DebugContext(ctx, "loaded VM config from snapshot", "duration_ms", time.Since(configLoadStart).Milliseconds())
230240

231-
instanceDir := filepath.Dir(socketPath)
232-
233241
// Build command arguments: QMP socket + VM configuration + incoming migration
234-
args := []string{
235-
"-chardev", fmt.Sprintf("socket,id=qmp,path=%s,server=on,wait=off", socketPath),
236-
"-mon", "chardev=qmp,mode=control",
237-
}
238-
// Append VM configuration as command-line arguments
242+
args := buildQMPArgs(socketPath)
239243
args = append(args, BuildArgs(config)...)
240244

241245
// Add incoming migration flag to restore from snapshot
@@ -244,63 +248,11 @@ func (s *Starter) RestoreVM(ctx context.Context, p *paths.Paths, version string,
244248
incomingURI := "exec:cat < " + memoryFile
245249
args = append(args, "-incoming", incomingURI)
246250

247-
// Create command
248-
cmd := exec.Command(binaryPath, args...)
249-
250-
// Daemonize: detach from parent process group
251-
cmd.SysProcAttr = &syscall.SysProcAttr{
252-
Setpgid: true,
253-
}
254-
255-
// Redirect stdout/stderr to VMM log file
256-
logsDir := filepath.Join(instanceDir, "logs")
257-
if err := os.MkdirAll(logsDir, 0755); err != nil {
258-
return 0, nil, fmt.Errorf("create logs directory: %w", err)
259-
}
260-
261-
vmmLogFile, err := os.OpenFile(
262-
filepath.Join(logsDir, "vmm.log"),
263-
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
264-
0644,
265-
)
251+
pid, hv, cu, err := s.startQEMUProcess(ctx, p, version, socketPath, args)
266252
if err != nil {
267-
return 0, nil, fmt.Errorf("create vmm log: %w", err)
268-
}
269-
defer vmmLogFile.Close()
270-
271-
cmd.Stdout = vmmLogFile
272-
cmd.Stderr = vmmLogFile
273-
274-
processStartTime := time.Now()
275-
if err := cmd.Start(); err != nil {
276-
return 0, nil, fmt.Errorf("start qemu: %w", err)
277-
}
278-
279-
pid := cmd.Process.Pid
280-
log.DebugContext(ctx, "QEMU process started", "pid", pid, "duration_ms", time.Since(processStartTime).Milliseconds())
281-
282-
// Setup cleanup to kill the process if subsequent steps fail
283-
cu := cleanup.Make(func() {
284-
syscall.Kill(pid, syscall.SIGKILL)
285-
})
286-
defer cu.Clean()
287-
288-
// Wait for socket to be ready
289-
socketWaitStart := time.Now()
290-
if err := waitForSocket(socketPath, 10*time.Second); err != nil {
291-
vmmLogPath := filepath.Join(logsDir, "vmm.log")
292-
if logData, readErr := os.ReadFile(vmmLogPath); readErr == nil && len(logData) > 0 {
293-
return 0, nil, fmt.Errorf("%w; vmm.log: %s", err, string(logData))
294-
}
295253
return 0, nil, err
296254
}
297-
log.DebugContext(ctx, "QMP socket ready", "duration_ms", time.Since(socketWaitStart).Milliseconds())
298-
299-
// Create QMP client
300-
hv, err := New(socketPath)
301-
if err != nil {
302-
return 0, nil, fmt.Errorf("create client: %w", err)
303-
}
255+
defer cu.Clean()
304256

305257
// Wait for incoming migration to complete
306258
// QEMU loads the migration data from the exec subprocess
@@ -311,7 +263,6 @@ func (s *Starter) RestoreVM(ctx context.Context, p *paths.Paths, version string,
311263
}
312264
log.DebugContext(ctx, "migration complete", "duration_ms", time.Since(migrationWaitStart).Milliseconds())
313265

314-
// Success - release cleanup to prevent killing the process
315266
cu.Release()
316267
log.DebugContext(ctx, "QEMU restore complete", "pid", pid, "total_duration_ms", time.Since(startTime).Milliseconds())
317268
return pid, hv, nil

0 commit comments

Comments
 (0)