11package bmc
22
33import (
4+ "context"
45 "fmt"
6+ "io"
57 "log/slog"
68 "strconv"
79 "strings"
8- "time"
910
11+ apiclient "github.com/metal-stack/api/go/client"
12+ apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
13+ infrav2 "github.com/metal-stack/api/go/metalstack/infra/v2"
1014 "github.com/metal-stack/go-hal"
1115 "github.com/metal-stack/go-hal/connect"
1216 halslog "github.com/metal-stack/go-hal/pkg/logger/slog"
1317 "github.com/metal-stack/metal-bmc/pkg/config"
1418)
1519
1620type BMCService struct {
17- log * slog.Logger
18- // NSQ related config options
19- mqAddress string
20- mqCACertFile string
21- mqClientCertFile string
22- mqClientCertKeyFile string
23- mqLogLevel string
24- machineTopic string
25- machineTopicTTL time.Duration
21+ log * slog.Logger
22+ cfg * config.Config
23+ client apiclient.Client
2624}
2725
28- func New (log * slog.Logger , c * config.Config ) * BMCService {
26+ func New (log * slog.Logger , client apiclient. Client , c * config.Config ) * BMCService {
2927 b := & BMCService {
30- log : log ,
31- mqAddress : c .MQAddress ,
32- mqCACertFile : c .MQCACertFile ,
33- mqClientCertFile : c .MQClientCertFile ,
34- mqClientCertKeyFile : c .MQClientCertKeyFile ,
35- mqLogLevel : c .MQLogLevel ,
36- machineTopic : c .MachineTopic ,
37- machineTopicTTL : c .MachineTopicTTL ,
28+ log : log ,
29+ cfg : c ,
30+ client : client ,
3831 }
3932 return b
4033}
4134
42- type MachineEvent struct {
43- Type EventType `json:"type,omitempty"`
44- OldMachineID string `json:"old,omitempty"`
45- Cmd * MachineExecCommand `json:"cmd,omitempty"`
35+ func (b * BMCService ) ProcessCommands () {
36+ b .log .Info ("processCommand, start waiting for bmc commands" )
37+ go func () {
38+ messageChan , errChan := b .subscribeAsync (context .Background (), b .cfg .PartitionID )
39+ select {
40+ case message := <- messageChan :
41+ err := b .handleMessage (message )
42+ if err != nil {
43+ b .log .Error ("processCommand" , "error" , err )
44+ }
45+ case err := <- errChan :
46+ b .log .Error ("processCommand" , "error" , err )
47+ }
48+ }()
4649}
4750
48- type MachineExecCommand struct {
49- TargetMachineID string `json:"target,omitempty"`
50- Command MachineCommand `json:"cmd,omitempty"`
51- IPMI * IPMI `json:"ipmi,omitempty"`
52- FirmwareUpdate * FirmwareUpdate `json:"firmwareupdate,omitempty"`
53- }
51+ func (b * BMCService ) handleMessage (message * infrav2.WaitForBMCCommandResponse ) error {
52+ b .log .Info ("handlemessage" , "message" , message )
5453
55- type IPMI struct {
56- // Address is host:port of the connection to the ipmi BMC, host can be either a ip address or a hostname
57- Address string `json:"address"`
58- User string `json:"user"`
59- Password string `json:"password"`
60- Fru Fru `json:"fru"`
61- }
54+ if message .MachineBmc == nil {
55+ return fmt .Errorf ("event does not contain bmc details:%v" , message )
56+ }
57+ outBand , err := b .outBand (message .MachineBmc )
58+ if err != nil {
59+ b .log .Error ("error creating outband connection" , "error" , err )
60+ return err
61+ }
6262
63- type FirmwareUpdate struct {
64- Kind string `json:"kind"`
65- URL string `json:"url"`
63+ switch message .BmcCommand {
64+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_MACHINE_DELETED :
65+ err := outBand .BootFrom (hal .BootTargetPXE )
66+ if err != nil {
67+ return err
68+ }
69+ return outBand .PowerReset ()
70+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_ON :
71+ return outBand .PowerOn ()
72+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_OFF :
73+ return outBand .PowerOff ()
74+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_RESET :
75+ return outBand .PowerReset ()
76+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_CYCLE :
77+ return outBand .PowerCycle ()
78+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_TO_BIOS :
79+ return outBand .BootFrom (hal .BootTargetBIOS )
80+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_FROM_DISK :
81+ return outBand .BootFrom (hal .BootTargetDisk )
82+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_BOOT_FROM_PXE :
83+ return outBand .BootFrom (hal .BootTargetPXE )
84+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_IDENTIFY_LED_ON :
85+ return outBand .IdentifyLEDOn ()
86+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_IDENTIFY_LED_OFF :
87+ return outBand .IdentifyLEDOff ()
88+ case apiv2 .MachineBMCCommand_MACHINE_BMC_COMMAND_MACHINE_CREATED :
89+ return outBand .BootFrom (hal .BootTargetDisk )
90+ default :
91+ b .log .Warn ("unhandled command" , "command" , message .BmcCommand .String ())
92+ }
93+ return nil
6694}
6795
68- type Fru struct {
69- BoardPartNumber string `json:"board_part_number"`
96+ // messageHandler is called when a message is received
97+ type messageHandler func (* infrav2.WaitForBMCCommandResponse ) error
98+
99+ // Subscribe subscribes to a topic and calls the handler for each message
100+ func (c * BMCService ) subscribe (ctx context.Context , topic string , handler messageHandler ) error {
101+ stream , err := c .client .Infrav2 ().BMC ().WaitForBMCCommand (ctx , & infrav2.WaitForBMCCommandRequest {Partition : topic })
102+ if err != nil {
103+ return fmt .Errorf ("failed to subscribe: %w" , err )
104+ }
105+ defer func () {
106+ _ = stream .Close ()
107+ }()
108+
109+ c .log .Info ("subscribed to machine bmc command" , "topic" , topic )
110+
111+ // Receive messages
112+ for stream .Receive () {
113+ msg := stream .Msg ()
114+ if err := handler (msg ); err != nil {
115+ c .log .Error ("handler error" , "error" , err )
116+ }
117+ }
118+
119+ if err := stream .Err (); err != nil {
120+ if err == io .EOF || err == context .Canceled {
121+ return nil
122+ }
123+ return fmt .Errorf ("stream error: %w" , err )
124+ }
125+
126+ return nil
70127}
71128
72- type MachineCommand string
73-
74- // FIXME these constants must move to a single location
75- const (
76- MachineOnCmd MachineCommand = "ON"
77- MachineOffCmd MachineCommand = "OFF"
78- MachineResetCmd MachineCommand = "RESET"
79- MachineCycleCmd MachineCommand = "CYCLE"
80- MachineBiosCmd MachineCommand = "BIOS"
81- MachineDiskCmd MachineCommand = "DISK"
82- MachinePxeCmd MachineCommand = "PXE"
83- MachineReinstallCmd MachineCommand = "REINSTALL"
84- ChassisIdentifyLEDOnCmd MachineCommand = "LED-ON"
85- ChassisIdentifyLEDOffCmd MachineCommand = "LED-OFF"
86- UpdateFirmwareCmd MachineCommand = "UPDATE-FIRMWARE"
87- )
129+ // SubscribeAsync subscribes asynchronously and returns a channel of messages
130+ func (c * BMCService ) subscribeAsync (ctx context.Context , topic string ) (<- chan * infrav2.WaitForBMCCommandResponse , <- chan error ) {
131+ var (
132+ msgChan = make (chan * infrav2.WaitForBMCCommandResponse , 100 )
133+ errChan = make (chan error , 1 )
134+ )
88135
89- type EventType string
136+ go func () {
137+ defer close (msgChan )
138+ defer close (errChan )
90139
91- // FIXME these constants must move to a single location
92- const (
93- Create EventType = "create"
94- Update EventType = "update"
95- Delete EventType = "delete"
96- Command EventType = "command"
97- )
140+ err := c .subscribe (ctx , topic , func (msg * infrav2.WaitForBMCCommandResponse ) error {
141+ select {
142+ case msgChan <- msg :
143+ return nil
144+ case <- ctx .Done ():
145+ return ctx .Err ()
146+ }
147+ })
148+
149+ if err != nil && err != context .Canceled {
150+ errChan <- err
151+ }
152+ }()
153+
154+ return msgChan , errChan
155+ }
98156
99- func (b * BMCService ) outBand (ipmi * IPMI ) (hal.OutBand , error ) {
100- host , portString , found := strings .Cut (ipmi .Address , ":" )
157+ func (b * BMCService ) outBand (bmc * apiv2. MachineBMC ) (hal.OutBand , error ) {
158+ host , portString , found := strings .Cut (bmc .Address , ":" )
101159 if ! found {
102160 portString = "623"
103161
@@ -106,7 +164,7 @@ func (b *BMCService) outBand(ipmi *IPMI) (hal.OutBand, error) {
106164 if err != nil {
107165 return nil , fmt .Errorf ("unable to convert port to an int %w" , err )
108166 }
109- outBand , err := connect .OutBand (host , port , ipmi .User , ipmi .Password , halslog .New (b .log ))
167+ outBand , err := connect .OutBand (host , port , bmc .User , bmc .Password , halslog .New (b .log ))
110168 if err != nil {
111169 return nil , err
112170 }
0 commit comments