Skip to content

Commit 39ef731

Browse files
committed
net fm
1 parent f265c8e commit 39ef731

8 files changed

Lines changed: 127 additions & 13 deletions

File tree

cmd/vm/commands.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,5 @@ func addVMFlags(cmd *cobra.Command) {
113113
cmd.Flags().Int("cpu", 2, "boot CPUs") //nolint:mnd
114114
cmd.Flags().String("memory", "1G", "memory size") //nolint:mnd
115115
cmd.Flags().String("storage", "10G", "COW disk size") //nolint:mnd
116+
cmd.Flags().Bool("no-network", false, "skip network configuration")
116117
}

cmd/vm/handler.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,14 @@ func (h Handler) createVM(cmd *cobra.Command, image string) (context.Context, *c
280280
}
281281
cmdcore.EnsureFirmwarePath(conf, bootCfg)
282282

283-
// TODO pass network configs
284-
info, err := hyper.Create(ctx, vmCfg, storageConfigs, nil, bootCfg)
283+
var networkConfigs []*types.NetworkConfig
284+
noNetwork, _ := cmd.Flags().GetBool("no-network")
285+
if !noNetwork {
286+
// TODO: call network.Config(ctx, []*types.VMConfig{vmCfg}) when a network provider is available.
287+
_ = noNetwork
288+
}
289+
290+
info, err := hyper.Create(ctx, vmCfg, storageConfigs, networkConfigs, bootCfg)
285291
if err != nil {
286292
return nil, nil, fmt.Errorf("create VM: %w", err)
287293
}

hypervisor/cloudhypervisor/api.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,20 @@ type chVMConfig struct {
1313
CPUs chCPUs `json:"cpus"`
1414
Memory chMemory `json:"memory"`
1515
Disks []chDisk `json:"disks,omitempty"`
16+
Nets []chNet `json:"nets,omitempty"`
1617
RNG chRNG `json:"rng"`
1718
Watchdog bool `json:"watchdog"`
1819
Serial chRuntimeFile `json:"serial"`
1920
Console chRuntimeFile `json:"console"`
2021
}
2122

23+
type chNet struct {
24+
Tap string `json:"tap"`
25+
Mac string `json:"mac,omitempty"`
26+
NumQueues int64 `json:"num_queues,omitempty"`
27+
QueueSize int64 `json:"queue_size,omitempty"`
28+
}
29+
2230
type chPayload struct {
2331
Firmware string `json:"firmware,omitempty"`
2432
Kernel string `json:"kernel,omitempty"`

hypervisor/cloudhypervisor/conf.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ func buildVMConfig(rec *hypervisor.VMRecord, consoleSockPath string) *chVMConfig
4848
cfg.Disks = append(cfg.Disks, storageConfigToDisk(sc, cpu))
4949
}
5050

51+
for _, nc := range rec.NetworkConfigs {
52+
cfg.Nets = append(cfg.Nets, networkConfigToNet(nc))
53+
}
54+
5155
if boot := rec.BootConfig; boot != nil {
5256
switch {
5357
case boot.KernelPath != "":
@@ -64,6 +68,15 @@ func buildVMConfig(rec *hypervisor.VMRecord, consoleSockPath string) *chVMConfig
6468
return cfg
6569
}
6670

71+
func networkConfigToNet(nc *types.NetworkConfig) chNet {
72+
return chNet{
73+
Tap: nc.Tap,
74+
Mac: nc.Mac,
75+
NumQueues: nc.Queue,
76+
QueueSize: nc.QueueSize,
77+
}
78+
}
79+
6780
func storageConfigToDisk(sc *types.StorageConfig, cpuCount int) chDisk {
6881
d := chDisk{
6982
Path: sc.Path,
@@ -127,6 +140,13 @@ func buildCLIArgs(cfg *chVMConfig, socketPath string) []string {
127140
}
128141
}
129142

143+
if len(cfg.Nets) > 0 {
144+
args = append(args, "--net")
145+
for _, n := range cfg.Nets {
146+
args = append(args, netToCLIArg(n))
147+
}
148+
}
149+
130150
args = append(args, "--rng", fmt.Sprintf("src=%s", cfg.RNG.Src))
131151

132152
if cfg.Watchdog {
@@ -172,6 +192,20 @@ func diskToCLIArg(d chDisk) string {
172192
return strings.Join(parts, ",")
173193
}
174194

195+
func netToCLIArg(n chNet) string {
196+
parts := []string{"tap=" + n.Tap}
197+
if n.Mac != "" {
198+
parts = append(parts, "mac="+n.Mac)
199+
}
200+
if n.NumQueues > 0 {
201+
parts = append(parts, fmt.Sprintf("num_queues=%d", n.NumQueues))
202+
}
203+
if n.QueueSize > 0 {
204+
parts = append(parts, fmt.Sprintf("queue_size=%d", n.QueueSize))
205+
}
206+
return strings.Join(parts, ",")
207+
}
208+
175209
func balloonToCLIArg(b *chBalloon) string {
176210
parts := []string{fmt.Sprintf("size=%d", b.Size)}
177211
if b.DeflateOnOOM {

hypervisor/cloudhypervisor/create.go

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cloudhypervisor
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"os"
78
"os/exec"
89
"path/filepath"
@@ -23,7 +24,7 @@ const CowSerial = "cocoon-cow"
2324
// To avoid a race with GC (which scans directories and removes those not in
2425
// the DB), we write a placeholder record first, then create directories and
2526
// prepare disks, and finally update the record to Created state.
26-
func (ch *CloudHypervisor) Create(ctx context.Context, vmCfg *types.VMConfig, storageConfigs []*types.StorageConfig, _ []*types.NetworkConfig, bootCfg *types.BootConfig) (*types.VM, error) {
27+
func (ch *CloudHypervisor) Create(ctx context.Context, vmCfg *types.VMConfig, storageConfigs []*types.StorageConfig, networkConfigs []*types.NetworkConfig, bootCfg *types.BootConfig) (*types.VM, error) {
2728
id := hypervisor.GenerateID()
2829
now := time.Now()
2930

@@ -66,9 +67,9 @@ func (ch *CloudHypervisor) Create(ctx context.Context, vmCfg *types.VMConfig, st
6667
bootCopy = &b
6768
}
6869
if bootCopy != nil && bootCopy.KernelPath != "" {
69-
sc, err = ch.prepareOCI(ctx, id, vmCfg, storageConfigs, bootCopy)
70+
sc, err = ch.prepareOCI(ctx, id, vmCfg, storageConfigs, networkConfigs, bootCopy)
7071
} else {
71-
sc, err = ch.prepareCloudimg(ctx, id, vmCfg, storageConfigs)
72+
sc, err = ch.prepareCloudimg(ctx, id, vmCfg, storageConfigs, networkConfigs)
7273
}
7374
if err != nil {
7475
_ = ch.removeVMDirs(ctx, id)
@@ -84,6 +85,7 @@ func (ch *CloudHypervisor) Create(ctx context.Context, vmCfg *types.VMConfig, st
8485
rec := hypervisor.VMRecord{
8586
VM: info,
8687
StorageConfigs: sc,
88+
NetworkConfigs: networkConfigs,
8789
BootConfig: bootCopy,
8890
ImageBlobIDs: blobIDs,
8991
}
@@ -102,7 +104,7 @@ func (ch *CloudHypervisor) Create(ctx context.Context, vmCfg *types.VMConfig, st
102104
// prepareOCI creates a raw COW disk, appends the COW StorageConfig, and builds
103105
// the kernel cmdline with layer/cow serial mappings.
104106
// Returns the updated StorageConfig slice.
105-
func (ch *CloudHypervisor) prepareOCI(ctx context.Context, vmID string, vmCfg *types.VMConfig, sc []*types.StorageConfig, boot *types.BootConfig) ([]*types.StorageConfig, error) {
107+
func (ch *CloudHypervisor) prepareOCI(ctx context.Context, vmID string, vmCfg *types.VMConfig, sc []*types.StorageConfig, nc []*types.NetworkConfig, boot *types.BootConfig) ([]*types.StorageConfig, error) {
106108
cowPath := ch.conf.CHVMCOWRawPath(vmID)
107109

108110
// Create sparse COW file (equivalent to truncate -s <size>).
@@ -126,17 +128,32 @@ func (ch *CloudHypervisor) prepareOCI(ctx context.Context, vmID string, vmCfg *t
126128
})
127129

128130
// Build cmdline with reversed layer serials for overlayfs lowerdir ordering (top layer first).
129-
boot.Cmdline = fmt.Sprintf(
131+
var cmdline strings.Builder
132+
fmt.Fprintf(&cmdline,
130133
"console=hvc0 loglevel=3 boot=cocoon cocoon.layers=%s cocoon.cow=%s clocksource=kvm-clock rw",
131134
strings.Join(ReverseLayerSerials(sc), ","), CowSerial,
132135
)
133136

137+
// Append static IP configuration for each network interface.
138+
// Format: ip=<client-IP>:<server>:<gw-IP>:<netmask>:<hostname>:<device>:<autoconf>
139+
if len(nc) > 0 {
140+
cmdline.WriteString(" net.ifnames=0")
141+
for _, n := range nc {
142+
if n.Network != nil {
143+
fmt.Fprintf(&cmdline, " ip=%s::%s:%s::%s:off",
144+
n.Network.IP, n.Network.Gateway,
145+
net.IP(n.Network.Netmask), n.Network.Device)
146+
}
147+
}
148+
}
149+
boot.Cmdline = cmdline.String()
150+
134151
return sc, nil
135152
}
136153

137154
// prepareCloudimg creates a qcow2 COW overlay backed by the base image blob.
138155
// Returns the updated StorageConfig slice (replaced with the overlay).
139-
func (ch *CloudHypervisor) prepareCloudimg(ctx context.Context, vmID string, vmCfg *types.VMConfig, sc []*types.StorageConfig) ([]*types.StorageConfig, error) {
156+
func (ch *CloudHypervisor) prepareCloudimg(ctx context.Context, vmID string, vmCfg *types.VMConfig, sc []*types.StorageConfig, nc []*types.NetworkConfig) ([]*types.StorageConfig, error) {
140157
if len(sc) == 0 {
141158
return nil, fmt.Errorf("cloudimg: no base image StorageConfig")
142159
}
@@ -162,16 +179,30 @@ func (ch *CloudHypervisor) prepareCloudimg(ctx context.Context, vmID string, vmC
162179
}
163180

164181
// Generate cloud-init cidata disk.
182+
metaCfg := &metadata.Config{
183+
InstanceID: vmID,
184+
Hostname: vmCfg.Name,
185+
RootPassword: ch.conf.DefaultRootPassword,
186+
}
187+
for _, n := range nc {
188+
if n.Network != nil {
189+
ones, _ := n.Network.Netmask.Size()
190+
metaCfg.Networks = append(metaCfg.Networks, metadata.NetworkInfo{
191+
IP: n.Network.IP.String(),
192+
Prefix: ones,
193+
Gateway: n.Network.Gateway.String(),
194+
Device: n.Network.Device,
195+
Mac: n.Mac,
196+
})
197+
}
198+
}
199+
165200
cidataPath := ch.conf.CHVMCidataPath(vmID)
166201
f, err := os.Create(cidataPath) //nolint:gosec
167202
if err != nil {
168203
return nil, fmt.Errorf("create cidata: %w", err)
169204
}
170-
if err := metadata.Generate(f, &metadata.Config{
171-
InstanceID: vmID,
172-
Hostname: vmCfg.Name,
173-
RootPassword: ch.conf.DefaultRootPassword,
174-
}); err != nil {
205+
if err := metadata.Generate(f, metaCfg); err != nil {
175206
_ = f.Close()
176207
return nil, fmt.Errorf("generate cidata: %w", err)
177208
}

hypervisor/db.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type VMRecord struct {
1414
types.VM
1515

1616
StorageConfigs []*types.StorageConfig `json:"storage_configs"`
17+
NetworkConfigs []*types.NetworkConfig `json:"network_configs,omitempty"`
1718
BootConfig *types.BootConfig `json:"boot_config,omitempty"` // nil for UEFI boot (cloudimg)
1819
ImageBlobIDs map[string]struct{} `json:"image_blob_ids,omitempty"` // blob hex set for GC pinning
1920
}

metadata/metadata.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ type Config struct {
1515
InstanceID string
1616
Hostname string
1717
RootPassword string
18+
Networks []NetworkInfo
19+
}
20+
21+
// NetworkInfo describes a single guest network interface for cloud-init netplan.
22+
type NetworkInfo struct {
23+
IP string // e.g. "10.0.0.2"
24+
Prefix int // CIDR prefix length, e.g. 24
25+
Gateway string // e.g. "10.0.0.1"
26+
Device string // e.g. "eth0"
27+
Mac string // e.g. "52:54:00:01:02:03"
1828
}
1929

2030
var tmplFuncs = template.FuncMap{
@@ -36,6 +46,25 @@ chpasswd:
3646
ssh_pwauth: true
3747
disable_root: false
3848
{{- end}}
49+
{{- if .Networks}}
50+
network:
51+
version: 2
52+
ethernets:
53+
{{- range .Networks}}
54+
{{.Device}}:
55+
{{- if .Mac}}
56+
match:
57+
macaddress: '{{.Mac}}'
58+
{{- end}}
59+
addresses:
60+
- {{.IP}}/{{.Prefix}}
61+
{{- if .Gateway}}
62+
routes:
63+
- to: default
64+
via: {{.Gateway}}
65+
{{- end}}
66+
{{- end}}
67+
{{- end}}
3968
`))
4069

4170
// Generate streams a cloud-init NoCloud cidata disk image (FAT12) to w.

types/network.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ type NetworkConfig struct {
88
Queue int64 `json:"queue"`
99
QueueSize int64 `json:"queue_size"`
1010

11+
// Guest-side IP configuration returned by the network plugin.
12+
// nil means DHCP / no static config.
13+
Network *Network `json:"network,omitempty"`
14+
1115
// offload_tso=on
1216
// offload_ufo=on
1317
// offload_csum=on

0 commit comments

Comments
 (0)