20
"github.com/google/uuid"
21
"github.com/hashicorp/go-multierror"
22
"github.com/siderolabs/gen/xslices"
23
"github.com/siderolabs/go-procfs/procfs"
25
"github.com/siderolabs/talos/pkg/machinery/constants"
26
"github.com/siderolabs/talos/pkg/machinery/kernel"
27
"github.com/siderolabs/talos/pkg/provision"
28
"github.com/siderolabs/talos/pkg/provision/providers/vm"
32
func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRequest, nodeReq provision.NodeRequest, opts *provision.Options) (provision.NodeInfo, error) {
33
arch := Arch(opts.TargetArch)
34
pidPath := state.GetRelativePath(fmt.Sprintf("%s.pid", nodeReq.Name))
36
var pflashImages []string
38
if pflashSpec := arch.PFlash(opts.UEFIEnabled, opts.ExtraUEFISearchPaths); pflashSpec != nil {
41
if pflashImages, err = p.createPFlashImages(state, nodeReq.Name, pflashSpec); err != nil {
42
return provision.NodeInfo{}, fmt.Errorf("error creating flash images: %w", err)
46
vcpuCount := int64(math.RoundToEven(float64(nodeReq.NanoCPUs) / 1000 / 1000 / 1000))
51
memSize := nodeReq.Memory / 1024 / 1024
53
diskPaths, err := p.CreateDisks(state, nodeReq)
55
return provision.NodeInfo{}, err
58
err = p.populateSystemDisk(diskPaths, clusterReq)
60
return provision.NodeInfo{}, err
63
logFile, err := os.OpenFile(state.GetRelativePath(fmt.Sprintf("%s.log", nodeReq.Name)), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o666)
65
return provision.NodeInfo{}, err
70
cmdline := procfs.NewCmdline("")
72
cmdline.SetAll(kernel.DefaultArgs)
75
cmdline.Append("console", arch.Console())
78
cmdline.Append("reboot", "k")
79
cmdline.Append("panic", "1")
80
cmdline.Append("talos.shutdown", "halt")
83
cmdline.Append("talos.platform", constants.PlatformMetal)
86
if nodeReq.ExtraKernelArgs != nil {
87
if err = cmdline.AppendAll(nodeReq.ExtraKernelArgs.Strings()); err != nil {
88
return provision.NodeInfo{}, err
94
if !nodeReq.SkipInjectingConfig {
95
cmdline.Append("talos.config", "{TALOS_CONFIG_URL}")
97
nodeConfig, err = nodeReq.Config.EncodeString()
99
return provision.NodeInfo{}, err
103
nodeUUID := uuid.New()
104
if nodeReq.UUID != nil {
105
nodeUUID = *nodeReq.UUID
108
apiPort, err := p.findBridgeListenPort(clusterReq)
110
return provision.NodeInfo{}, fmt.Errorf("error finding listen address for the API: %w", err)
113
defaultBootOrder := "cn"
114
if nodeReq.DefaultBootOrder != "" {
115
defaultBootOrder = nodeReq.DefaultBootOrder
119
for i := range nodeReq.Disks {
120
if nodeReq.Disks[i].Driver != "" {
125
nodeReq.Disks[i].Driver = "virtio"
127
nodeReq.Disks[i].Driver = "ide"
131
launchConfig := LaunchConfig{
132
QemuExecutable: arch.QemuExecutable(),
133
DiskPaths: diskPaths,
134
DiskDrivers: xslices.Map(nodeReq.Disks, func(disk *provision.Disk) string {
137
VCPUCount: vcpuCount,
139
KernelArgs: cmdline.String(),
140
MachineType: arch.QemuMachine(),
141
PFlashImages: pflashImages,
142
MonitorPath: state.GetRelativePath(fmt.Sprintf("%s.monitor", nodeReq.Name)),
143
EnableKVM: opts.TargetArch == runtime.GOARCH,
144
BadRTC: nodeReq.BadRTC,
145
DefaultBootOrder: defaultBootOrder,
146
BootloaderEnabled: opts.BootloaderEnabled,
149
BridgeName: state.BridgeName,
150
NetworkConfig: state.VMCNIConfig,
151
CNI: clusterReq.Network.CNI,
152
CIDRs: clusterReq.Network.CIDRs,
153
NoMasqueradeCIDRs: clusterReq.Network.NoMasqueradeCIDRs,
155
GatewayAddrs: clusterReq.Network.GatewayAddrs,
156
MTU: clusterReq.Network.MTU,
157
Nameservers: clusterReq.Network.Nameservers,
158
TFTPServer: nodeReq.TFTPServer,
159
IPXEBootFileName: nodeReq.IPXEBootFilename,
163
if clusterReq.IPXEBootScript != "" {
164
launchConfig.TFTPServer = clusterReq.Network.GatewayAddrs[0].String()
165
launchConfig.IPXEBootFileName = fmt.Sprintf("ipxe/%s/snp.efi", string(arch))
168
nodeInfo := provision.NodeInfo{
174
NanoCPUs: nodeReq.NanoCPUs,
175
Memory: nodeReq.Memory,
176
DiskSize: nodeReq.Disks[0].Size,
183
if opts.TPM2Enabled {
184
tpm2, tpm2Err := p.createVirtualTPM2State(state, nodeReq.Name)
186
return provision.NodeInfo{}, tpm2Err
189
launchConfig.TPM2Config = tpm2
190
nodeInfo.TPM2StateDir = tpm2.StateDir
193
if !clusterReq.Network.DHCPSkipHostname {
194
launchConfig.Hostname = nodeReq.Name
197
if !(nodeReq.PXEBooted || launchConfig.IPXEBootFileName != "") {
198
launchConfig.KernelImagePath = strings.ReplaceAll(clusterReq.KernelPath, constants.ArchVariable, opts.TargetArch)
199
launchConfig.InitrdPath = strings.ReplaceAll(clusterReq.InitramfsPath, constants.ArchVariable, opts.TargetArch)
200
launchConfig.ISOPath = strings.ReplaceAll(clusterReq.ISOPath, constants.ArchVariable, opts.TargetArch)
203
launchConfig.StatePath, err = state.StatePath()
205
return provision.NodeInfo{}, err
208
launchConfigFile, err := os.Create(state.GetRelativePath(fmt.Sprintf("%s.config", nodeReq.Name)))
210
return provision.NodeInfo{}, err
213
if err = json.NewEncoder(launchConfigFile).Encode(&launchConfig); err != nil {
214
return provision.NodeInfo{}, err
217
if _, err = launchConfigFile.Seek(0, io.SeekStart); err != nil {
218
return provision.NodeInfo{}, err
221
defer launchConfigFile.Close()
223
cmd := exec.Command(clusterReq.SelfExecutable, "qemu-launch")
226
cmd.Stdin = launchConfigFile
227
cmd.SysProcAttr = &syscall.SysProcAttr{
231
if err = cmd.Start(); err != nil {
232
return provision.NodeInfo{}, err
235
if err = os.WriteFile(pidPath, []byte(strconv.Itoa(cmd.Process.Pid)), os.ModePerm); err != nil {
236
return provision.NodeInfo{}, fmt.Errorf("error writing PID file: %w", err)
244
func (p *provisioner) createNodes(state *vm.State, clusterReq provision.ClusterRequest, nodeReqs []provision.NodeRequest, opts *provision.Options) ([]provision.NodeInfo, error) {
245
errCh := make(chan error)
246
nodeCh := make(chan provision.NodeInfo, len(nodeReqs))
248
for _, nodeReq := range nodeReqs {
249
go func(nodeReq provision.NodeRequest) {
250
nodeInfo, err := p.createNode(state, clusterReq, nodeReq, opts)
259
var multiErr *multierror.Error
262
multiErr = multierror.Append(multiErr, <-errCh)
267
nodesInfo := make([]provision.NodeInfo, 0, len(nodeReqs))
269
for nodeInfo := range nodeCh {
270
nodesInfo = append(nodesInfo, nodeInfo)
273
return nodesInfo, multiErr.ErrorOrNil()
276
func (p *provisioner) findBridgeListenPort(clusterReq provision.ClusterRequest) (int, error) {
277
l, err := net.Listen("tcp", net.JoinHostPort(clusterReq.Network.GatewayAddrs[0].String(), "0"))
282
port := l.Addr().(*net.TCPAddr).Port
284
return port, l.Close()
287
func (p *provisioner) populateSystemDisk(disks []string, clusterReq provision.ClusterRequest) error {
288
if len(disks) > 0 && clusterReq.DiskImagePath != "" {
289
disk, err := os.OpenFile(disks[0], os.O_RDWR, 0o755)
295
image, err := os.Open(clusterReq.DiskImagePath)
301
_, err = io.Copy(disk, image)