Skip to content

Commit ef9a515

Browse files
committed
plumbing for service apis + sandbox
1 parent 82ff4ab commit ef9a515

6 files changed

Lines changed: 125 additions & 0 deletions

File tree

Sources/APIServer/APIServer+Start.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ extension APIServer {
297297
routes[XPCRoute.containerBootstrap] = XPCServer.route(harness.bootstrap)
298298
routes[XPCRoute.containerDial] = XPCServer.route(harness.dial)
299299
routes[XPCRoute.containerStop] = XPCServer.route(harness.stop)
300+
routes[XPCRoute.containerFreeze] = XPCServer.route(harness.freeze)
301+
routes[XPCRoute.containerThaw] = XPCServer.route(harness.thaw)
300302
routes[XPCRoute.containerStartProcess] = XPCServer.route(harness.startProcess)
301303
routes[XPCRoute.containerCreateProcess] = XPCServer.route(harness.createProcess)
302304
routes[XPCRoute.containerResize] = XPCServer.route(harness.resize)

Sources/Services/ContainerAPIService/Client/ContainerClient.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,38 @@ public struct ContainerClient: Sendable {
193193
}
194194
}
195195

196+
/// Freeze writes on the container root filesystem.
197+
public func freeze(id: String) async throws {
198+
do {
199+
let request = XPCMessage(route: .containerFreeze)
200+
request.set(key: .id, value: id)
201+
202+
try await xpcClient.send(request)
203+
} catch {
204+
throw ContainerizationError(
205+
.internalError,
206+
message: "failed to freeze container",
207+
cause: error
208+
)
209+
}
210+
}
211+
212+
/// Thaw writes on the container root filesystem.
213+
public func thaw(id: String) async throws {
214+
do {
215+
let request = XPCMessage(route: .containerThaw)
216+
request.set(key: .id, value: id)
217+
218+
try await xpcClient.send(request)
219+
} catch {
220+
throw ContainerizationError(
221+
.internalError,
222+
message: "failed to thaw container",
223+
cause: error
224+
)
225+
}
226+
}
227+
196228
/// Delete the container along with any resources.
197229
public func delete(id: String, force: Bool = false) async throws {
198230
do {

Sources/Services/ContainerAPIService/Client/XPC+.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ public enum XPCRoute: String {
156156
case containerWait
157157
case containerDelete
158158
case containerStop
159+
case containerFreeze
160+
case containerThaw
159161
case containerDial
160162
case containerResize
161163
case containerKill

Sources/Services/ContainerAPIService/Server/Containers/ContainersHarness.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,32 @@ public struct ContainersHarness: Sendable {
7777
return message.reply()
7878
}
7979

80+
@Sendable
81+
public func freeze(_ message: XPCMessage) async throws -> XPCMessage {
82+
let id = message.string(key: .id)
83+
guard let id else {
84+
throw ContainerizationError(
85+
.invalidArgument,
86+
message: "id cannot be empty"
87+
)
88+
}
89+
try await service.freeze(id: id)
90+
return message.reply()
91+
}
92+
93+
@Sendable
94+
public func thaw(_ message: XPCMessage) async throws -> XPCMessage {
95+
let id = message.string(key: .id)
96+
guard let id else {
97+
throw ContainerizationError(
98+
.invalidArgument,
99+
message: "id cannot be empty"
100+
)
101+
}
102+
try await service.thaw(id: id)
103+
return message.reply()
104+
}
105+
80106
@Sendable
81107
public func dial(_ message: XPCMessage) async throws -> XPCMessage {
82108
let id = message.string(key: .id)

Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,68 @@ public actor ContainersService {
645645
try await handleContainerExit(id: id)
646646
}
647647

648+
/// Freeze writes on the root filesystem of a running container.
649+
public func freeze(id: String) async throws {
650+
log.debug(
651+
"ContainersService: enter",
652+
metadata: [
653+
"func": "\(#function)",
654+
"id": "\(id)",
655+
]
656+
)
657+
defer {
658+
log.debug(
659+
"ContainersService: exit",
660+
metadata: [
661+
"func": "\(#function)",
662+
"id": "\(id)",
663+
]
664+
)
665+
}
666+
667+
let state = try self._getContainerState(id: id)
668+
guard state.snapshot.status == .running else {
669+
throw ContainerizationError(
670+
.invalidState,
671+
message: "container \(id) is \(state.snapshot.status) and cannot be frozen"
672+
)
673+
}
674+
675+
let client = try state.getClient()
676+
try await client.filesystemOperation(operation: .freeze, path: "/")
677+
}
678+
679+
/// Thaw writes on the root filesystem of a running container.
680+
public func thaw(id: String) async throws {
681+
log.debug(
682+
"ContainersService: enter",
683+
metadata: [
684+
"func": "\(#function)",
685+
"id": "\(id)",
686+
]
687+
)
688+
defer {
689+
log.debug(
690+
"ContainersService: exit",
691+
metadata: [
692+
"func": "\(#function)",
693+
"id": "\(id)",
694+
]
695+
)
696+
}
697+
698+
let state = try self._getContainerState(id: id)
699+
guard state.snapshot.status == .running else {
700+
throw ContainerizationError(
701+
.invalidState,
702+
message: "container \(id) is \(state.snapshot.status) and cannot be thawed"
703+
)
704+
}
705+
706+
let client = try state.getClient()
707+
try await client.filesystemOperation(operation: .thaw, path: "/")
708+
}
709+
648710
public func dial(id: String, port: UInt32) async throws -> FileHandle {
649711
log.debug(
650712
"ContainersService: enter",

Sources/Services/Runtime/RuntimeClient/RuntimeClient.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ extension RuntimeClient {
327327
switch operation {
328328
case .freeze: "freeze"
329329
case .thaw: "thaw"
330+
case .trim: "trim"
330331
}
331332
}())
332333
request.set(key: RuntimeKeys.filesystemPath.rawValue, value: path)

0 commit comments

Comments
 (0)