66 "errors"
77 "fmt"
88 "net/http"
9+ "strings"
910
1011 "github.com/c2h5oh/datasize"
1112 "github.com/onkernel/hypeman/lib/guest"
@@ -15,6 +16,7 @@ import (
1516 mw "github.com/onkernel/hypeman/lib/middleware"
1617 "github.com/onkernel/hypeman/lib/network"
1718 "github.com/onkernel/hypeman/lib/oapi"
19+ "github.com/onkernel/hypeman/lib/resources"
1820 "github.com/samber/lo"
1921)
2022
@@ -82,6 +84,23 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
8284 overlaySize = int64 (overlayBytes )
8385 }
8486
87+ // Parse disk_io_bps (0 = auto/unlimited)
88+ diskIOBps := int64 (0 )
89+ if request .Body .DiskIoBps != nil && * request .Body .DiskIoBps != "" {
90+ var ioBpsBytes datasize.ByteSize
91+ // Remove "/s" suffix if present
92+ ioStr := * request .Body .DiskIoBps
93+ ioStr = strings .TrimSuffix (ioStr , "/s" )
94+ ioStr = strings .TrimSuffix (ioStr , "ps" )
95+ if err := ioBpsBytes .UnmarshalText ([]byte (ioStr )); err != nil {
96+ return oapi.CreateInstance400JSONResponse {
97+ Code : "invalid_disk_io_bps" ,
98+ Message : fmt .Sprintf ("invalid disk_io_bps format: %v" , err ),
99+ }, nil
100+ }
101+ diskIOBps = int64 (ioBpsBytes )
102+ }
103+
85104 vcpus := 2
86105 if request .Body .Vcpus != nil {
87106 vcpus = * request .Body .Vcpus
@@ -98,6 +117,33 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
98117 networkEnabled = * request .Body .Network .Enabled
99118 }
100119
120+ // Parse network bandwidth limits (0 = auto)
121+ // Supports both bit-based (e.g., "1Gbps") and byte-based (e.g., "125MB/s") formats
122+ var networkBandwidthDownload int64
123+ var networkBandwidthUpload int64
124+ if request .Body .Network != nil {
125+ if request .Body .Network .BandwidthDownload != nil && * request .Body .Network .BandwidthDownload != "" {
126+ bw , err := resources .ParseBandwidth (* request .Body .Network .BandwidthDownload )
127+ if err != nil {
128+ return oapi.CreateInstance400JSONResponse {
129+ Code : "invalid_bandwidth_download" ,
130+ Message : fmt .Sprintf ("invalid bandwidth_download format: %v" , err ),
131+ }, nil
132+ }
133+ networkBandwidthDownload = bw
134+ }
135+ if request .Body .Network .BandwidthUpload != nil && * request .Body .Network .BandwidthUpload != "" {
136+ bw , err := resources .ParseBandwidth (* request .Body .Network .BandwidthUpload )
137+ if err != nil {
138+ return oapi.CreateInstance400JSONResponse {
139+ Code : "invalid_bandwidth_upload" ,
140+ Message : fmt .Sprintf ("invalid bandwidth_upload format: %v" , err ),
141+ }, nil
142+ }
143+ networkBandwidthUpload = bw
144+ }
145+ }
146+
101147 // Parse devices (GPU passthrough)
102148 var deviceRefs []string
103149 if request .Body .Devices != nil {
@@ -144,18 +190,36 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
144190 hvType = hypervisor .Type (* request .Body .Hypervisor )
145191 }
146192
193+ // Calculate default resource limits when not specified (0 = auto)
194+ // Uses proportional allocation based on CPU: (vcpus / cpuCapacity) * resourceCapacity
195+ if diskIOBps == 0 {
196+ diskIOBps , _ = s .ResourceManager .DefaultDiskIOBandwidth (vcpus )
197+ }
198+ if networkBandwidthDownload == 0 || networkBandwidthUpload == 0 {
199+ defaultDown , defaultUp := s .ResourceManager .DefaultNetworkBandwidth (vcpus )
200+ if networkBandwidthDownload == 0 {
201+ networkBandwidthDownload = defaultDown
202+ }
203+ if networkBandwidthUpload == 0 {
204+ networkBandwidthUpload = defaultUp
205+ }
206+ }
207+
147208 domainReq := instances.CreateInstanceRequest {
148- Name : request .Body .Name ,
149- Image : request .Body .Image ,
150- Size : size ,
151- HotplugSize : hotplugSize ,
152- OverlaySize : overlaySize ,
153- Vcpus : vcpus ,
154- Env : env ,
155- NetworkEnabled : networkEnabled ,
156- Devices : deviceRefs ,
157- Volumes : volumes ,
158- Hypervisor : hvType ,
209+ Name : request .Body .Name ,
210+ Image : request .Body .Image ,
211+ Size : size ,
212+ HotplugSize : hotplugSize ,
213+ OverlaySize : overlaySize ,
214+ Vcpus : vcpus ,
215+ DiskIOBps : diskIOBps ,
216+ NetworkBandwidthDownload : networkBandwidthDownload ,
217+ NetworkBandwidthUpload : networkBandwidthUpload ,
218+ Env : env ,
219+ NetworkEnabled : networkEnabled ,
220+ Devices : deviceRefs ,
221+ Volumes : volumes ,
222+ Hypervisor : hvType ,
159223 }
160224
161225 inst , err := s .InstanceManager .CreateInstance (ctx , domainReq )
@@ -539,14 +603,29 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance {
539603 hotplugSizeStr := datasize .ByteSize (inst .HotplugSize ).HR ()
540604 overlaySizeStr := datasize .ByteSize (inst .OverlaySize ).HR ()
541605
542- // Build network object with ip/mac nested inside
606+ // Format bandwidth as human-readable (bytes/s to rate string)
607+ var downloadBwStr , uploadBwStr * string
608+ if inst .NetworkBandwidthDownload > 0 {
609+ s := datasize .ByteSize (inst .NetworkBandwidthDownload ).HR () + "/s"
610+ downloadBwStr = & s
611+ }
612+ if inst .NetworkBandwidthUpload > 0 {
613+ s := datasize .ByteSize (inst .NetworkBandwidthUpload ).HR () + "/s"
614+ uploadBwStr = & s
615+ }
616+
617+ // Build network object with ip/mac and bandwidth nested inside
543618 netObj := & struct {
544- Enabled * bool `json:"enabled,omitempty"`
545- Ip * string `json:"ip"`
546- Mac * string `json:"mac"`
547- Name * string `json:"name,omitempty"`
619+ BandwidthDownload * string `json:"bandwidth_download,omitempty"`
620+ BandwidthUpload * string `json:"bandwidth_upload,omitempty"`
621+ Enabled * bool `json:"enabled,omitempty"`
622+ Ip * string `json:"ip"`
623+ Mac * string `json:"mac"`
624+ Name * string `json:"name,omitempty"`
548625 }{
549- Enabled : lo .ToPtr (inst .NetworkEnabled ),
626+ Enabled : lo .ToPtr (inst .NetworkEnabled ),
627+ BandwidthDownload : downloadBwStr ,
628+ BandwidthUpload : uploadBwStr ,
550629 }
551630 if inst .NetworkEnabled {
552631 netObj .Name = lo .ToPtr ("default" )
@@ -557,6 +636,13 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance {
557636 // Convert hypervisor type
558637 hvType := oapi .InstanceHypervisor (inst .HypervisorType )
559638
639+ // Format disk I/O as human-readable
640+ var diskIoBpsStr * string
641+ if inst .DiskIOBps > 0 {
642+ s := datasize .ByteSize (inst .DiskIOBps ).HR () + "/s"
643+ diskIoBpsStr = & s
644+ }
645+
560646 oapiInst := oapi.Instance {
561647 Id : inst .Id ,
562648 Name : inst .Name ,
@@ -567,6 +653,7 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance {
567653 HotplugSize : lo .ToPtr (hotplugSizeStr ),
568654 OverlaySize : lo .ToPtr (overlaySizeStr ),
569655 Vcpus : lo .ToPtr (inst .Vcpus ),
656+ DiskIoBps : diskIoBpsStr ,
570657 Network : netObj ,
571658 CreatedAt : inst .CreatedAt ,
572659 StartedAt : inst .StartedAt ,
0 commit comments