Skip to content

Commit 5551610

Browse files
committed
chore: make qemu provider build on darwin
This is the first step in splitting up the darwin qemu provider feature into smaller, easier to review bits. Make the minimal changes required to have the qemu provider build on darwin. The following changes will come in the "first crash" order. Currently the next step that needs fixing is the preflight checks. After that come the various networking bits. * use a newer commit of the dhcpd fork that has the darwin compatible logic (see insomniacslk/dhcp#550) * make sure that the (broken) darwin qemu logic only runs when `DARWIN_QEMU` env var is set * avoid using the linux specific cni.ns package (this can be changed later, just has to be done for now so the thing compiles) * use a cross-platform fallocate package Signed-off-by: Orzelius <33936483+Orzelius@users.noreply.github.com>
1 parent 1e67758 commit 5551610

File tree

12 files changed

+292
-208
lines changed

12 files changed

+292
-208
lines changed

cmd/talosctl/cmd/mgmt/dhcpd_launch_linux.go renamed to cmd/talosctl/cmd/mgmt/dhcpd_launch.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5+
//go:build linux || darwin
6+
57
package mgmt
68

79
import (

cmd/talosctl/cmd/mgmt/qemu_launch_linux.go renamed to cmd/talosctl/cmd/mgmt/qemu_launch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5-
//go:build linux
5+
//go:build linux || darwin
66

77
package mgmt
88

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ replace (
3131

3232
// fd-leak related replacements: https://github.com/siderolabs/talos/issues/9412
3333
// https://github.com/insomniacslk/dhcp/pull/550
34-
replace github.com/insomniacslk/dhcp => github.com/smira/dhcp v0.0.0-20241001122726-31e9ef21c016
34+
replace github.com/insomniacslk/dhcp => github.com/smira/dhcp v0.0.0-20250407153013-99942baa5d59
3535

3636
// Kubernetes dependencies sharing the same version.
3737
require (
@@ -74,6 +74,7 @@ require (
7474
github.com/coredns/coredns v1.12.1
7575
github.com/coreos/go-iptables v0.8.0
7676
github.com/cosi-project/runtime v0.10.2
77+
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
7778
github.com/distribution/reference v0.6.0
7879
github.com/docker/cli v28.0.4+incompatible
7980
github.com/docker/docker v28.0.4+incompatible

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
175175
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
176176
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
177177
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
178+
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e h1:lj77EKYUpYXTd8CD/+QMIf8b6OIOTsfEBSXiAzuEHTU=
179+
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e/go.mod h1:3ZQK6DMPSz/QZ73jlWxBtUhNA8xZx7LzUFSq/OfP8vk=
178180
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
179181
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
180182
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
@@ -685,8 +687,8 @@ github.com/siderolabs/wireguard-go v0.0.0-20240401105714-9c7067e9d4b9 h1:VSb26LY
685687
github.com/siderolabs/wireguard-go v0.0.0-20240401105714-9c7067e9d4b9/go.mod h1:7+dAh+K+Zo+AnP0mCypmwx7M6k2SyqRuLQMX91qZPr0=
686688
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
687689
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
688-
github.com/smira/dhcp v0.0.0-20241001122726-31e9ef21c016 h1:pImpynwlfelZICjeAVIj4OdNsS+RadE4D+KC+RzpUt8=
689-
github.com/smira/dhcp v0.0.0-20241001122726-31e9ef21c016/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
690+
github.com/smira/dhcp v0.0.0-20250407153013-99942baa5d59 h1:lBQLOP8ZZI6mbePwGX/bqXi3srwhYB/zcwqrnX+JJIc=
691+
github.com/smira/dhcp v0.0.0-20250407153013-99942baa5d59/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
690692
github.com/smira/kobject v0.0.0-20240304111826-49c8d4613389 h1:f/5NRv5IGZxbjBhc5MnlbNmyuXBPxvekhBAUzyKWyLY=
691693
github.com/smira/kobject v0.0.0-20240304111826-49c8d4613389/go.mod h1:+SexPO1ZvdbbWUdUnyXEWv3+4NwHZjKhxOmQqHY4Pqc=
692694
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,25 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5-
//go:build linux
5+
//go:build linux || darwin
66

77
package providers
88

99
import (
1010
"context"
11+
"errors"
12+
"os"
13+
"runtime"
1114

1215
"github.com/siderolabs/talos/pkg/provision"
1316
"github.com/siderolabs/talos/pkg/provision/providers/qemu"
1417
)
1518

1619
func newQemu(ctx context.Context) (provision.Provisioner, error) {
20+
// TODO: remove once the provisioner works on darwin
21+
_, darwinQemuEnvIsSet := os.LookupEnv("DARWIN_QEMU")
22+
if runtime.GOOS == "darwin" && !darwinQemuEnvIsSet {
23+
return nil, errors.New("qemu provisioner is not supported on this platform")
24+
}
1725
return qemu.NewProvisioner(ctx)
1826
}

pkg/provision/providers/qemu/launch.go

Lines changed: 3 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,10 @@ import (
1717
"strings"
1818
"time"
1919

20-
"github.com/alexflint/go-filemutex"
2120
"github.com/containernetworking/cni/libcni"
22-
"github.com/containernetworking/cni/pkg/types"
23-
types100 "github.com/containernetworking/cni/pkg/types/100"
24-
"github.com/containernetworking/plugins/pkg/ns"
25-
"github.com/containernetworking/plugins/pkg/testutils"
26-
"github.com/containernetworking/plugins/pkg/utils"
27-
"github.com/coreos/go-iptables/iptables"
2821
"github.com/google/uuid"
29-
"github.com/siderolabs/gen/xslices"
30-
"github.com/siderolabs/go-blockdevice/v2/blkid"
31-
sideronet "github.com/siderolabs/net"
3222

3323
"github.com/siderolabs/talos/pkg/provision"
34-
"github.com/siderolabs/talos/pkg/provision/internal/cniutils"
3524
"github.com/siderolabs/talos/pkg/provision/providers/vm"
3625
)
3726

@@ -94,7 +83,7 @@ type LaunchConfig struct {
9483
// filled by CNI invocation
9584
tapName string
9685
vmMAC string
97-
ns ns.NetNS
86+
nsPath string
9887

9988
// signals
10089
c chan os.Signal
@@ -108,190 +97,6 @@ type tpm2Config struct {
10897
StateDir string
10998
}
11099

111-
// withCNIOperationLocked ensures that CNI operations don't run concurrently.
112-
//
113-
// There are race conditions in the CNI plugins that can cause a failure if called concurrently.
114-
func withCNIOperationLocked[T any](config *LaunchConfig, f func() (T, error)) (T, error) {
115-
var zeroT T
116-
117-
lock, err := filemutex.New(filepath.Join(config.StatePath, "cni.lock"))
118-
if err != nil {
119-
return zeroT, fmt.Errorf("failed to create CNI lock: %w", err)
120-
}
121-
122-
if err = lock.Lock(); err != nil {
123-
return zeroT, fmt.Errorf("failed to acquire CNI lock: %w", err)
124-
}
125-
126-
defer func() {
127-
if err := lock.Close(); err != nil {
128-
log.Printf("failed to release CNI lock: %s", err)
129-
}
130-
}()
131-
132-
return f()
133-
}
134-
135-
// withCNIOperationLockedNoResult ensures that CNI operations don't run concurrently.
136-
func withCNIOperationLockedNoResult(config *LaunchConfig, f func() error) error {
137-
_, err := withCNIOperationLocked(config, func() (struct{}, error) {
138-
return struct{}{}, f()
139-
})
140-
141-
return err
142-
}
143-
144-
// withCNI creates network namespace, launches CNI and passes control to the next function
145-
// filling config with netNS and interface details.
146-
//
147-
//nolint:gocyclo
148-
func withCNI(ctx context.Context, config *LaunchConfig, f func(config *LaunchConfig) error) error {
149-
// random ID for the CNI, maps to single VM
150-
containerID := uuid.New().String()
151-
152-
cniConfig := libcni.NewCNIConfigWithCacheDir(config.CNI.BinPath, config.CNI.CacheDir, nil)
153-
154-
// create a network namespace
155-
ns, err := testutils.NewNS()
156-
if err != nil {
157-
return err
158-
}
159-
160-
defer func() {
161-
ns.Close() //nolint:errcheck
162-
testutils.UnmountNS(ns) //nolint:errcheck
163-
}()
164-
165-
ips := make([]string, len(config.IPs))
166-
for j := range ips {
167-
ips[j] = sideronet.FormatCIDR(config.IPs[j], config.CIDRs[j])
168-
}
169-
170-
gatewayAddrs := xslices.Map(config.GatewayAddrs, netip.Addr.String)
171-
172-
runtimeConf := libcni.RuntimeConf{
173-
ContainerID: containerID,
174-
NetNS: ns.Path(),
175-
IfName: "veth0",
176-
Args: [][2]string{
177-
{"IP", strings.Join(ips, ",")},
178-
{"GATEWAY", strings.Join(gatewayAddrs, ",")},
179-
{"IgnoreUnknown", "1"},
180-
},
181-
}
182-
183-
// attempt to clean up network in case it was deployed previously
184-
err = withCNIOperationLockedNoResult(
185-
config,
186-
func() error {
187-
return cniConfig.DelNetworkList(ctx, config.NetworkConfig, &runtimeConf)
188-
},
189-
)
190-
if err != nil {
191-
return fmt.Errorf("error deleting CNI network: %w", err)
192-
}
193-
194-
res, err := withCNIOperationLocked(
195-
config,
196-
func() (types.Result, error) {
197-
return cniConfig.AddNetworkList(ctx, config.NetworkConfig, &runtimeConf)
198-
},
199-
)
200-
if err != nil {
201-
return fmt.Errorf("error provisioning CNI network: %w", err)
202-
}
203-
204-
defer func() {
205-
if e := withCNIOperationLockedNoResult(
206-
config,
207-
func() error {
208-
return cniConfig.DelNetworkList(ctx, config.NetworkConfig, &runtimeConf)
209-
},
210-
); e != nil {
211-
log.Printf("error cleaning up CNI: %s", e)
212-
}
213-
}()
214-
215-
currentResult, err := types100.NewResultFromResult(res)
216-
if err != nil {
217-
return fmt.Errorf("failed to parse cni result: %w", err)
218-
}
219-
220-
vmIface, tapIface, err := cniutils.VMTapPair(currentResult, containerID)
221-
if err != nil {
222-
return errors.New(
223-
"failed to parse VM network configuration from CNI output, ensure CNI is configured with a plugin " +
224-
"that supports automatic VM network configuration such as tc-redirect-tap")
225-
}
226-
227-
cniChain := utils.FormatChainName(config.NetworkConfig.Name, containerID)
228-
229-
ipt, err := iptables.New()
230-
if err != nil {
231-
return fmt.Errorf("failed to initialize iptables: %w", err)
232-
}
233-
234-
// don't masquerade traffic with "broadcast" destination from the VM
235-
//
236-
// no need to clean up the rule, as CNI drops the whole chain
237-
if err = ipt.InsertUnique("nat", cniChain, 1, "--destination", "255.255.255.255/32", "-j", "ACCEPT"); err != nil {
238-
return fmt.Errorf("failed to insert iptables rule to allow broadcast traffic: %w", err)
239-
}
240-
241-
for _, cidr := range config.NoMasqueradeCIDRs {
242-
if err = ipt.InsertUnique("nat", cniChain, 1, "--destination", cidr.String(), "-j", "ACCEPT"); err != nil {
243-
return fmt.Errorf("failed to insert iptables rule to allow non-masquerade traffic to cidr %q: %w", cidr.String(), err)
244-
}
245-
}
246-
247-
config.tapName = tapIface.Name
248-
config.vmMAC = vmIface.Mac
249-
config.ns = ns
250-
251-
for j := range config.CIDRs {
252-
nameservers := make([]netip.Addr, 0, len(config.Nameservers))
253-
254-
// filter nameservers by IPv4/IPv6 matching IPs
255-
for i := range config.Nameservers {
256-
if config.IPs[j].Is6() {
257-
if config.Nameservers[i].Is6() {
258-
nameservers = append(nameservers, config.Nameservers[i])
259-
}
260-
} else {
261-
if config.Nameservers[i].Is4() {
262-
nameservers = append(nameservers, config.Nameservers[i])
263-
}
264-
}
265-
}
266-
267-
// dump node IP/mac/hostname for dhcp
268-
if err = vm.DumpIPAMRecord(config.StatePath, vm.IPAMRecord{
269-
IP: config.IPs[j],
270-
Netmask: byte(config.CIDRs[j].Bits()),
271-
MAC: vmIface.Mac,
272-
Hostname: config.Hostname,
273-
Gateway: config.GatewayAddrs[j],
274-
MTU: config.MTU,
275-
Nameservers: nameservers,
276-
TFTPServer: config.TFTPServer,
277-
IPXEBootFilename: config.IPXEBootFileName,
278-
}); err != nil {
279-
return err
280-
}
281-
}
282-
283-
return f(config)
284-
}
285-
286-
func checkPartitions(config *LaunchConfig) (bool, error) {
287-
info, err := blkid.ProbePath(config.DiskPaths[0], blkid.WithSectorSize(config.DiskBlockSizes[0]))
288-
if err != nil {
289-
return false, fmt.Errorf("error probing disk: %w", err)
290-
}
291-
292-
return info.Name == "gpt" && len(info.Parts) > 0, nil
293-
}
294-
295100
// launchVM runs qemu with args built based on config.
296101
//
297102
//nolint:gocyclo,cyclop
@@ -521,9 +326,7 @@ func launchVM(config *LaunchConfig) error {
521326
cmd.Stdout = os.Stdout
522327
cmd.Stderr = os.Stderr
523328

524-
if err := ns.WithNetNSPath(config.ns.Path(), func(_ ns.NetNS) error {
525-
return cmd.Start()
526-
}); err != nil {
329+
if err := startQemuCmd(config, cmd); err != nil {
527330
return err
528331
}
529332

@@ -608,7 +411,7 @@ func Launch() error {
608411
config.sdStubExtraCmdlineConfig = fmt.Sprintf(" talos.config=http://%s/config.yaml", httpServer.GetAddr())
609412
}
610413

611-
return withCNI(ctx, &config, func(config *LaunchConfig) error {
414+
return withNetworkContext(ctx, &config, func(config *LaunchConfig) error {
612415
for {
613416
for config.controller.PowerState() != PoweredOn {
614417
select {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package qemu
6+
7+
import (
8+
"context"
9+
"os/exec"
10+
)
11+
12+
func withNetworkContext(ctx context.Context, config *LaunchConfig, f func(config *LaunchConfig) error) error {
13+
panic("not implemented")
14+
}
15+
16+
func checkPartitions(config *LaunchConfig) (bool, error) {
17+
panic("not implemented")
18+
}
19+
20+
// startQemuCmd on darwin just runs cmd.Start
21+
func startQemuCmd(_ *LaunchConfig, cmd *exec.Cmd) error {
22+
return cmd.Start()
23+
}

0 commit comments

Comments
 (0)