Skip to content

Commit 2a8e14e

Browse files
committed
review fixes
1 parent 4ca52dd commit 2a8e14e

File tree

11 files changed

+112
-21
lines changed

11 files changed

+112
-21
lines changed

cmd/api/api/instances.go

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,38 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
116116
networkEnabled = *request.Body.Network.Enabled
117117
}
118118

119+
// Parse network bandwidth limits (0 = auto)
120+
var networkBandwidthDownload int64
121+
var networkBandwidthUpload int64
122+
if request.Body.Network != nil {
123+
if request.Body.Network.BandwidthDownload != nil && *request.Body.Network.BandwidthDownload != "" {
124+
var bwBytes datasize.ByteSize
125+
bwStr := *request.Body.Network.BandwidthDownload
126+
bwStr = strings.TrimSuffix(bwStr, "/s")
127+
bwStr = strings.TrimSuffix(bwStr, "ps")
128+
if err := bwBytes.UnmarshalText([]byte(bwStr)); err != nil {
129+
return oapi.CreateInstance400JSONResponse{
130+
Code: "invalid_bandwidth_download",
131+
Message: fmt.Sprintf("invalid bandwidth_download format: %v", err),
132+
}, nil
133+
}
134+
networkBandwidthDownload = int64(bwBytes)
135+
}
136+
if request.Body.Network.BandwidthUpload != nil && *request.Body.Network.BandwidthUpload != "" {
137+
var bwBytes datasize.ByteSize
138+
bwStr := *request.Body.Network.BandwidthUpload
139+
bwStr = strings.TrimSuffix(bwStr, "/s")
140+
bwStr = strings.TrimSuffix(bwStr, "ps")
141+
if err := bwBytes.UnmarshalText([]byte(bwStr)); err != nil {
142+
return oapi.CreateInstance400JSONResponse{
143+
Code: "invalid_bandwidth_upload",
144+
Message: fmt.Sprintf("invalid bandwidth_upload format: %v", err),
145+
}, nil
146+
}
147+
networkBandwidthUpload = int64(bwBytes)
148+
}
149+
}
150+
119151
// Parse devices (GPU passthrough)
120152
var deviceRefs []string
121153
if request.Body.Devices != nil {
@@ -163,18 +195,20 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
163195
}
164196

165197
domainReq := instances.CreateInstanceRequest{
166-
Name: request.Body.Name,
167-
Image: request.Body.Image,
168-
Size: size,
169-
HotplugSize: hotplugSize,
170-
OverlaySize: overlaySize,
171-
Vcpus: vcpus,
172-
DiskIOBps: diskIOBps,
173-
Env: env,
174-
NetworkEnabled: networkEnabled,
175-
Devices: deviceRefs,
176-
Volumes: volumes,
177-
Hypervisor: hvType,
198+
Name: request.Body.Name,
199+
Image: request.Body.Image,
200+
Size: size,
201+
HotplugSize: hotplugSize,
202+
OverlaySize: overlaySize,
203+
Vcpus: vcpus,
204+
DiskIOBps: diskIOBps,
205+
NetworkBandwidthDownload: networkBandwidthDownload,
206+
NetworkBandwidthUpload: networkBandwidthUpload,
207+
Env: env,
208+
NetworkEnabled: networkEnabled,
209+
Devices: deviceRefs,
210+
Volumes: volumes,
211+
Hypervisor: hvType,
178212
}
179213

180214
inst, err := s.InstanceManager.CreateInstance(ctx, domainReq)

cmd/api/config/config.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"fmt"
45
"os"
56
"runtime/debug"
67
"strconv"
@@ -112,6 +113,9 @@ type Config struct {
112113
OversubNetwork float64 // Network oversubscription ratio
113114
OversubDiskIO float64 // Disk I/O oversubscription ratio
114115

116+
// Network rate limiting
117+
UploadBurstMultiplier int // Multiplier for upload burst ceiling vs guaranteed rate (default: 4)
118+
115119
// Resource capacity limits (empty = auto-detect from host)
116120
DiskLimit string // Hard disk limit for DataDir, e.g. "500GB"
117121
NetworkLimit string // Hard network limit, e.g. "10Gbps" (empty = detect from uplink speed)
@@ -190,6 +194,9 @@ func Load() *Config {
190194
OversubNetwork: getEnvFloat("OVERSUB_NETWORK", 2.0),
191195
OversubDiskIO: getEnvFloat("OVERSUB_DISK_IO", 2.0),
192196

197+
// Network rate limiting
198+
UploadBurstMultiplier: getEnvInt("UPLOAD_BURST_MULTIPLIER", 4),
199+
193200
// Resource capacity limits (empty = auto-detect)
194201
DiskLimit: getEnv("DISK_LIMIT", ""),
195202
NetworkLimit: getEnv("NETWORK_LIMIT", ""),
@@ -233,3 +240,28 @@ func getEnvFloat(key string, defaultValue float64) float64 {
233240
}
234241
return defaultValue
235242
}
243+
244+
// Validate checks configuration values for correctness.
245+
// Returns an error if any configuration value is invalid.
246+
func (c *Config) Validate() error {
247+
// Validate oversubscription ratios are positive
248+
if c.OversubCPU <= 0 {
249+
return fmt.Errorf("OVERSUB_CPU must be positive, got %v", c.OversubCPU)
250+
}
251+
if c.OversubMemory <= 0 {
252+
return fmt.Errorf("OVERSUB_MEMORY must be positive, got %v", c.OversubMemory)
253+
}
254+
if c.OversubDisk <= 0 {
255+
return fmt.Errorf("OVERSUB_DISK must be positive, got %v", c.OversubDisk)
256+
}
257+
if c.OversubNetwork <= 0 {
258+
return fmt.Errorf("OVERSUB_NETWORK must be positive, got %v", c.OversubNetwork)
259+
}
260+
if c.OversubDiskIO <= 0 {
261+
return fmt.Errorf("OVERSUB_DISK_IO must be positive, got %v", c.OversubDiskIO)
262+
}
263+
if c.UploadBurstMultiplier < 1 {
264+
return fmt.Errorf("UPLOAD_BURST_MULTIPLIER must be >= 1, got %v", c.UploadBurstMultiplier)
265+
}
266+
return nil
267+
}

cmd/api/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ func run() error {
182182
if err := app.NetworkManager.SetupHTB(app.Ctx, networkCapacity); err != nil {
183183
logger.Warn("failed to setup HTB on bridge (network rate limiting disabled)", "error", err)
184184
}
185-
logger.Info("Set up HTB qdisc on bridge for network fair sharing")
186185

187186
// Reconcile device state (clears orphaned attachments from crashed VMs)
188187
// Set up liveness checker so device reconciliation can accurately detect orphaned attachments

lib/instances/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ func (m *manager) createInstance(
336336
InstanceName: req.Name,
337337
DownloadBps: stored.NetworkBandwidthDownload,
338338
UploadBps: stored.NetworkBandwidthUpload,
339-
UploadCeilBps: stored.NetworkBandwidthUpload * 2, // Allow burst to 2x guaranteed rate
339+
UploadCeilBps: stored.NetworkBandwidthUpload * int64(m.networkManager.GetUploadBurstMultiplier()),
340340
})
341341
if err != nil {
342342
log.ErrorContext(ctx, "failed to allocate network", "instance_id", id, "network", networkName, "error", err)

lib/network/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ Network bandwidth is limited separately for download and upload directions:
252252
When not specified in the create request:
253253
- Both download and upload = `(vcpus / cpu_capacity) * network_capacity`
254254
- Symmetric by default
255-
- Upload ceiling = 2x guaranteed rate (allows bursting)
255+
- Upload ceiling = 4x guaranteed rate (configurable via `UPLOAD_BURST_MULTIPLIER`)
256256

257257
Note: In case of unexpected scenarios like power loss, straggler TAP devices may persist until manual cleanup or host reboot.
258258

lib/network/allocate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ func (m *manager) RecreateAllocation(ctx context.Context, instanceID string, dow
111111
}
112112

113113
// 3. Recreate TAP device with same name and rate limits from instance metadata
114-
// Use uploadBps as ceiling too (same as guaranteed rate on restore)
115-
if err := m.createTAPDevice(alloc.TAPDevice, network.Bridge, network.Isolated, downloadBps, uploadBps, uploadBps); err != nil {
114+
uploadCeilBps := uploadBps * int64(m.GetUploadBurstMultiplier())
115+
if err := m.createTAPDevice(alloc.TAPDevice, network.Bridge, network.Isolated, downloadBps, uploadBps, uploadCeilBps); err != nil {
116116
return fmt.Errorf("create TAP device: %w", err)
117117
}
118118
m.recordTAPOperation(ctx, "create")

lib/network/manager.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ type Manager interface {
3030
GetAllocation(ctx context.Context, instanceID string) (*Allocation, error)
3131
ListAllocations(ctx context.Context) ([]Allocation, error)
3232
NameExists(ctx context.Context, name string) (bool, error)
33+
34+
// GetUploadBurstMultiplier returns the configured multiplier for upload burst ceiling.
35+
GetUploadBurstMultiplier() int
3336
}
3437

3538
// manager implements the Manager interface
@@ -123,3 +126,12 @@ func (m *manager) getDefaultNetwork(ctx context.Context) (*Network, error) {
123126
func (m *manager) SetupHTB(ctx context.Context, capacityBps int64) error {
124127
return m.setupBridgeHTB(ctx, m.config.BridgeName, capacityBps)
125128
}
129+
130+
// GetUploadBurstMultiplier returns the configured multiplier for upload burst ceiling.
131+
// Defaults to 4 if not configured.
132+
func (m *manager) GetUploadBurstMultiplier() int {
133+
if m.config.UploadBurstMultiplier < 1 {
134+
return DefaultUploadBurstMultiplier
135+
}
136+
return m.config.UploadBurstMultiplier
137+
}

lib/network/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ package network
22

33
import "time"
44

5+
// DefaultUploadBurstMultiplier is the default multiplier applied to guaranteed upload rate
6+
// to calculate the burst ceiling. This allows VMs to burst above their guaranteed rate
7+
// when other VMs are not using their full bandwidth allocation.
8+
// Configurable via UPLOAD_BURST_MULTIPLIER environment variable.
9+
const DefaultUploadBurstMultiplier = 4
10+
511
// Network represents a virtual network for instances
612
type Network struct {
713
Name string // "default", "internal"

lib/resources/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Bidirectional rate limiting with separate download and upload controls:
5757
**Default limits:**
5858
- Proportional to CPU: `(vcpus / cpu_capacity) * network_capacity`
5959
- Symmetric download/upload by default
60-
- Upload ceiling = 2x guaranteed rate (allows bursting when bandwidth available)
60+
- Upload ceiling = 4x guaranteed rate by default (configurable via `UPLOAD_BURST_MULTIPLIER`)
6161

6262
**Capacity tracking:**
6363
- Uses max(download, upload) per instance since they share physical link

lib/resources/cpu.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package resources
33
import (
44
"bufio"
55
"context"
6+
"fmt"
67
"os"
78
"strconv"
89
"strings"
@@ -63,7 +64,7 @@ func (c *CPUResource) Allocated(ctx context.Context) (int64, error) {
6364
func detectCPUCapacity() (int64, error) {
6465
file, err := os.Open("/proc/cpuinfo")
6566
if err != nil {
66-
return 0, err
67+
return 0, fmt.Errorf("open /proc/cpuinfo: %w", err)
6768
}
6869
defer file.Close()
6970

0 commit comments

Comments
 (0)