diff --git a/app/sidero-controller-manager/internal/ipxe/ipxe_server.go b/app/sidero-controller-manager/internal/ipxe/ipxe_server.go index 381b361e6..5031374e2 100644 --- a/app/sidero-controller-manager/internal/ipxe/ipxe_server.go +++ b/app/sidero-controller-manager/internal/ipxe/ipxe_server.go @@ -40,11 +40,57 @@ const bootFile = `#!ipxe chain ipxe?uuid=${uuid}&mac=${mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial}&arch=${buildarch} ` -// bootTemplate is embedded into iPXE binary when that binary is sent to the node. -// -// bootTemplate should be kept in sync with the bootFile above. -var bootTemplate = template.Must(template.New("iPXE embedded").Parse(`dhcp -chain http://{{ .Endpoint }}:{{ .Port }}/ipxe?uuid=${uuid}&mac=${mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial}&arch=${buildarch} +// BootTemplate is embedded into iPXE binary when that binary is sent to the node. +var BootTemplate = template.Must(template.New("iPXE embedded").Parse(` +prompt --key 0x02 --timeout 2000 Press Ctrl-B for the iPXE command line... && shell || + +# print interfaces +ifstat + +# retry 10 times overall +set attempts:int32 10 +set x:int32 0 + +:retry_loop + + set idx:int32 0 + + :loop + # try DHCP on each available interface + isset ${net${idx}/mac} || goto exhausted + + ifclose + dhcp net${idx} && goto boot + + :next_iface + inc idx && goto loop + + :boot + # attempt boot, if fails try next iface + route + + chain http://{{ .Endpoint }}:{{ .Port }}/ipxe?uuid=${uuid}&mac=${net${idx}/mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial}&arch=${buildarch} || goto next_iface + +:exhausted + echo + echo Failed to iPXE boot successfully via all interfaces + + iseq ${x} ${attempts} && goto fail || + + echo Retrying... + echo + + inc x + goto retry_loop + +:fail + echo + echo Failed to get a valid response after ${attempts} attempts + echo + + echo Rebooting in 5 seconds... + sleep 5 + reboot `)) // ipxeTemplate is returned as response to `chain` request from the bootFile/bootTemplate to boot actual OS (or Sidero agent). @@ -194,7 +240,7 @@ func RegisterIPXE(mux *http.ServeMux, endpoint string, port int, args string, bo var embeddedScriptBuf bytes.Buffer - if err := bootTemplate.Execute(&embeddedScriptBuf, map[string]string{ + if err := BootTemplate.Execute(&embeddedScriptBuf, map[string]string{ "Endpoint": apiEndpoint, "Port": strconv.Itoa(iPXEPort), }); err != nil { diff --git a/app/sidero-controller-manager/internal/ipxe/ipxe_test.go b/app/sidero-controller-manager/internal/ipxe/ipxe_test.go index 22a634ed3..d78b074b6 100644 --- a/app/sidero-controller-manager/internal/ipxe/ipxe_test.go +++ b/app/sidero-controller-manager/internal/ipxe/ipxe_test.go @@ -4,11 +4,26 @@ package ipxe_test -import "testing" +import ( + "bytes" + "testing" -func TestEmpty(t *testing.T) { - // added for accurate coverage estimation - // - // please remove it once any unit-test is added - // for this package + "github.com/stretchr/testify/assert" + + "github.com/talos-systems/sidero/app/sidero-controller-manager/internal/ipxe" +) + +func TestEmbeddedLength(t *testing.T) { + var buf bytes.Buffer + + assert.NoError(t, ipxe.BootTemplate.Execute(&buf, struct { + Endpoint string + Port string + }{ // use bigger values here to get maximum length of the script + Endpoint: "[2001:470:6d:30e:e5b8:903e:3701:7332]", + Port: "12345", + })) + + // iPXE script should fit length of the reserved space in the iPXE binary + assert.Less(t, buf.Len(), 1000) }