@@ -21,6 +21,7 @@ import ContainerizationError
2121import ContainerizationExtras
2222import ContainerizationOCI
2323import ContainerizationOS
24+ import Darwin
2425import Foundation
2526
2627/// A parsed volume specification from user input
@@ -90,6 +91,12 @@ public struct Parser {
9091 public static func resources(
9192 cpus: Int64 ? ,
9293 memory: String ? ,
94+ blkioWeight: UInt16 ? = nil ,
95+ blkioWeightDevice: [ String ] = [ ] ,
96+ deviceReadBps: [ String ] = [ ] ,
97+ deviceWriteBps: [ String ] = [ ] ,
98+ deviceReadIops: [ String ] = [ ] ,
99+ deviceWriteIops: [ String ] = [ ] ,
93100 defaultCPUs: Int ,
94101 defaultMemory: MemorySize ,
95102 ) throws -> ContainerConfiguration . Resources {
@@ -105,9 +112,116 @@ public struct Parser {
105112 resource. memoryInBytes = try Parser . memoryStringAsMiB ( memory) . mib ( )
106113 }
107114
115+ resource. blockIO = try Parser . blockIO (
116+ weight: blkioWeight,
117+ weightDevice: blkioWeightDevice,
118+ deviceReadBps: deviceReadBps,
119+ deviceWriteBps: deviceWriteBps,
120+ deviceReadIops: deviceReadIops,
121+ deviceWriteIops: deviceWriteIops
122+ )
123+
108124 return resource
109125 }
110126
127+ public static func blockIO(
128+ weight: UInt16 ? ,
129+ weightDevice: [ String ] ,
130+ deviceReadBps: [ String ] ,
131+ deviceWriteBps: [ String ] ,
132+ deviceReadIops: [ String ] ,
133+ deviceWriteIops: [ String ]
134+ ) throws -> LinuxBlockIO ? {
135+ let hasBlockIO = weight != nil
136+ || !weightDevice. isEmpty
137+ || !deviceReadBps. isEmpty
138+ || !deviceWriteBps. isEmpty
139+ || !deviceReadIops. isEmpty
140+ || !deviceWriteIops. isEmpty
141+ guard hasBlockIO else { return nil }
142+
143+ if let weight {
144+ try validateBlockIOWeight ( weight)
145+ }
146+
147+ return try LinuxBlockIO (
148+ weight: weight,
149+ leafWeight: nil ,
150+ weightDevice: weightDevice. map {
151+ let parsed = try parseBlockIODeviceSpec ( $0)
152+ let weight = try parseUInt16 ( parsed. value, name: " --blkio-weight-device weight " )
153+ try validateBlockIOWeight ( weight)
154+ return LinuxWeightDevice ( major: parsed. device. major, minor: parsed. device. minor, weight: weight, leafWeight: nil )
155+ } ,
156+ throttleReadBpsDevice: deviceReadBps. map {
157+ let parsed = try parseBlockIODeviceSpec ( $0)
158+ return LinuxThrottleDevice ( major: parsed. device. major, minor: parsed. device. minor, rate: try parseByteRate ( parsed. value) )
159+ } ,
160+ throttleWriteBpsDevice: deviceWriteBps. map {
161+ let parsed = try parseBlockIODeviceSpec ( $0)
162+ return LinuxThrottleDevice ( major: parsed. device. major, minor: parsed. device. minor, rate: try parseByteRate ( parsed. value) )
163+ } ,
164+ throttleReadIOPSDevice: deviceReadIops. map {
165+ let parsed = try parseBlockIODeviceSpec ( $0)
166+ return LinuxThrottleDevice ( major: parsed. device. major, minor: parsed. device. minor, rate: try parseUInt64 ( parsed. value, name: " --device-read-iops rate " ) )
167+ } ,
168+ throttleWriteIOPSDevice: deviceWriteIops. map {
169+ let parsed = try parseBlockIODeviceSpec ( $0)
170+ return LinuxThrottleDevice ( major: parsed. device. major, minor: parsed. device. minor, rate: try parseUInt64 ( parsed. value, name: " --device-write-iops rate " ) )
171+ }
172+ )
173+ }
174+
175+ private static func parseBlockIODeviceSpec( _ value: String ) throws -> ( device: LinuxBlockIODevice , value: String ) {
176+ let parts = value. split ( separator: " : " , maxSplits: 1 , omittingEmptySubsequences: false )
177+ guard parts. count == 2 , !parts[ 0 ] . isEmpty, !parts[ 1 ] . isEmpty else {
178+ throw ContainerizationError ( . invalidArgument, message: " block I/O device spec must be '<path>:<value>' " )
179+ }
180+
181+ return try ( blockIODevice ( path: String ( parts [ 0 ] ) ) , String ( parts [ 1 ] ) )
182+ }
183+
184+ private static func blockIODevice( path: String ) throws -> LinuxBlockIODevice {
185+ var info = stat ( )
186+ guard stat ( path, & info) == 0 else {
187+ throw ContainerizationError ( . notFound, message: " block I/O device path not found: \( path) " )
188+ }
189+
190+ let rawDevice = UInt32 ( bitPattern: info. st_rdev)
191+ let major = Int64 ( ( rawDevice >> 24 ) & 0xff )
192+ let minor = Int64 ( rawDevice & 0x00ff_ffff )
193+ return LinuxBlockIODevice ( major: major, minor: minor)
194+ }
195+
196+ private static func parseByteRate( _ value: String ) throws -> UInt64 {
197+ let measurement = try Measurement . parse ( parsing: value)
198+ let bytes = measurement. converted ( to: . bytes) . value
199+ guard bytes >= 0 , bytes. rounded ( . down) == bytes else {
200+ throw ContainerizationError ( . invalidArgument, message: " block I/O byte rate must be a non-negative whole number of bytes " )
201+ }
202+ return UInt64 ( bytes)
203+ }
204+
205+ private static func parseUInt16( _ value: String , name: String ) throws -> UInt16 {
206+ guard let parsed = UInt16 ( value) else {
207+ throw ContainerizationError ( . invalidArgument, message: " \( name) must be an unsigned 16-bit integer " )
208+ }
209+ return parsed
210+ }
211+
212+ private static func parseUInt64( _ value: String , name: String ) throws -> UInt64 {
213+ guard let parsed = UInt64 ( value) else {
214+ throw ContainerizationError ( . invalidArgument, message: " \( name) must be an unsigned 64-bit integer " )
215+ }
216+ return parsed
217+ }
218+
219+ private static func validateBlockIOWeight( _ value: UInt16 ) throws {
220+ guard ( 10 ... 1000 ) . contains ( value) else {
221+ throw ContainerizationError ( . invalidArgument, message: " block I/O weight must be between 10 and 1000 " )
222+ }
223+ }
224+
111225 public static func allEnv( imageEnvs: [ String ] , envFiles: [ String ] , envs: [ String ] ) throws -> [ String ] {
112226 var combined : [ String ] = [ ]
113227 combined. append ( contentsOf: Parser . env ( envList: imageEnvs) )
0 commit comments