@@ -3,6 +3,7 @@ package cloudhypervisor
33import (
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 }
0 commit comments