internal/server: fire instance-agent events when agent status changes

Signed-off-by: 0xk1f0 <dev@k1f0.dev>
This commit is contained in:
0xk1f0 2026-03-12 15:59:20 +01:00
parent d7eacd827e
commit 8d100d3093
3 changed files with 36 additions and 16 deletions

View File

@ -483,7 +483,7 @@ func (d *qemu) getMonitorEventHandler() func(event string, data map[string]any)
state := d.state
return func(event string, data map[string]any) {
if !slices.Contains([]string{qmp.EventVMShutdown, qmp.EventVMReset, qmp.EventAgentStarted, qmp.EventRTCChange}, event) {
if !slices.Contains([]string{qmp.EventVMShutdown, qmp.EventVMReset, qmp.EventAgentStarted, qmp.EventAgentStopped, qmp.EventRTCChange}, event) {
return // Don't bother loading the instance from DB if we aren't going to handle the event.
}
@ -518,6 +518,12 @@ func (d *qemu) getMonitorEventHandler() func(event string, data map[string]any)
return
}
state.Events.SendLifecycle(instProject.Name, lifecycle.InstanceAgentStarted.Event(d, nil))
case qmp.EventAgentStopped:
d.logger.Debug("Instance agent stopped")
state.Events.SendLifecycle(instProject.Name, lifecycle.InstanceAgentStopped.Event(d, nil))
case qmp.EventVMReset:
monitor, err := d.qmpConnect()
if err == nil {
@ -792,6 +798,8 @@ func (d *qemu) onStop(target string) error {
} else {
d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceStopped.Event(d, nil))
}
// agent stopped when shutdown
d.state.Events.SendLifecycle(d.project.Name, lifecycle.InstanceAgentStopped.Event(d, nil))
}
// Reboot the instance.

View File

@ -27,6 +27,9 @@ var RingbufSize = 16
// EventAgentStarted is the event sent once the agent has started.
var EventAgentStarted = "AGENT-STARTED"
// EventAgentStopped is the event sent once the agent has stopped.
var EventAgentStopped = "AGENT-STOPPED"
// EventVMReset is the event sent when VM guest reboots.
var EventVMReset = "RESET"
@ -84,21 +87,7 @@ func (m *Monitor) start() error {
// Extract the last entry.
entries := strings.Split(resp.Return, "\n")
if len(entries) > 1 {
status := entries[len(entries)-2]
m.agentStartedMu.Lock()
switch status {
case "STARTED":
if !m.agentStarted && m.eventHandler != nil {
go m.eventHandler(EventAgentStarted, nil)
}
m.agentStarted = true
case "STOPPED":
m.agentStarted = false
}
m.agentStartedMu.Unlock()
m.processAgentStatus(entries[len(entries)-2])
}
}
@ -181,6 +170,27 @@ func (m *Monitor) start() error {
return nil
}
// processAgentStatus handles a status string read from the agent vserial ringbuffer.
func (m *Monitor) processAgentStatus(status string) {
m.agentStartedMu.Lock()
defer m.agentStartedMu.Unlock()
switch status {
case "STARTED":
if !m.agentStarted && m.eventHandler != nil {
go m.eventHandler(EventAgentStarted, nil)
}
m.agentStarted = true
case "STOPPED":
if m.agentStarted && m.eventHandler != nil {
go m.eventHandler(EventAgentStopped, nil)
}
m.agentStarted = false
}
}
// ping is used to validate if the QMP socket is still active.
func (m *Monitor) ping() error {
// Check if disconnected

View File

@ -18,6 +18,8 @@ type InstanceAction string
// All supported lifecycle events for instances.
const (
InstanceAgentStarted = InstanceAction(api.EventLifecycleInstanceAgentStarted)
InstanceAgentStopped = InstanceAction(api.EventLifecycleInstanceAgentStopped)
InstanceConsole = InstanceAction(api.EventLifecycleInstanceConsole)
InstanceConsoleReset = InstanceAction(api.EventLifecycleInstanceConsoleReset)
InstanceConsoleRetrieved = InstanceAction(api.EventLifecycleInstanceConsoleRetrieved)