Skip to content
Open
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 89 additions & 14 deletions vminitd/Sources/vminitd/Server+GRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,20 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
}

return .init()
} catch let err as ContainerizationError {
log.error(
"createProcess",
metadata: [
"id": "\(request.id)",
"containerID": "\(request.containerID)",
"error": "\(err)",
])
switch err.code {
case .invalidArgument:
throw GRPCStatus(code: .invalidArgument, message: "createProcess: failed to create process: \(err)")
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we getting lower fidelity on these error codes? Since we are catching a ContainerizationError it's going to have the original code already as that actually happened. Throwing a new error causes us to lose a bit of information. Is there a reason to do this through this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are catching ContainerizationError to convert it to GRPCStatus. Without this conversion, all errors would become generic GRPC internal errors.

Copy link
Contributor Author

@dkovba dkovba Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems reasonable to implement toGRPCStatus in ContainerizationError to reduce the amount of code for error conversion and to convert all possible errors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see now. Many of the codes should map well from cz error to grpc status. You could do something like:

extension ContainerizationError {
    /// Convert the ContainerizationError into a GRPCStatus result.
    var status: GRPCStatus {
        let code: GRPCStatus.Code = {
            switch self.code {
            case .notFound: .notFound
            case .exists: .alreadyExists
            case .unsupported: .unimplemented
            case .unknown: .unknown
            case .internalError: .internalError
            case .interrupted: .unavailable
            case .invalidState: .failedPrecondition
            case .invalidArgument: .invalidArgument
            case .timeout: .deadlineExceeded
            default: .internalError
            }
        }()
        return GRPCStatus(
            code: code,
            message: self.description,
            cause: self
        )
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach loses the operation context compared to what I've already implemented. However, we can handle more error codes.

throw GRPCStatus(code: .internalError, message: "createProcess: failed to create process: \(err)")
}
} catch {
log.error(
"createProcess",
Expand All @@ -506,10 +520,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
"containerID": "\(request.containerID)",
"error": "\(error)",
])
if error is GRPCStatus {
throw error
}
throw GRPCStatus(code: .internalError, message: "createProcess: \(error)")
throw GRPCStatus(code: .internalError, message: "createProcess: failed to create process: \(error)")
}
}

Expand All @@ -532,10 +543,35 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
)
}

let ctr = try await self.state.get(container: request.containerID)
try await ctr.kill(execID: request.id, request.signal)
do {
let ctr = try await self.state.get(container: request.containerID)
try await ctr.kill(execID: request.id, request.signal)

return .init()
return .init()
} catch let err as ContainerizationError {
log.error(
"killProcess",
metadata: [
"id": "\(request.id)",
"containerID": "\(request.containerID)",
"error": "\(err)",
])
switch err.code {
case .notFound:
throw GRPCStatus(code: .notFound, message: "killProcess: failed to kill process: \(err)")
default:
throw GRPCStatus(code: .internalError, message: "killProcess: failed to kill process: \(err)")
}
} catch {
log.error(
"killProcess",
metadata: [
"id": "\(request.id)",
"containerID": "\(request.containerID)",
"error": "\(error)",
])
throw GRPCStatus(code: .internalError, message: "killProcess: failed to kill process: \(error)")
}
}

func deleteProcess(
Expand Down Expand Up @@ -568,6 +604,20 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
}

return .init()
} catch let err as ContainerizationError {
log.error(
"deleteProcess",
metadata: [
"id": "\(request.id)",
"containerID": "\(request.containerID)",
"error": "\(err)",
])
switch err.code {
case .notFound:
throw GRPCStatus(code: .notFound, message: "deleteProcess: failed to delete process: \(err)")
default:
throw GRPCStatus(code: .internalError, message: "deleteProcess: failed to delete process: \(err)")
}
} catch {
log.error(
"deleteProcess",
Expand All @@ -576,10 +626,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
"containerID": "\(request.containerID)",
"error": "\(error)",
])
throw GRPCStatus(
code: .internalError,
message: "deleteProcess: \(error)"
)
throw GRPCStatus(code: .internalError, message: "deleteProcess: failed to delete process: \(error)")
}
}

Expand Down Expand Up @@ -607,6 +654,20 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
return .with {
$0.pid = pid
}
} catch let err as ContainerizationError {
log.error(
"startProcess",
metadata: [
"id": "\(request.id)",
"containerID": "\(request.containerID)",
"error": "\(err)",
])
switch err.code {
case .notFound:
throw GRPCStatus(code: .notFound, message: "startProcess: failed to start process: \(err)")
default:
throw GRPCStatus(code: .internalError, message: "startProcess: failed to start process: \(err)")
}
} catch {
log.error(
"startProcess",
Expand Down Expand Up @@ -688,6 +749,20 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
$0.exitCode = exitStatus.exitStatus
$0.exitedAt = Google_Protobuf_Timestamp(date: exitStatus.exitedAt)
}
} catch let err as ContainerizationError {
log.error(
"waitProcess",
metadata: [
"id": "\(request.id)",
"containerID": "\(request.containerID)",
"error": "\(err)",
])
switch err.code {
case .notFound:
throw GRPCStatus(code: .notFound, message: "waitProcess: failed to wait on process: \(err)")
default:
throw GRPCStatus(code: .internalError, message: "waitProcess: failed to wait on process: \(err)")
}
} catch {
log.error(
"waitProcess",
Expand Down Expand Up @@ -788,7 +863,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
metadata: [
"error": "\(error)"
])
throw GRPCStatus(code: .internalError, message: "ip-addr-add: \(error)")
throw GRPCStatus(code: .internalError, message: "failed to set IP address on interface \(request.interface): \(error)")
}

return .init()
Expand Down Expand Up @@ -846,7 +921,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
metadata: [
"error": "\(error)"
])
throw GRPCStatus(code: .internalError, message: "ip-route-add-default: \(error)")
throw GRPCStatus(code: .internalError, message: "failed to set default gateway on interface \(request.interface): \(error)")
}

return .init()
Expand Down Expand Up @@ -887,7 +962,7 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid
metadata: [
"error": "\(error)"
])
throw GRPCStatus(code: .internalError, message: "configure-dns: \(error)")
throw GRPCStatus(code: .internalError, message: "failed to configure DNS at location \(request.location): \(error)")
}

return .init()
Expand Down