Merge pull request #6703 from mxpv/s

Sandbox API
This commit is contained in:
Derek McGowan 2022-04-18 20:55:06 -07:00 committed by GitHub
commit be60973a30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 12334 additions and 75 deletions

View File

@ -928,6 +928,13 @@ file {
} }
json_name: "extensions" json_name: "extensions"
} }
field {
name: "sandbox"
number: 11
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandbox"
}
nested_type { nested_type {
name: "LabelsEntry" name: "LabelsEntry"
field { field {
@ -2929,6 +2936,494 @@ file {
} }
syntax: "proto3" syntax: "proto3"
} }
file {
name: "github.com/containerd/containerd/api/types/sandbox.proto"
package: "containerd.types"
dependency: "gogoproto/gogo.proto"
dependency: "google/protobuf/any.proto"
dependency: "google/protobuf/timestamp.proto"
message_type {
name: "Sandbox"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
field {
name: "runtime"
number: 2
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox.Runtime"
options {
65001: 0
}
json_name: "runtime"
}
field {
name: "spec"
number: 3
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Any"
json_name: "spec"
}
field {
name: "labels"
number: 4
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox.LabelsEntry"
options {
65001: 0
}
json_name: "labels"
}
field {
name: "created_at"
number: 5
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Timestamp"
options {
65010: 1
65001: 0
}
json_name: "createdAt"
}
field {
name: "updated_at"
number: 6
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Timestamp"
options {
65010: 1
65001: 0
}
json_name: "updatedAt"
}
field {
name: "extensions"
number: 7
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox.ExtensionsEntry"
options {
65001: 0
}
json_name: "extensions"
}
nested_type {
name: "Runtime"
field {
name: "name"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "name"
}
field {
name: "options"
number: 2
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Any"
json_name: "options"
}
}
nested_type {
name: "LabelsEntry"
field {
name: "key"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "key"
}
field {
name: "value"
number: 2
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "value"
}
options {
map_entry: true
}
}
nested_type {
name: "ExtensionsEntry"
field {
name: "key"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "key"
}
field {
name: "value"
number: 2
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Any"
json_name: "value"
}
options {
map_entry: true
}
}
}
options {
go_package: "github.com/containerd/containerd/api/types;types"
}
weak_dependency: 0
syntax: "proto3"
}
file {
name: "github.com/containerd/containerd/api/services/sandbox/v1/sandbox.proto"
package: "containerd.services.sandbox.v1"
dependency: "google/protobuf/any.proto"
dependency: "google/protobuf/timestamp.proto"
dependency: "gogoproto/gogo.proto"
dependency: "github.com/containerd/containerd/api/types/sandbox.proto"
dependency: "github.com/containerd/containerd/api/types/mount.proto"
message_type {
name: "StoreCreateRequest"
field {
name: "sandbox"
number: 1
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox"
options {
65001: 0
}
json_name: "sandbox"
}
}
message_type {
name: "StoreCreateResponse"
field {
name: "sandbox"
number: 1
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox"
options {
65001: 0
}
json_name: "sandbox"
}
}
message_type {
name: "StoreUpdateRequest"
field {
name: "sandbox"
number: 1
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox"
options {
65001: 0
}
json_name: "sandbox"
}
field {
name: "fields"
number: 2
label: LABEL_REPEATED
type: TYPE_STRING
json_name: "fields"
}
}
message_type {
name: "StoreUpdateResponse"
field {
name: "sandbox"
number: 1
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox"
options {
65001: 0
}
json_name: "sandbox"
}
}
message_type {
name: "StoreDeleteRequest"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
}
message_type {
name: "StoreDeleteResponse"
}
message_type {
name: "StoreListRequest"
field {
name: "filters"
number: 1
label: LABEL_REPEATED
type: TYPE_STRING
json_name: "filters"
}
}
message_type {
name: "StoreListResponse"
field {
name: "list"
number: 1
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox"
options {
65001: 0
}
json_name: "list"
}
}
message_type {
name: "StoreGetRequest"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
}
message_type {
name: "StoreGetResponse"
field {
name: "sandbox"
number: 1
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".containerd.types.Sandbox"
json_name: "sandbox"
}
}
message_type {
name: "ControllerStartRequest"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
field {
name: "rootfs"
number: 2
label: LABEL_REPEATED
type: TYPE_MESSAGE
type_name: ".containerd.types.Mount"
json_name: "rootfs"
}
field {
name: "options"
number: 3
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Any"
json_name: "options"
}
}
message_type {
name: "ControllerStartResponse"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
field {
name: "pid"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "pid"
}
}
message_type {
name: "ControllerShutdownRequest"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
field {
name: "timeout_secs"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "timeoutSecs"
}
}
message_type {
name: "ControllerShutdownResponse"
}
message_type {
name: "ControllerWaitRequest"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
}
message_type {
name: "ControllerWaitResponse"
field {
name: "exit_status"
number: 1
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "exitStatus"
}
field {
name: "exited_at"
number: 2
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Timestamp"
options {
65010: 1
65001: 0
}
json_name: "exitedAt"
}
}
message_type {
name: "ControllerStatusRequest"
field {
name: "sandbox_id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "sandboxId"
}
}
message_type {
name: "ControllerStatusResponse"
field {
name: "id"
number: 1
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "id"
}
field {
name: "pid"
number: 2
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "pid"
}
field {
name: "state"
number: 3
label: LABEL_OPTIONAL
type: TYPE_STRING
json_name: "state"
}
field {
name: "exit_status"
number: 4
label: LABEL_OPTIONAL
type: TYPE_UINT32
json_name: "exitStatus"
}
field {
name: "exited_at"
number: 5
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Timestamp"
options {
65010: 1
65001: 0
}
json_name: "exitedAt"
}
field {
name: "extra"
number: 6
label: LABEL_OPTIONAL
type: TYPE_MESSAGE
type_name: ".google.protobuf.Any"
json_name: "extra"
}
}
service {
name: "Store"
method {
name: "Create"
input_type: ".containerd.services.sandbox.v1.StoreCreateRequest"
output_type: ".containerd.services.sandbox.v1.StoreCreateResponse"
}
method {
name: "Update"
input_type: ".containerd.services.sandbox.v1.StoreUpdateRequest"
output_type: ".containerd.services.sandbox.v1.StoreUpdateResponse"
}
method {
name: "Delete"
input_type: ".containerd.services.sandbox.v1.StoreDeleteRequest"
output_type: ".containerd.services.sandbox.v1.StoreDeleteResponse"
}
method {
name: "List"
input_type: ".containerd.services.sandbox.v1.StoreListRequest"
output_type: ".containerd.services.sandbox.v1.StoreListResponse"
}
method {
name: "Get"
input_type: ".containerd.services.sandbox.v1.StoreGetRequest"
output_type: ".containerd.services.sandbox.v1.StoreGetResponse"
}
}
service {
name: "Controller"
method {
name: "Start"
input_type: ".containerd.services.sandbox.v1.ControllerStartRequest"
output_type: ".containerd.services.sandbox.v1.ControllerStartResponse"
}
method {
name: "Shutdown"
input_type: ".containerd.services.sandbox.v1.ControllerShutdownRequest"
output_type: ".containerd.services.sandbox.v1.ControllerShutdownResponse"
}
method {
name: "Wait"
input_type: ".containerd.services.sandbox.v1.ControllerWaitRequest"
output_type: ".containerd.services.sandbox.v1.ControllerWaitResponse"
}
method {
name: "Status"
input_type: ".containerd.services.sandbox.v1.ControllerStatusRequest"
output_type: ".containerd.services.sandbox.v1.ControllerStatusResponse"
}
}
options {
go_package: "github.com/containerd/containerd/api/services/sandbox/v1;sandbox"
}
weak_dependency: 2
syntax: "proto3"
}
file { file {
name: "github.com/containerd/containerd/api/services/snapshots/v1/snapshots.proto" name: "github.com/containerd/containerd/api/services/snapshots/v1/snapshots.proto"
package: "containerd.services.snapshots.v1" package: "containerd.services.snapshots.v1"

View File

@ -82,6 +82,8 @@ type Container struct {
// data, one should only update the specified extension using field paths // data, one should only update the specified extension using field paths
// to select a specific map key. // to select a specific map key.
Extensions map[string]types.Any `protobuf:"bytes,10,rep,name=extensions,proto3" json:"extensions" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Extensions map[string]types.Any `protobuf:"bytes,10,rep,name=extensions,proto3" json:"extensions" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
// Sandbox ID this container belongs to.
Sandbox string `protobuf:"bytes,11,opt,name=sandbox,proto3" json:"sandbox,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -594,58 +596,59 @@ func init() {
} }
var fileDescriptor_311afb8e15951042 = []byte{ var fileDescriptor_311afb8e15951042 = []byte{
// 815 bytes of a gzipped FileDescriptorProto // 832 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcb, 0x72, 0x12, 0x4d, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xcb, 0x72, 0x1a, 0x47,
0x14, 0xce, 0x00, 0x19, 0xc2, 0xe1, 0xaf, 0xfa, 0xff, 0xea, 0x1f, 0x71, 0x1c, 0xab, 0x80, 0xb0, 0x14, 0xd5, 0x00, 0x1a, 0xc4, 0x25, 0x55, 0x49, 0x75, 0x08, 0x99, 0x4c, 0xaa, 0x00, 0xb1, 0xa2,
0xa2, 0x2c, 0x1d, 0x12, 0xb4, 0xcc, 0xcd, 0x4d, 0xc8, 0xad, 0xd4, 0xc4, 0x4a, 0x4d, 0x74, 0xa3, 0x52, 0xc9, 0x20, 0x91, 0x54, 0xf4, 0xca, 0x46, 0xe8, 0x55, 0x49, 0xa4, 0x94, 0x6a, 0x94, 0x6c,
0x8b, 0x38, 0x40, 0x87, 0x8c, 0xcc, 0xcd, 0xe9, 0x86, 0x92, 0x72, 0x11, 0x7d, 0x03, 0x77, 0x3e, 0xe2, 0x85, 0x3c, 0x40, 0x0b, 0x8d, 0x99, 0x97, 0xa7, 0x1b, 0x4a, 0x94, 0x17, 0xb2, 0xff, 0xc0,
0x82, 0xaf, 0x92, 0xa5, 0x4b, 0x57, 0xd1, 0x50, 0x3e, 0x88, 0x35, 0x3d, 0x3d, 0xcc, 0x84, 0x8b, 0x7f, 0xe1, 0xb5, 0xff, 0x42, 0x4b, 0x2f, 0xbd, 0x92, 0x2d, 0xca, 0x1f, 0xe2, 0xea, 0x9e, 0x1e,
0x42, 0x62, 0x76, 0x7d, 0xe8, 0xf3, 0x9d, 0xf3, 0xf5, 0x77, 0xfa, 0x6b, 0x06, 0x76, 0x9b, 0x3a, 0x66, 0xc4, 0xc3, 0x06, 0xc9, 0xda, 0xf5, 0xa5, 0xef, 0xb9, 0xf7, 0xf4, 0xb9, 0x7d, 0x7a, 0x80,
0x3d, 0x6e, 0xd7, 0x94, 0xba, 0x6d, 0x96, 0xeb, 0xb6, 0x45, 0x35, 0xdd, 0xc2, 0x6e, 0x23, 0xba, 0xc3, 0xb6, 0x49, 0xcf, 0xbb, 0x0d, 0xad, 0xe9, 0xda, 0xd5, 0xa6, 0xeb, 0x50, 0xc3, 0x74, 0xb0,
0xd4, 0x1c, 0xbd, 0x4c, 0xb0, 0xdb, 0xd1, 0xeb, 0x98, 0x84, 0xbf, 0x93, 0x72, 0x67, 0x31, 0x12, 0xdf, 0x8a, 0x2f, 0x0d, 0xcf, 0xac, 0x12, 0xec, 0xf7, 0xcc, 0x26, 0x26, 0xd1, 0xef, 0xa4, 0xda,
0x29, 0x8e, 0x6b, 0x53, 0x1b, 0xcd, 0x87, 0x38, 0x25, 0xc0, 0x28, 0x91, 0xac, 0xce, 0xa2, 0x9c, 0x5b, 0x8d, 0x45, 0x9a, 0xe7, 0xbb, 0xd4, 0x45, 0xcb, 0x11, 0x4e, 0x0b, 0x31, 0x5a, 0x2c, 0xab,
0x69, 0xda, 0x4d, 0x9b, 0x65, 0x97, 0xbd, 0x95, 0x0f, 0x94, 0x6f, 0x35, 0x6d, 0xbb, 0x69, 0xe0, 0xb7, 0xaa, 0xe6, 0xda, 0x6e, 0xdb, 0xe5, 0xd9, 0x55, 0xb6, 0x0a, 0x80, 0xea, 0x0f, 0x6d, 0xd7,
0x32, 0x8b, 0x6a, 0xed, 0xa3, 0xb2, 0x66, 0x75, 0xf9, 0xd6, 0xed, 0xc1, 0x2d, 0x6c, 0x3a, 0x34, 0x6d, 0x5b, 0xb8, 0xca, 0xa3, 0x46, 0xf7, 0xac, 0x6a, 0x38, 0x7d, 0xb1, 0xf5, 0xe3, 0xe8, 0x16,
0xd8, 0x2c, 0x0c, 0x6e, 0x1e, 0xe9, 0xd8, 0x68, 0x1c, 0x9a, 0x1a, 0x69, 0xf1, 0x8c, 0xfc, 0x60, 0xb6, 0x3d, 0x1a, 0x6e, 0x96, 0x46, 0x37, 0xcf, 0x4c, 0x6c, 0xb5, 0x4e, 0x6d, 0x83, 0x74, 0x44,
0x06, 0xd5, 0x4d, 0x4c, 0xa8, 0x66, 0x3a, 0x7e, 0x42, 0xf1, 0xb3, 0x08, 0xa9, 0x8d, 0x80, 0x22, 0x46, 0x71, 0x34, 0x83, 0x9a, 0x36, 0x26, 0xd4, 0xb0, 0xbd, 0x20, 0xa1, 0xfc, 0x5a, 0x86, 0xcc,
0xca, 0x42, 0x4c, 0x6f, 0x48, 0x42, 0x41, 0x28, 0xa5, 0xaa, 0x62, 0xef, 0x2c, 0x1f, 0x7b, 0xbc, 0x4e, 0x48, 0x11, 0xe5, 0x21, 0x61, 0xb6, 0x14, 0xa9, 0x24, 0x55, 0x32, 0x75, 0x79, 0x70, 0x5d,
0xa9, 0xc6, 0xf4, 0x06, 0xda, 0x07, 0xd1, 0xd0, 0x6a, 0xd8, 0x20, 0x52, 0xac, 0x10, 0x2f, 0xa5, 0x4c, 0xfc, 0xb9, 0xab, 0x27, 0xcc, 0x16, 0x3a, 0x06, 0xd9, 0x32, 0x1a, 0xd8, 0x22, 0x4a, 0xa2,
0x2b, 0xcb, 0xca, 0x1f, 0x8f, 0xaa, 0xf4, 0xab, 0x2a, 0xbb, 0x0c, 0xba, 0x65, 0x51, 0xb7, 0xab, 0x94, 0xac, 0x64, 0x6b, 0xeb, 0xda, 0x67, 0x8f, 0xaa, 0x0d, 0xab, 0x6a, 0x87, 0x1c, 0xba, 0xe7,
0xf2, 0x3a, 0x28, 0x03, 0xb3, 0xba, 0xa9, 0x35, 0xb1, 0x14, 0xf7, 0x9a, 0xa9, 0x7e, 0x80, 0x9e, 0x50, 0xbf, 0xaf, 0x8b, 0x3a, 0x28, 0x07, 0x8b, 0xa6, 0x6d, 0xb4, 0xb1, 0x92, 0x64, 0xcd, 0xf4,
0x41, 0xd2, 0x6d, 0x5b, 0x1e, 0x47, 0x29, 0x51, 0x10, 0x4a, 0xe9, 0xca, 0x83, 0xa9, 0x1a, 0xa9, 0x20, 0x40, 0xff, 0x40, 0xda, 0xef, 0x3a, 0x8c, 0xa3, 0x92, 0x2a, 0x49, 0x95, 0x6c, 0xed, 0xb7,
0x3e, 0x56, 0x0d, 0x8a, 0xa0, 0x12, 0x24, 0x88, 0x83, 0xeb, 0xd2, 0x2c, 0x2b, 0x96, 0x51, 0x7c, 0xb9, 0x1a, 0xe9, 0x01, 0x56, 0x0f, 0x8b, 0xa0, 0x0a, 0xa4, 0x88, 0x87, 0x9b, 0xca, 0x22, 0x2f,
0x35, 0x94, 0x40, 0x0d, 0x65, 0xdd, 0xea, 0xaa, 0x2c, 0x03, 0x15, 0x20, 0x4d, 0x2c, 0xcd, 0x21, 0x96, 0xd3, 0x02, 0x35, 0xb4, 0x50, 0x0d, 0x6d, 0xdb, 0xe9, 0xeb, 0x3c, 0x03, 0x95, 0x20, 0x4b,
0xc7, 0x36, 0xa5, 0xd8, 0x95, 0x44, 0xc6, 0x2a, 0xfa, 0x13, 0x9a, 0x87, 0x7f, 0x82, 0xf0, 0xb0, 0x1c, 0xc3, 0x23, 0xe7, 0x2e, 0xa5, 0xd8, 0x57, 0x64, 0xce, 0x2a, 0xfe, 0x13, 0x5a, 0x86, 0xaf,
0x85, 0xbb, 0x52, 0xf2, 0x62, 0xca, 0x53, 0xdc, 0x45, 0x1b, 0x00, 0x75, 0x17, 0x6b, 0x14, 0x37, 0xc2, 0xf0, 0xb4, 0x83, 0xfb, 0x4a, 0xfa, 0x76, 0xca, 0xdf, 0xb8, 0x8f, 0x76, 0x00, 0x9a, 0x3e,
0x0e, 0x35, 0x2a, 0xcd, 0xb1, 0xa6, 0xf2, 0x50, 0xd3, 0xe7, 0xc1, 0x08, 0xaa, 0x73, 0xa7, 0x67, 0x36, 0x28, 0x6e, 0x9d, 0x1a, 0x54, 0x59, 0xe2, 0x4d, 0xd5, 0xb1, 0xa6, 0xff, 0x86, 0x23, 0xa8,
0xf9, 0x99, 0x4f, 0xdf, 0xf3, 0x82, 0x9a, 0xe2, 0xb8, 0x75, 0xea, 0x15, 0x69, 0x3b, 0x8d, 0xa0, 0x2f, 0x5d, 0x5d, 0x17, 0x17, 0x5e, 0xbe, 0x2b, 0x4a, 0x7a, 0x46, 0xe0, 0xb6, 0x29, 0x2b, 0xd2,
0x48, 0x6a, 0x9a, 0x22, 0x1c, 0xb7, 0x4e, 0x51, 0x0d, 0x00, 0xbf, 0xa3, 0xd8, 0x22, 0xba, 0x6d, 0xf5, 0x5a, 0x61, 0x91, 0xcc, 0x3c, 0x45, 0x04, 0x6e, 0x9b, 0xa2, 0x06, 0x00, 0xbe, 0xa0, 0xd8,
0x11, 0x09, 0xd8, 0xd0, 0x1e, 0x4d, 0xa5, 0xe5, 0x56, 0x1f, 0xce, 0x06, 0x57, 0x4d, 0x78, 0x6d, 0x21, 0xa6, 0xeb, 0x10, 0x05, 0xf8, 0xd0, 0xfe, 0x98, 0x4b, 0xcb, 0xbd, 0x21, 0x9c, 0x0f, 0xae,
0xd4, 0x48, 0x55, 0x79, 0x05, 0xd2, 0x91, 0xc9, 0xa2, 0xff, 0x20, 0xee, 0xc9, 0xc2, 0x2e, 0x8f, 0x9e, 0x62, 0x6d, 0xf4, 0x58, 0x55, 0xa4, 0x40, 0x9a, 0x18, 0x4e, 0xab, 0xe1, 0x5e, 0x28, 0x59,
0xea, 0x2d, 0xbd, 0x19, 0x77, 0x34, 0xa3, 0x8d, 0xa5, 0x98, 0x3f, 0x63, 0x16, 0xac, 0xc6, 0x96, 0xae, 0x45, 0x18, 0xaa, 0x1b, 0x90, 0x8d, 0xcd, 0x1c, 0x7d, 0x03, 0x49, 0x26, 0x18, 0xbf, 0x56,
0x05, 0x79, 0x0f, 0x92, 0x7c, 0x56, 0x08, 0x41, 0xc2, 0xd2, 0x4c, 0xcc, 0x71, 0x6c, 0x8d, 0x14, 0x3a, 0x5b, 0xb2, 0xe9, 0xf7, 0x0c, 0xab, 0x8b, 0x95, 0x44, 0x30, 0x7d, 0x1e, 0x6c, 0x26, 0xd6,
0x48, 0xda, 0x0e, 0x65, 0xd4, 0x63, 0xbf, 0x99, 0x5c, 0x90, 0x24, 0x1f, 0xc0, 0xbf, 0x03, 0x74, 0x25, 0xf5, 0x08, 0xd2, 0x62, 0x8a, 0x08, 0x41, 0xca, 0x31, 0x6c, 0x2c, 0x70, 0x7c, 0x8d, 0x34,
0x47, 0xb0, 0xb9, 0x13, 0x65, 0x33, 0xae, 0x64, 0xc8, 0xb1, 0x78, 0x0f, 0xfe, 0xdf, 0xc1, 0xb4, 0x48, 0xbb, 0x1e, 0xe5, 0x87, 0x4a, 0x7c, 0x62, 0xa6, 0x61, 0x92, 0x7a, 0x02, 0x5f, 0x8f, 0x1c,
0x2f, 0x88, 0x8a, 0xdf, 0xb6, 0x31, 0xa1, 0xe3, 0x2c, 0x52, 0x3c, 0x86, 0xcc, 0xc5, 0x74, 0xe2, 0x64, 0x02, 0x9b, 0x9f, 0xe2, 0x6c, 0xa6, 0x95, 0x8c, 0x38, 0x96, 0x7f, 0x81, 0x6f, 0x0f, 0x30,
0xd8, 0x16, 0xc1, 0x68, 0x1f, 0x52, 0x7d, 0x89, 0x19, 0x2c, 0x5d, 0xb9, 0x3b, 0xcd, 0x20, 0xb8, 0x1d, 0x4a, 0xa5, 0xe3, 0xa7, 0x5d, 0x4c, 0xe8, 0x34, 0xf3, 0x94, 0xcf, 0x21, 0x77, 0x3b, 0x9d,
0xf0, 0x61, 0x91, 0xe2, 0x22, 0xdc, 0xd8, 0xd5, 0x49, 0xd8, 0x8a, 0x04, 0xd4, 0x24, 0x48, 0x1e, 0x78, 0xae, 0x43, 0x30, 0x3a, 0x86, 0xcc, 0x50, 0x7c, 0x0e, 0xcb, 0xd6, 0x7e, 0x9e, 0x67, 0x44,
0xe9, 0x06, 0xc5, 0x2e, 0x91, 0x84, 0x42, 0xbc, 0x94, 0x52, 0x83, 0xb0, 0x68, 0x40, 0x76, 0x10, 0x62, 0x24, 0x51, 0x91, 0xf2, 0x2a, 0x7c, 0x77, 0x68, 0x92, 0xa8, 0x15, 0x09, 0xa9, 0x29, 0x90,
0xc2, 0xe9, 0xa9, 0x00, 0x61, 0x63, 0x06, 0xbb, 0x1c, 0xbf, 0x48, 0x95, 0xe2, 0x1b, 0xc8, 0x6e, 0x3e, 0x33, 0x2d, 0x8a, 0x7d, 0xa2, 0x48, 0xa5, 0x24, 0x1b, 0x95, 0x08, 0xcb, 0x16, 0xe4, 0x47,
0xb0, 0xeb, 0x3c, 0x24, 0xde, 0xdf, 0x17, 0xa3, 0x05, 0x37, 0x87, 0x7a, 0x5d, 0x9b, 0xf2, 0x5f, 0x21, 0x82, 0x9e, 0x0e, 0x10, 0x35, 0xe6, 0xb0, 0xbb, 0xf1, 0x8b, 0x55, 0x29, 0x3f, 0x81, 0xfc,
0x04, 0xc8, 0xbe, 0x60, 0x1e, 0xbb, 0xfe, 0x93, 0xa1, 0x35, 0x48, 0xfb, 0x7e, 0x66, 0xef, 0x39, 0x0e, 0xbf, 0xe8, 0x63, 0xe2, 0x7d, 0x79, 0x31, 0x3a, 0xf0, 0xfd, 0x58, 0xaf, 0x07, 0x53, 0xfe,
0xbf, 0xb5, 0xc3, 0x0f, 0xc1, 0xb6, 0xf7, 0xe4, 0xef, 0x69, 0xa4, 0xa5, 0xf2, 0x67, 0xc3, 0x5b, 0x95, 0x04, 0xf9, 0xff, 0xb8, 0xfb, 0x1e, 0xfe, 0x64, 0x68, 0x0b, 0xb2, 0x81, 0xd3, 0xf9, 0x4b,
0x7b, 0xb2, 0x0c, 0x11, 0xbd, 0x36, 0x59, 0x16, 0x20, 0xbb, 0x89, 0x0d, 0x3c, 0x42, 0x95, 0x71, 0x2f, 0x6e, 0xed, 0xf8, 0x13, 0xb1, 0xcf, 0x3e, 0x06, 0x47, 0x06, 0xe9, 0xe8, 0xe2, 0x41, 0x61,
0x66, 0xa9, 0x41, 0xe6, 0xc2, 0x7d, 0xdc, 0xc3, 0x84, 0x78, 0xef, 0xff, 0x93, 0x2b, 0x72, 0x8b, 0x6b, 0x26, 0xcb, 0x18, 0xd1, 0x07, 0x93, 0x65, 0x05, 0xf2, 0xbb, 0xd8, 0xc2, 0x13, 0x54, 0x99,
0xb0, 0xaa, 0xfc, 0x9c, 0x05, 0x08, 0x2f, 0x3c, 0xea, 0x40, 0x7c, 0x07, 0x53, 0xf4, 0x70, 0x82, 0x66, 0x96, 0x06, 0xe4, 0x6e, 0xdd, 0xc7, 0x23, 0x4c, 0x08, 0xfb, 0x32, 0xfc, 0x75, 0x4f, 0x6e,
0x72, 0x23, 0x6c, 0x2f, 0x2f, 0x4d, 0x8d, 0xe3, 0x72, 0xbf, 0x87, 0x84, 0x77, 0x54, 0x34, 0xc9, 0x31, 0x56, 0xb5, 0x0f, 0x8b, 0x00, 0xd1, 0x85, 0x47, 0x3d, 0x48, 0x1e, 0x60, 0x8a, 0x7e, 0x9f,
0x5f, 0xe6, 0x48, 0x5b, 0xcb, 0x2b, 0x97, 0x40, 0xf2, 0xe6, 0x1f, 0x05, 0x00, 0x6f, 0xeb, 0x80, 0xa1, 0xdc, 0x04, 0xdb, 0xab, 0x6b, 0x73, 0xe3, 0x84, 0xdc, 0xcf, 0x20, 0xc5, 0x8e, 0x8a, 0x66,
0xba, 0x58, 0x33, 0xaf, 0xc0, 0x61, 0x69, 0x5a, 0x24, 0x9f, 0xe8, 0x82, 0x80, 0x4e, 0x40, 0xf4, 0xf9, 0x98, 0x4e, 0xb4, 0xb5, 0xba, 0x71, 0x07, 0xa4, 0x68, 0xfe, 0x42, 0x02, 0x60, 0x5b, 0x27,
0x1d, 0x8a, 0x26, 0x39, 0xc8, 0xe8, 0x87, 0x43, 0x5e, 0xbd, 0x0c, 0x94, 0x8b, 0x70, 0x02, 0xa2, 0xd4, 0xc7, 0x86, 0x7d, 0x0f, 0x0e, 0x6b, 0xf3, 0x22, 0xc5, 0x44, 0x57, 0x24, 0x74, 0x09, 0x72,
0xef, 0x85, 0x89, 0x08, 0x8c, 0xf6, 0xf7, 0x44, 0x04, 0xc6, 0x39, 0xee, 0x15, 0x88, 0xbe, 0x3f, 0xe0, 0x50, 0x34, 0xcb, 0x41, 0x26, 0x3f, 0x1c, 0xea, 0xe6, 0x5d, 0xa0, 0x42, 0x84, 0x4b, 0x90,
0x26, 0x22, 0x30, 0xda, 0x4a, 0x72, 0x76, 0xc8, 0xf9, 0x5b, 0xde, 0x97, 0x60, 0xf5, 0xf5, 0xe9, 0x03, 0x2f, 0xcc, 0x44, 0x60, 0xb2, 0xbf, 0x67, 0x22, 0x30, 0xcd, 0x71, 0x8f, 0x40, 0x0e, 0xfc,
0x79, 0x6e, 0xe6, 0xdb, 0x79, 0x6e, 0xe6, 0x43, 0x2f, 0x27, 0x9c, 0xf6, 0x72, 0xc2, 0xd7, 0x5e, 0x31, 0x13, 0x81, 0xc9, 0x56, 0x52, 0xf3, 0x63, 0xce, 0xdf, 0x63, 0xff, 0x11, 0xeb, 0x8f, 0xaf,
0x4e, 0xf8, 0xd1, 0xcb, 0x09, 0x2f, 0xb7, 0xaf, 0xf0, 0x71, 0xbb, 0x16, 0x46, 0x35, 0x91, 0x75, 0x6e, 0x0a, 0x0b, 0x6f, 0x6f, 0x0a, 0x0b, 0xcf, 0x07, 0x05, 0xe9, 0x6a, 0x50, 0x90, 0xde, 0x0c,
0xbc, 0xff, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x77, 0xc8, 0x5c, 0x78, 0x2d, 0x0b, 0x00, 0x00, 0x0a, 0xd2, 0xfb, 0x41, 0x41, 0xfa, 0x7f, 0xff, 0x1e, 0x7f, 0x7b, 0xb7, 0xa2, 0xa8, 0x21, 0xf3,
0x8e, 0xbf, 0x7e, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xef, 0xe7, 0xe1, 0xb7, 0x47, 0x0b, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
@ -960,6 +963,13 @@ func (m *Container) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized) i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized)
} }
if len(m.Sandbox) > 0 {
i -= len(m.Sandbox)
copy(dAtA[i:], m.Sandbox)
i = encodeVarintContainers(dAtA, i, uint64(len(m.Sandbox)))
i--
dAtA[i] = 0x5a
}
if len(m.Extensions) > 0 { if len(m.Extensions) > 0 {
for k := range m.Extensions { for k := range m.Extensions {
v := m.Extensions[k] v := m.Extensions[k]
@ -1563,6 +1573,10 @@ func (m *Container) Size() (n int) {
n += mapEntrySize + 1 + sovContainers(uint64(mapEntrySize)) n += mapEntrySize + 1 + sovContainers(uint64(mapEntrySize))
} }
} }
l = len(m.Sandbox)
if l > 0 {
n += 1 + l + sovContainers(uint64(l))
}
if m.XXX_unrecognized != nil { if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized) n += len(m.XXX_unrecognized)
} }
@ -1788,6 +1802,7 @@ func (this *Container) String() string {
`CreatedAt:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CreatedAt), "Timestamp", "types.Timestamp", 1), `&`, ``, 1) + `,`, `CreatedAt:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.CreatedAt), "Timestamp", "types.Timestamp", 1), `&`, ``, 1) + `,`,
`UpdatedAt:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.UpdatedAt), "Timestamp", "types.Timestamp", 1), `&`, ``, 1) + `,`, `UpdatedAt:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.UpdatedAt), "Timestamp", "types.Timestamp", 1), `&`, ``, 1) + `,`,
`Extensions:` + mapStringForExtensions + `,`, `Extensions:` + mapStringForExtensions + `,`,
`Sandbox:` + fmt.Sprintf("%v", this.Sandbox) + `,`,
`XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`,
`}`, `}`,
}, "") }, "")
@ -2480,6 +2495,38 @@ func (m *Container) Unmarshal(dAtA []byte) error {
} }
m.Extensions[mapkey] = *mapvalue m.Extensions[mapkey] = *mapvalue
iNdEx = postIndex iNdEx = postIndex
case 11:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Sandbox", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowContainers
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthContainers
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthContainers
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Sandbox = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipContainers(dAtA[iNdEx:]) skippy, err := skipContainers(dAtA[iNdEx:])

View File

@ -114,6 +114,9 @@ message Container {
// data, one should only update the specified extension using field paths // data, one should only update the specified extension using field paths
// to select a specific map key. // to select a specific map key.
map<string, google.protobuf.Any> extensions = 10 [(gogoproto.nullable) = false]; map<string, google.protobuf.Any> extensions = 10 [(gogoproto.nullable) = false];
// Sandbox ID this container belongs to.
string sandbox = 11;
} }
message GetContainerRequest { message GetContainerRequest {

View File

@ -0,0 +1,17 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,133 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
// Sandbox is a v2 runtime extension that allows more complex execution environments for containers.
// This adds a notion of groups of containers that share same lifecycle and/or resources.
// A few good fits for sandbox can be:
// - A "pause" container in k8s, that acts as a parent process for child containers to hold network namespace.
// - (micro)VMs that launch a VM process and executes containers inside guest OS.
// containerd in this case remains implementation agnostic and delegates sandbox handling to runtimes.
// See proposal and discussion here: https://github.com/containerd/containerd/issues/4131
package containerd.services.sandbox.v1;
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import weak "gogoproto/gogo.proto";
import "github.com/containerd/containerd/api/types/sandbox.proto";
import "github.com/containerd/containerd/api/types/mount.proto";
option go_package = "github.com/containerd/containerd/api/services/sandbox/v1;sandbox";
// Store provides a metadata storage interface for sandboxes. Similarly to `Containers`,
// sandbox object includes info required to start a new instance, but no runtime state.
// When running a new sandbox instance, store objects are used as base type to create from.
service Store {
rpc Create(StoreCreateRequest) returns (StoreCreateResponse);
rpc Update(StoreUpdateRequest) returns (StoreUpdateResponse);
rpc Delete(StoreDeleteRequest) returns (StoreDeleteResponse);
rpc List(StoreListRequest) returns (StoreListResponse);
rpc Get(StoreGetRequest) returns (StoreGetResponse);
}
message StoreCreateRequest {
containerd.types.Sandbox sandbox = 1 [(gogoproto.nullable) = false];
}
message StoreCreateResponse {
containerd.types.Sandbox sandbox = 1 [(gogoproto.nullable) = false];
}
message StoreUpdateRequest {
containerd.types.Sandbox sandbox = 1 [(gogoproto.nullable) = false];
repeated string fields = 2;
}
message StoreUpdateResponse {
containerd.types.Sandbox sandbox = 1 [(gogoproto.nullable) = false];
}
message StoreDeleteRequest {
string sandbox_id = 1;
}
message StoreDeleteResponse {}
message StoreListRequest {
repeated string filters = 1;
}
message StoreListResponse {
repeated containerd.types.Sandbox list = 1 [(gogoproto.nullable) = false];
}
message StoreGetRequest {
string sandbox_id = 1;
}
message StoreGetResponse {
containerd.types.Sandbox sandbox = 1;
}
// Controller is an interface to manage runtime sandbox instances.
service Controller {
rpc Start(ControllerStartRequest) returns (ControllerStartResponse);
rpc Shutdown(ControllerShutdownRequest) returns (ControllerShutdownResponse);
rpc Wait(ControllerWaitRequest) returns (ControllerWaitResponse);
rpc Status(ControllerStatusRequest) returns (ControllerStatusResponse);
}
message ControllerStartRequest {
string sandbox_id = 1;
repeated containerd.types.Mount rootfs = 2;
google.protobuf.Any options = 3;
}
message ControllerStartResponse {
string sandbox_id = 1;
uint32 pid = 2;
}
message ControllerShutdownRequest {
string sandbox_id = 1;
uint32 timeout_secs = 2;
}
message ControllerShutdownResponse {}
message ControllerWaitRequest {
string sandbox_id = 1;
}
message ControllerWaitResponse {
uint32 exit_status = 1;
google.protobuf.Timestamp exited_at = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
}
message ControllerStatusRequest {
string sandbox_id = 1;
}
message ControllerStatusResponse {
string id = 1;
uint32 pid = 2;
string state = 3;
uint32 exit_status = 4;
google.protobuf.Timestamp exited_at = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
google.protobuf.Any extra = 6;
}

1148
api/types/sandbox.pb.go Normal file

File diff suppressed because it is too large Load Diff

52
api/types/sandbox.proto Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package containerd.types;
import weak "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
option go_package = "github.com/containerd/containerd/api/types;types";
// Sandbox represents a sandbox metadata object that keeps all info required by controller to
// work with a particular instance.
message Sandbox {
// SandboxID is a unique instance identifier within namespace
string sandbox_id = 1;
message Runtime {
// Name is the name of the runtime.
string name = 1;
// Options specify additional runtime initialization options for the shim (this data will be available in StartShim).
// Typically this data expected to be runtime shim implementation specific.
google.protobuf.Any options = 2;
}
// Runtime specifies which runtime to use for executing this container.
Runtime runtime = 2 [(gogoproto.nullable) = false];
// Spec is sandbox configuration (kin of OCI runtime spec), spec's data will be written to a config.json file in the
// bundle directory (similary to OCI spec).
google.protobuf.Any spec = 3;
// Labels provides an area to include arbitrary data on containers.
map<string, string> labels = 4 [(gogoproto.nullable) = false];
// CreatedAt is the time the container was first created.
google.protobuf.Timestamp created_at = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
// UpdatedAt is the last time the container was mutated.
google.protobuf.Timestamp updated_at = 6 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
// Extensions allow clients to provide optional blobs that can be handled by runtime.
map<string, google.protobuf.Any> extensions = 7 [(gogoproto.nullable) = false];
}

View File

@ -35,6 +35,7 @@ import (
introspectionapi "github.com/containerd/containerd/api/services/introspection/v1" introspectionapi "github.com/containerd/containerd/api/services/introspection/v1"
leasesapi "github.com/containerd/containerd/api/services/leases/v1" leasesapi "github.com/containerd/containerd/api/services/leases/v1"
namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1" namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1"
sandboxsapi "github.com/containerd/containerd/api/services/sandbox/v1"
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1" snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
"github.com/containerd/containerd/api/services/tasks/v1" "github.com/containerd/containerd/api/services/tasks/v1"
versionservice "github.com/containerd/containerd/api/services/version/v1" versionservice "github.com/containerd/containerd/api/services/version/v1"
@ -54,13 +55,14 @@ import (
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/sandbox"
"github.com/containerd/containerd/services/introspection" "github.com/containerd/containerd/services/introspection"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
snproxy "github.com/containerd/containerd/snapshots/proxy" snproxy "github.com/containerd/containerd/snapshots/proxy"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
ptypes "github.com/gogo/protobuf/types" ptypes "github.com/gogo/protobuf/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/backoff" "google.golang.org/grpc/backoff"
@ -688,6 +690,26 @@ func (c *Client) EventService() EventService {
return NewEventServiceFromClient(eventsapi.NewEventsClient(c.conn)) return NewEventServiceFromClient(eventsapi.NewEventsClient(c.conn))
} }
// SandboxStore returns the underlying sandbox store client
func (c *Client) SandboxStore() sandbox.Store {
if c.sandboxStore != nil {
return c.sandboxStore
}
c.connMu.Lock()
defer c.connMu.Unlock()
return NewRemoteSandboxStore(sandboxsapi.NewStoreClient(c.conn))
}
// SandboxController returns the underlying sandbox controller client
func (c *Client) SandboxController() sandbox.Controller {
if c.sandboxController != nil {
return c.sandboxController
}
c.connMu.Lock()
defer c.connMu.Unlock()
return NewSandboxRemoteController(sandboxsapi.NewControllerClient(c.conn))
}
// VersionService returns the underlying VersionClient // VersionService returns the underlying VersionClient
func (c *Client) VersionService() versionservice.VersionClient { func (c *Client) VersionService() versionservice.VersionClient {
c.connMu.Lock() c.connMu.Lock()

View File

@ -23,6 +23,7 @@ import (
"context" "context"
"github.com/containerd/containerd/runtime/v2/runc/manager" "github.com/containerd/containerd/runtime/v2/runc/manager"
_ "github.com/containerd/containerd/runtime/v2/runc/pause"
_ "github.com/containerd/containerd/runtime/v2/runc/task/plugin" _ "github.com/containerd/containerd/runtime/v2/runc/task/plugin"
"github.com/containerd/containerd/runtime/v2/shim" "github.com/containerd/containerd/runtime/v2/shim"
) )

View File

@ -34,6 +34,7 @@ import (
_ "github.com/containerd/containerd/services/leases" _ "github.com/containerd/containerd/services/leases"
_ "github.com/containerd/containerd/services/namespaces" _ "github.com/containerd/containerd/services/namespaces"
_ "github.com/containerd/containerd/services/opt" _ "github.com/containerd/containerd/services/opt"
_ "github.com/containerd/containerd/services/sandbox"
_ "github.com/containerd/containerd/services/snapshots" _ "github.com/containerd/containerd/services/snapshots"
_ "github.com/containerd/containerd/services/tasks" _ "github.com/containerd/containerd/services/tasks"
_ "github.com/containerd/containerd/services/version" _ "github.com/containerd/containerd/services/version"

View File

@ -31,6 +31,7 @@ import (
"github.com/containerd/containerd/cmd/ctr/commands/plugins" "github.com/containerd/containerd/cmd/ctr/commands/plugins"
"github.com/containerd/containerd/cmd/ctr/commands/pprof" "github.com/containerd/containerd/cmd/ctr/commands/pprof"
"github.com/containerd/containerd/cmd/ctr/commands/run" "github.com/containerd/containerd/cmd/ctr/commands/run"
"github.com/containerd/containerd/cmd/ctr/commands/sandboxes"
"github.com/containerd/containerd/cmd/ctr/commands/snapshots" "github.com/containerd/containerd/cmd/ctr/commands/snapshots"
"github.com/containerd/containerd/cmd/ctr/commands/tasks" "github.com/containerd/containerd/cmd/ctr/commands/tasks"
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version" versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
@ -114,6 +115,7 @@ containerd CLI
tasks.Command, tasks.Command,
install.Command, install.Command,
ociCmd.Command, ociCmd.Command,
sandboxes.Command,
}, extraCmds...) }, extraCmds...)
app.Before = func(context *cli.Context) error { app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") { if context.GlobalBool("debug") {

View File

@ -0,0 +1,178 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandboxes
import (
"fmt"
"os"
"text/tabwriter"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/defaults"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/oci"
"github.com/urfave/cli"
)
// Command is a set of subcommands to manage runtimes with sandbox support
var Command = cli.Command{
Name: "sandboxes",
Aliases: []string{"sandbox", "sb", "s"},
Usage: "manage sandboxes",
Subcommands: cli.Commands{
runCommand,
listCommand,
removeCommand,
},
}
var runCommand = cli.Command{
Name: "run",
Aliases: []string{"create", "c", "r"},
Usage: "run a new sandbox",
ArgsUsage: "[flags] <pod-config.json> <sandbox-id>",
Flags: []cli.Flag{
cli.StringFlag{
Name: "runtime",
Usage: "runtime name",
Value: defaults.DefaultRuntime,
},
},
Action: func(context *cli.Context) error {
var (
id = context.Args().Get(0)
runtime = context.String("runtime")
)
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
sandbox, err := client.NewSandbox(ctx, id,
containerd.WithSandboxRuntime(runtime, nil),
containerd.WithSandboxSpec(&oci.Spec{}),
)
if err != nil {
return fmt.Errorf("failed to create new sandbox: %w", err)
}
err = sandbox.Start(ctx)
if err != nil {
return fmt.Errorf("failed to start: %w", err)
}
fmt.Println(sandbox.ID())
return nil
},
}
var listCommand = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "list sandboxes",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "filters",
Usage: "the list of filters to apply when querying sandboxes from the store",
},
},
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
var (
writer = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
filters = context.StringSlice("filters")
)
defer func() {
_ = writer.Flush()
}()
list, err := client.SandboxStore().List(ctx, filters...)
if err != nil {
return fmt.Errorf("failed to list sandboxes: %w", err)
}
if _, err := fmt.Fprintln(writer, "ID\tCREATED\tRUNTIME\t"); err != nil {
return err
}
for _, sandbox := range list {
_, err := fmt.Fprintf(writer, "%s\t%s\t%s\t\n", sandbox.ID, sandbox.CreatedAt, sandbox.Runtime.Name)
if err != nil {
return err
}
}
return nil
},
}
var removeCommand = cli.Command{
Name: "remove",
Aliases: []string{"rm"},
ArgsUsage: "<id> [<id>, ...]",
Usage: "remove sandboxes",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "ignore shutdown errors when removing sandbox",
},
},
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
force := context.Bool("force")
for _, id := range context.Args() {
sandbox, err := client.LoadSandbox(ctx, id)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to load sandbox %s", id)
continue
}
err = sandbox.Stop(ctx)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to stop sandbox %s", id)
if !force {
continue
}
}
err = sandbox.Delete(ctx)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to shutdown sandbox %s", id)
continue
}
log.G(ctx).Infof("deleted: %s", id)
}
return nil
},
}

View File

@ -74,6 +74,15 @@ func WithRuntime(name string, options interface{}) NewContainerOpts {
} }
} }
// WithSandbox joins the container to a container group (aka sandbox) from the given ID
// Note: shim runtime must support sandboxes environments.
func WithSandbox(sandboxID string) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
c.SandboxID = sandboxID
return nil
}
}
// WithImage sets the provided image as the base for the container // WithImage sets the provided image as the base for the container
func WithImage(i Image) NewContainerOpts { func WithImage(i Image) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error { return func(ctx context.Context, client *Client, c *containers.Container) error {

View File

@ -76,6 +76,11 @@ type Container struct {
// Extensions stores client-specified metadata // Extensions stores client-specified metadata
Extensions map[string]typeurl.Any Extensions map[string]typeurl.Any
// SandboxID is an identifier of sandbox this container belongs to.
//
// This property is optional, but can't be changed after creation.
SandboxID string
} }
// RuntimeInfo holds runtime specific information // RuntimeInfo holds runtime specific information

View File

@ -166,6 +166,7 @@ func containerToProto(container *containers.Container) containersapi.Container {
Snapshotter: container.Snapshotter, Snapshotter: container.Snapshotter,
SnapshotKey: container.SnapshotKey, SnapshotKey: container.SnapshotKey,
Extensions: extensions, Extensions: extensions,
Sandbox: container.SandboxID,
} }
} }
@ -193,6 +194,7 @@ func containerFromProto(containerpb *containersapi.Container) containers.Contain
CreatedAt: containerpb.CreatedAt, CreatedAt: containerpb.CreatedAt,
UpdatedAt: containerpb.UpdatedAt, UpdatedAt: containerpb.UpdatedAt,
Extensions: extensions, Extensions: extensions,
SandboxID: containerpb.Sandbox,
} }
} }

2
go.mod
View File

@ -48,6 +48,7 @@ require (
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/opencontainers/selinux v1.10.0 github.com/opencontainers/selinux v1.10.0
github.com/pelletier/go-toml v1.9.3 github.com/pelletier/go-toml v1.9.3
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
@ -103,7 +104,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e // indirect github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/common v0.30.0 // indirect

View File

@ -24,6 +24,7 @@ import (
"github.com/containerd/containerd/filters" "github.com/containerd/containerd/filters"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases" "github.com/containerd/containerd/leases"
"github.com/containerd/containerd/sandbox"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
) )
@ -149,6 +150,23 @@ func adaptSnapshot(info snapshots.Info) filters.Adaptor {
}) })
} }
func adaptSandbox(instance *sandbox.Sandbox) filters.Adaptor {
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
if len(fieldpath) == 0 {
return "", false
}
switch fieldpath[0] {
case "id":
return instance.ID, true
case "labels":
return checkMap(fieldpath[1:], instance.Labels)
default:
return "", false
}
})
}
func checkMap(fieldpath []string, m map[string]string) (string, bool) { func checkMap(fieldpath []string, m map[string]string) (string, bool) {
if len(m) == 0 { if len(m) == 0 {
return "", false return "", false

View File

@ -213,11 +213,11 @@ func WriteAny(bkt *bolt.Bucket, name []byte, any typeurl.Any) error {
data, err := proto.Marshal(pbany) data, err := proto.Marshal(pbany)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to marshal: %w", err)
} }
if err := bkt.Put(name, data); err != nil { if err := bkt.Put(name, data); err != nil {
return err return fmt.Errorf("put failed: %w", err)
} }
return nil return nil

View File

@ -130,6 +130,7 @@ var (
bucketKeyObjectBlob = []byte("blob") // stores content links bucketKeyObjectBlob = []byte("blob") // stores content links
bucketKeyObjectIngests = []byte("ingests") // stores ingest objects bucketKeyObjectIngests = []byte("ingests") // stores ingest objects
bucketKeyObjectLeases = []byte("leases") // stores leases bucketKeyObjectLeases = []byte("leases") // stores leases
bucketKeyObjectSandboxes = []byte("sandboxes") // stores sandboxes
bucketKeyDigest = []byte("digest") bucketKeyDigest = []byte("digest")
bucketKeyMediaType = []byte("mediatype") bucketKeyMediaType = []byte("mediatype")
@ -149,6 +150,7 @@ var (
bucketKeyExpected = []byte("expected") bucketKeyExpected = []byte("expected")
bucketKeyRef = []byte("ref") bucketKeyRef = []byte("ref")
bucketKeyExpireAt = []byte("expireat") bucketKeyExpireAt = []byte("expireat")
bucketKeySandboxID = []byte("sandboxid")
deprecatedBucketKeyObjectIngest = []byte("ingest") // stores ingest links, deprecated in v1.2 deprecatedBucketKeyObjectIngest = []byte("ingest") // stores ingest links, deprecated in v1.2
) )
@ -270,3 +272,19 @@ func createIngestBucket(tx *bolt.Tx, namespace, ref string) (*bolt.Bucket, error
func getIngestBucket(tx *bolt.Tx, namespace, ref string) *bolt.Bucket { func getIngestBucket(tx *bolt.Tx, namespace, ref string) *bolt.Bucket {
return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(ref)) return getBucket(tx, bucketKeyVersion, []byte(namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(ref))
} }
func createSandboxBucket(tx *bolt.Tx, namespace string) (*bolt.Bucket, error) {
return createBucketIfNotExists(
tx,
[]byte(namespace),
bucketKeyObjectSandboxes,
)
}
func getSandboxBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
return getBucket(
tx,
[]byte(namespace),
bucketKeyObjectSandboxes,
)
}

View File

@ -359,6 +359,8 @@ func readContainer(container *containers.Container, bkt *bolt.Bucket) error {
} }
container.Extensions = extensions container.Extensions = extensions
case string(bucketKeySandboxID):
container.SandboxID = string(v)
} }
return nil return nil
@ -407,5 +409,9 @@ func writeContainer(bkt *bolt.Bucket, container *containers.Container) error {
return err return err
} }
if err := bkt.Put(bucketKeySandboxID, []byte(container.SandboxID)); err != nil {
return err
}
return boltutil.WriteLabels(bkt, container.Labels) return boltutil.WriteLabels(bkt, container.Labels)
} }

377
metadata/sandbox.go Normal file
View File

@ -0,0 +1,377 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metadata
import (
"context"
"fmt"
"strings"
"time"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/filters"
"github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/metadata/boltutil"
"github.com/containerd/containerd/namespaces"
api "github.com/containerd/containerd/sandbox"
"github.com/containerd/typeurl"
"github.com/pkg/errors"
"go.etcd.io/bbolt"
)
type sandboxStore struct {
db *DB
}
var _ api.Store = (*sandboxStore)(nil)
// NewSandboxStore creates a datababase client for sandboxes
func NewSandboxStore(db *DB) api.Store {
return &sandboxStore{db: db}
}
// Create a sandbox record in the store
func (s *sandboxStore) Create(ctx context.Context, sandbox api.Sandbox) (api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return api.Sandbox{}, err
}
sandbox.CreatedAt = time.Now().UTC()
sandbox.UpdatedAt = sandbox.CreatedAt
if err := s.validate(&sandbox); err != nil {
return api.Sandbox{}, errors.Wrap(err, "failed to validate sandbox")
}
if err := s.db.Update(func(tx *bbolt.Tx) error {
parent, err := createSandboxBucket(tx, ns)
if err != nil {
return fmt.Errorf("create error: %w", err)
}
if err := s.write(parent, &sandbox, false); err != nil {
return fmt.Errorf("write error: %w", err)
}
return nil
}); err != nil {
return api.Sandbox{}, err
}
return sandbox, nil
}
// Update the sandbox with the provided sandbox object and fields
func (s *sandboxStore) Update(ctx context.Context, sandbox api.Sandbox, fieldpaths ...string) (api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return api.Sandbox{}, err
}
ret := api.Sandbox{}
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
parent := getSandboxBucket(tx, ns)
if parent == nil {
return errors.Wrap(errdefs.ErrNotFound, "no sandbox buckets")
}
updated, err := s.read(parent, []byte(sandbox.ID))
if err != nil {
return err
}
if len(fieldpaths) == 0 {
fieldpaths = []string{"labels", "extensions", "spec", "runtime"}
if updated.Runtime.Name != sandbox.Runtime.Name {
return errors.Wrapf(errdefs.ErrInvalidArgument, "sandbox.Runtime.Name field is immutable")
}
}
for _, path := range fieldpaths {
if strings.HasPrefix(path, "labels.") {
if updated.Labels == nil {
updated.Labels = map[string]string{}
}
key := strings.TrimPrefix(path, "labels.")
updated.Labels[key] = sandbox.Labels[key]
continue
} else if strings.HasPrefix(path, "extensions.") {
if updated.Extensions == nil {
updated.Extensions = map[string]typeurl.Any{}
}
key := strings.TrimPrefix(path, "extensions.")
updated.Extensions[key] = sandbox.Extensions[key]
continue
}
switch path {
case "labels":
updated.Labels = sandbox.Labels
case "extensions":
updated.Extensions = sandbox.Extensions
case "runtime":
updated.Runtime = sandbox.Runtime
case "spec":
updated.Spec = sandbox.Spec
default:
return errors.Wrapf(errdefs.ErrInvalidArgument, "cannot update %q field on sandbox %q", path, sandbox.ID)
}
}
updated.UpdatedAt = time.Now().UTC()
if err := s.validate(&updated); err != nil {
return err
}
if err := s.write(parent, &updated, true); err != nil {
return err
}
ret = updated
return nil
}); err != nil {
return api.Sandbox{}, err
}
return ret, nil
}
// Get sandbox metadata using the id
func (s *sandboxStore) Get(ctx context.Context, id string) (api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return api.Sandbox{}, err
}
ret := api.Sandbox{}
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
bucket := getSandboxBucket(tx, ns)
if bucket == nil {
return errors.Wrap(errdefs.ErrNotFound, "no sandbox buckets")
}
out, err := s.read(bucket, []byte(id))
if err != nil {
return err
}
ret = out
return nil
}); err != nil {
return api.Sandbox{}, err
}
return ret, nil
}
// List returns sandboxes that match one or more of the provided filters
func (s *sandboxStore) List(ctx context.Context, fields ...string) ([]api.Sandbox, error) {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}
filter, err := filters.ParseAll(fields...)
if err != nil {
return nil, errors.Wrap(errdefs.ErrInvalidArgument, err.Error())
}
var (
list []api.Sandbox
)
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
bucket := getSandboxBucket(tx, ns)
if bucket == nil {
// We haven't created any sandboxes yet, just return empty list
return nil
}
if err := bucket.ForEach(func(k, v []byte) error {
info, err := s.read(bucket, k)
if err != nil {
return errors.Wrapf(err, "failed to read bucket %q", string(k))
}
if filter.Match(adaptSandbox(&info)) {
list = append(list, info)
}
return nil
}); err != nil {
return err
}
return nil
}); err != nil {
return nil, err
}
return list, nil
}
// Delete a sandbox from metadata store using the id
func (s *sandboxStore) Delete(ctx context.Context, id string) error {
ns, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return err
}
if err := update(ctx, s.db, func(tx *bbolt.Tx) error {
buckets := getSandboxBucket(tx, ns)
if buckets == nil {
return errors.Wrap(errdefs.ErrNotFound, "no sandbox buckets")
}
if err := buckets.DeleteBucket([]byte(id)); err != nil {
return errors.Wrapf(err, "failed to delete sandbox %q", id)
}
return nil
}); err != nil {
return err
}
return nil
}
func (s *sandboxStore) write(parent *bbolt.Bucket, instance *api.Sandbox, overwrite bool) error {
var (
bucket *bbolt.Bucket
err error
id = []byte(instance.ID)
)
if overwrite {
bucket, err = parent.CreateBucketIfNotExists(id)
if err != nil {
return err
}
} else {
bucket = parent.Bucket(id)
if bucket != nil {
return errors.Wrapf(errdefs.ErrAlreadyExists, "sandbox bucket %q already exists", instance.ID)
}
bucket, err = parent.CreateBucket(id)
if err != nil {
return err
}
}
if err := boltutil.WriteTimestamps(bucket, instance.CreatedAt, instance.UpdatedAt); err != nil {
return err
}
if err := boltutil.WriteLabels(bucket, instance.Labels); err != nil {
return err
}
if err := boltutil.WriteExtensions(bucket, instance.Extensions); err != nil {
return err
}
if err := boltutil.WriteAny(bucket, bucketKeySpec, instance.Spec); err != nil {
return err
}
runtimeBucket, err := bucket.CreateBucketIfNotExists(bucketKeyRuntime)
if err != nil {
return err
}
if err := runtimeBucket.Put(bucketKeyName, []byte(instance.Runtime.Name)); err != nil {
return err
}
if err := boltutil.WriteAny(runtimeBucket, bucketKeyOptions, instance.Runtime.Options); err != nil {
return err
}
return nil
}
func (s *sandboxStore) read(parent *bbolt.Bucket, id []byte) (api.Sandbox, error) {
var (
inst api.Sandbox
err error
)
bucket := parent.Bucket(id)
if bucket == nil {
return api.Sandbox{}, errors.Wrapf(errdefs.ErrNotFound, "bucket %q not found", id)
}
inst.ID = string(id)
inst.Labels, err = boltutil.ReadLabels(bucket)
if err != nil {
return api.Sandbox{}, err
}
if err := boltutil.ReadTimestamps(bucket, &inst.CreatedAt, &inst.UpdatedAt); err != nil {
return api.Sandbox{}, err
}
inst.Spec, err = boltutil.ReadAny(bucket, bucketKeySpec)
if err != nil {
return api.Sandbox{}, err
}
runtimeBucket := bucket.Bucket(bucketKeyRuntime)
if runtimeBucket == nil {
return api.Sandbox{}, errors.New("no runtime bucket")
}
inst.Runtime.Name = string(runtimeBucket.Get(bucketKeyName))
inst.Runtime.Options, err = boltutil.ReadAny(runtimeBucket, bucketKeyOptions)
if err != nil {
return api.Sandbox{}, err
}
inst.Extensions, err = boltutil.ReadExtensions(bucket)
if err != nil {
return api.Sandbox{}, err
}
return inst, nil
}
func (s *sandboxStore) validate(new *api.Sandbox) error {
if err := identifiers.Validate(new.ID); err != nil {
return errors.Wrap(err, "invalid sandbox ID")
}
if new.CreatedAt.IsZero() {
return errors.Wrap(errdefs.ErrInvalidArgument, "creation date must not be zero")
}
if new.UpdatedAt.IsZero() {
return errors.Wrap(errdefs.ErrInvalidArgument, "updated date must not be zero")
}
if new.Runtime.Name == "" {
return errors.Wrapf(errdefs.ErrInvalidArgument, "sandbox.Runtime.Name must be set")
}
return nil
}

292
metadata/sandbox_test.go Normal file
View File

@ -0,0 +1,292 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metadata
import (
"reflect"
"testing"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/protobuf"
api "github.com/containerd/containerd/sandbox"
"github.com/containerd/typeurl"
"github.com/gogo/protobuf/types"
)
func TestSandboxCreate(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
in := api.Sandbox{
ID: "1",
Labels: map[string]string{"a": "1", "b": "2"},
Spec: &types.Any{TypeUrl: "1", Value: []byte{1, 2, 3}},
Extensions: map[string]typeurl.Any{
"ext1": &types.Any{TypeUrl: "url/1", Value: []byte{1, 2, 3}},
"ext2": &types.Any{TypeUrl: "url/2", Value: []byte{3, 2, 1}},
},
Runtime: api.RuntimeOpts{
Name: "test",
Options: &types.Any{TypeUrl: "url/3", Value: []byte{4, 5, 6}},
},
}
_, err := store.Create(ctx, in)
if err != nil {
t.Fatal(err)
}
out, err := store.Get(ctx, "1")
if err != nil {
t.Fatal(err)
}
assertEqualInstances(t, in, out)
}
func TestSandboxCreateDup(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
in := api.Sandbox{
ID: "1",
Spec: &types.Any{TypeUrl: "1", Value: []byte{1, 2, 3}},
Runtime: api.RuntimeOpts{Name: "test"},
}
_, err := store.Create(ctx, in)
if err != nil {
t.Fatal(err)
}
_, err = store.Create(ctx, in)
if !errdefs.IsAlreadyExists(err) {
t.Fatalf("expected %+v, got %+v", errdefs.ErrAlreadyExists, err)
}
}
func TestSandboxUpdate(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
if _, err := store.Create(ctx, api.Sandbox{
ID: "2",
Labels: map[string]string{"lbl1": "existing"},
Spec: &types.Any{TypeUrl: "1", Value: []byte{1}}, // will replace
Extensions: map[string]typeurl.Any{
"ext2": &types.Any{TypeUrl: "url2", Value: []byte{4, 5, 6}}, // will append `ext1`
},
Runtime: api.RuntimeOpts{Name: "test"}, // no change
}); err != nil {
t.Fatal(err)
}
expectedSpec := types.Any{TypeUrl: "2", Value: []byte{3, 2, 1}}
out, err := store.Update(ctx, api.Sandbox{
ID: "2",
Labels: map[string]string{"lbl1": "new"},
Spec: &expectedSpec,
Extensions: map[string]typeurl.Any{
"ext1": &types.Any{TypeUrl: "url1", Value: []byte{1, 2}},
},
}, "labels.lbl1", "extensions.ext1", "spec")
if err != nil {
t.Fatal(err)
}
expected := api.Sandbox{
ID: "2",
Spec: &expectedSpec,
Labels: map[string]string{
"lbl1": "new",
},
Extensions: map[string]typeurl.Any{
"ext1": &types.Any{TypeUrl: "url1", Value: []byte{1, 2}},
"ext2": &types.Any{TypeUrl: "url2", Value: []byte{4, 5, 6}},
},
Runtime: api.RuntimeOpts{Name: "test"},
}
assertEqualInstances(t, out, expected)
}
func TestSandboxGetInvalid(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
_, err := store.Get(ctx, "invalid_id")
if err == nil {
t.Fatalf("expected %+v error for invalid ID", errdefs.ErrNotFound)
} else if !errdefs.IsNotFound(err) {
t.Fatalf("unexpected error %T type", err)
}
}
func TestSandboxList(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
in := []api.Sandbox{
{
ID: "1",
Labels: map[string]string{"test": "1"},
Spec: &types.Any{TypeUrl: "1", Value: []byte{1, 2, 3}},
Extensions: map[string]typeurl.Any{"ext": &types.Any{}},
Runtime: api.RuntimeOpts{Name: "test"},
},
{
ID: "2",
Labels: map[string]string{"test": "2"},
Spec: &types.Any{TypeUrl: "2", Value: []byte{3, 2, 1}},
Extensions: map[string]typeurl.Any{"ext": &types.Any{
TypeUrl: "test",
Value: []byte{9},
}},
Runtime: api.RuntimeOpts{Name: "test"},
},
}
for _, inst := range in {
_, err := store.Create(ctx, inst)
if err != nil {
t.Fatal(err)
}
}
out, err := store.List(ctx)
if err != nil {
t.Fatal(err)
}
if len(in) != len(out) {
t.Fatalf("expected list size: %d != %d", len(in), len(out))
}
for i := range out {
assertEqualInstances(t, out[i], in[i])
}
}
func TestSandboxListWithFilter(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
in := []api.Sandbox{
{
ID: "1",
Labels: map[string]string{"test": "1"},
Spec: &types.Any{TypeUrl: "1", Value: []byte{1, 2, 3}},
Extensions: map[string]typeurl.Any{"ext": &types.Any{}},
Runtime: api.RuntimeOpts{Name: "test"},
},
{
ID: "2",
Labels: map[string]string{"test": "2"},
Spec: &types.Any{TypeUrl: "2", Value: []byte{3, 2, 1}},
Extensions: map[string]typeurl.Any{"ext": &types.Any{
TypeUrl: "test",
Value: []byte{9},
}},
Runtime: api.RuntimeOpts{Name: "test"},
},
}
for _, inst := range in {
_, err := store.Create(ctx, inst)
if err != nil {
t.Fatal(err)
}
}
out, err := store.List(ctx, "id==1")
if err != nil {
t.Fatal(err)
}
if len(out) != 1 {
t.Fatalf("expected list to contain 1 element, got %d", len(out))
}
assertEqualInstances(t, out[0], in[0])
}
func TestSandboxDelete(t *testing.T) {
ctx, db, done := testDB(t)
defer done()
store := NewSandboxStore(db)
in := api.Sandbox{
ID: "2",
Spec: &types.Any{TypeUrl: "1", Value: []byte{1, 2, 3}},
Runtime: api.RuntimeOpts{Name: "test"},
}
_, err := store.Create(ctx, in)
if err != nil {
t.Fatal(err)
}
err = store.Delete(ctx, "2")
if err != nil {
t.Fatalf("deleted failed %+v", err)
}
_, err = store.Get(ctx, "2")
if !errdefs.IsNotFound(err) {
t.Fatalf("unexpected err result: %+v != %+v", err, errdefs.ErrNotFound)
}
}
func assertEqualInstances(t *testing.T, x, y api.Sandbox) {
if x.ID != y.ID {
t.Fatalf("ids are not equal: %q != %q", x.ID, y.ID)
}
if !reflect.DeepEqual(x.Labels, y.Labels) {
t.Fatalf("labels are not equal: %+v != %+v", x.Labels, y.Labels)
}
if !reflect.DeepEqual(x.Spec, y.Spec) {
t.Fatalf("specs are not equal: %+v != %+v", x.Spec, y.Spec)
}
if !reflect.DeepEqual(x.Extensions, y.Extensions) {
t.Fatalf("extensions are not equal: %+v != %+v", x.Extensions, y.Extensions)
}
if x.Runtime.Name != y.Runtime.Name {
t.Fatalf("runtime names are not equal: %q != %q", x.Runtime.Name, y.Runtime.Name)
}
if !reflect.DeepEqual(protobuf.FromAny(x.Runtime.Options), protobuf.FromAny(y.Runtime.Options)) {
t.Fatalf("runtime options are not equal: %+v != %+v", x.Runtime.Options, y.Runtime.Options)
}
}

View File

@ -49,6 +49,8 @@ type CreateOpts struct {
// Runtime name to use (e.g. `io.containerd.NAME.VERSION`). // Runtime name to use (e.g. `io.containerd.NAME.VERSION`).
// As an alternative full abs path to binary may be specified instead. // As an alternative full abs path to binary may be specified instead.
Runtime string Runtime string
// SandboxID is an optional ID of sandbox this container belongs to
SandboxID string
} }
// Exit information for a process // Exit information for a process

View File

@ -25,6 +25,8 @@ import (
"github.com/containerd/containerd/identifiers" "github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/typeurl"
"github.com/opencontainers/runtime-spec/specs-go"
) )
const configFilename = "config.json" const configFilename = "config.json"
@ -43,7 +45,7 @@ func LoadBundle(ctx context.Context, root, id string) (*Bundle, error) {
} }
// NewBundle returns a new bundle on disk // NewBundle returns a new bundle on disk
func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bundle, err error) { func NewBundle(ctx context.Context, root, state, id string, spec typeurl.Any) (b *Bundle, err error) {
if err := identifiers.Validate(id); err != nil { if err := identifiers.Validate(id); err != nil {
return nil, fmt.Errorf("invalid task id %s: %w", id, err) return nil, fmt.Errorf("invalid task id %s: %w", id, err)
} }
@ -73,9 +75,11 @@ func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bun
if err := os.Mkdir(b.Path, 0700); err != nil { if err := os.Mkdir(b.Path, 0700); err != nil {
return nil, err return nil, err
} }
if err := prepareBundleDirectoryPermissions(b.Path, spec); err != nil { if typeurl.Is(spec, &specs.Spec{}) {
if err := prepareBundleDirectoryPermissions(b.Path, spec.GetValue()); err != nil {
return nil, err return nil, err
} }
}
paths = append(paths, b.Path) paths = append(paths, b.Path)
// create working directory for the bundle // create working directory for the bundle
if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil { if err := os.MkdirAll(filepath.Dir(work), 0711); err != nil {
@ -100,9 +104,14 @@ func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bun
if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil { if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil {
return nil, err return nil, err
} }
if spec := spec.GetValue(); spec != nil {
// write the spec to the bundle // write the spec to the bundle
err = os.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666) err = os.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666)
return b, err if err != nil {
return nil, fmt.Errorf("failed to write %s", configFilename)
}
}
return b, nil
} }
// Bundle represents an OCI bundle // Bundle represents an OCI bundle

View File

@ -29,6 +29,7 @@ import (
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci" "github.com/containerd/containerd/oci"
"github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/pkg/testutil"
"github.com/containerd/typeurl"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -57,11 +58,11 @@ func TestNewBundle(t *testing.T) {
GIDMappings: []specs.LinuxIDMapping{{ContainerID: 0, HostID: usernsGID}}, GIDMappings: []specs.LinuxIDMapping{{ContainerID: 0, HostID: usernsGID}},
} }
} }
specBytes, err := json.Marshal(&spec) specAny, err := typeurl.MarshalAny(&spec)
require.NoError(t, err, "failed to marshal spec") require.NoError(t, err, "failed to marshal spec")
ctx := namespaces.WithNamespace(context.TODO(), namespaces.Default) ctx := namespaces.WithNamespace(context.TODO(), namespaces.Default)
b, err := NewBundle(ctx, work, state, id, specBytes) b, err := NewBundle(ctx, work, state, id, specAny)
require.NoError(t, err, "NewBundle should succeed") require.NoError(t, err, "NewBundle should succeed")
require.NotNil(t, b, "bundle should not be nil") require.NotNil(t, b, "bundle should not be nil")

View File

@ -96,6 +96,24 @@ func init() {
return NewTaskManager(shimManager), nil return NewTaskManager(shimManager), nil
}, },
}) })
// Task manager uses shim manager as a dependency to manage shim instances.
// However, due to time limits and to avoid migration steps in 1.6 release,
// use the following workaround.
// This expected to be removed in 1.7.
plugin.Register(&plugin.Registration{
Type: plugin.RuntimePluginV2,
ID: "shim",
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
taskManagerI, err := ic.GetByID(plugin.RuntimePluginV2, "task")
if err != nil {
return nil, err
}
taskManager := taskManagerI.(*TaskManager)
return taskManager.manager, nil
},
})
} }
type ManagerConfig struct { type ManagerConfig struct {
@ -158,7 +176,7 @@ func (m *ShimManager) ID() string {
// Start launches a new shim instance // Start launches a new shim instance
func (m *ShimManager) Start(ctx context.Context, id string, opts runtime.CreateOpts) (_ ShimProcess, retErr error) { func (m *ShimManager) Start(ctx context.Context, id string, opts runtime.CreateOpts) (_ ShimProcess, retErr error) {
bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec.GetValue()) bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -168,6 +186,40 @@ func (m *ShimManager) Start(ctx context.Context, id string, opts runtime.CreateO
} }
}() }()
// This container belongs to sandbox which supposed to be already started via sandbox API.
if opts.SandboxID != "" {
process, err := m.Get(ctx, opts.SandboxID)
if err != nil {
return nil, fmt.Errorf("can't find sandbox %s", opts.SandboxID)
}
// Write sandbox ID this task belongs to.
if err := os.WriteFile(filepath.Join(bundle.Path, "sandbox"), []byte(opts.SandboxID), 0600); err != nil {
return nil, err
}
address, err := shimbinary.ReadAddress(filepath.Join(m.state, process.Namespace(), opts.SandboxID, "address"))
if err != nil {
return nil, fmt.Errorf("failed to get socket address for sandbox %q: %w", opts.SandboxID, err)
}
// Use sandbox's socket address to handle task requests for this container.
if err := shimbinary.WriteAddress(filepath.Join(bundle.Path, "address"), address); err != nil {
return nil, err
}
shim, err := loadShim(ctx, bundle, func() {})
if err != nil {
return nil, fmt.Errorf("failed to load sandbox task %q: %w", opts.SandboxID, err)
}
if err := m.shims.Add(ctx, shim); err != nil {
return nil, err
}
return shim, nil
}
shim, err := m.startShim(ctx, bundle, id, opts) shim, err := m.startShim(ctx, bundle, id, opts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -324,7 +376,8 @@ func (m *ShimManager) Get(ctx context.Context, id string) (ShimProcess, error) {
return nil, err return nil, err
} }
return proc, nil shimTask := proc.(*shimTask)
return shimTask, nil
} }
// Delete a runtime task // Delete a runtime task
@ -388,7 +441,8 @@ func (m *TaskManager) Create(ctx context.Context, taskID string, opts runtime.Cr
dctx, cancel := timeout.WithContext(context.Background(), cleanupTimeout) dctx, cancel := timeout.WithContext(context.Background(), cleanupTimeout)
defer cancel() defer cancel()
_, errShim := shim.delete(dctx, func(context.Context, string) {}) sandboxed := opts.SandboxID != ""
_, errShim := shim.delete(dctx, sandboxed, func(context.Context, string) {})
if errShim != nil { if errShim != nil {
if errdefs.IsDeadlineExceeded(errShim) { if errdefs.IsDeadlineExceeded(errShim) {
dctx, cancel = timeout.WithContext(context.Background(), cleanupTimeout) dctx, cancel = timeout.WithContext(context.Background(), cleanupTimeout)
@ -422,8 +476,14 @@ func (m *TaskManager) Delete(ctx context.Context, taskID string) (*runtime.Exit,
return nil, err return nil, err
} }
container, err := m.manager.containers.Get(ctx, taskID)
if err != nil {
return nil, err
}
sandboxed := container.SandboxID != ""
shimTask := item.(*shimTask) shimTask := item.(*shimTask)
exit, err := shimTask.delete(ctx, func(ctx context.Context, id string) { exit, err := shimTask.delete(ctx, sandboxed, func(ctx context.Context, id string) {
m.manager.shims.Delete(ctx, id) m.manager.shims.Delete(ctx, id)
}) })

View File

@ -0,0 +1,104 @@
//go:build linux
// +build linux
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pause
import (
"context"
"github.com/containerd/containerd/pkg/shutdown"
"github.com/containerd/ttrpc"
log "github.com/sirupsen/logrus"
"github.com/containerd/containerd/plugin"
api "github.com/containerd/containerd/runtime/v2/task"
)
func init() {
plugin.Register(&plugin.Registration{
Type: plugin.TTRPCPlugin,
ID: "pause",
Requires: []plugin.Type{
plugin.InternalPlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
ss, err := ic.GetByID(plugin.InternalPlugin, "shutdown")
if err != nil {
return nil, err
}
return &pauseService{
shutdown: ss.(shutdown.Service),
}, nil
},
})
}
// pauseService is an extension for task v2 runtime to support Pod "pause" containers via sandbox API.
type pauseService struct {
shutdown shutdown.Service
}
var _ api.SandboxService = (*pauseService)(nil)
func (p *pauseService) RegisterTTRPC(server *ttrpc.Server) error {
api.RegisterSandboxService(server, p)
return nil
}
func (p *pauseService) StartSandbox(ctx context.Context, req *api.StartSandboxRequest) (*api.StartSandboxResponse, error) {
log.Debugf("start sandbox request: %+v", req)
return &api.StartSandboxResponse{}, nil
}
func (p *pauseService) StopSandbox(ctx context.Context, req *api.StopSandboxRequest) (*api.StopSandboxResponse, error) {
log.Debugf("stop sandbox request: %+v", req)
p.shutdown.Shutdown()
return &api.StopSandboxResponse{}, nil
}
func (p *pauseService) WaitSandbox(ctx context.Context, req *api.WaitSandboxRequest) (*api.WaitSandboxResponse, error) {
return &api.WaitSandboxResponse{
ExitStatus: 0,
}, nil
}
func (p *pauseService) UpdateSandbox(ctx context.Context, req *api.UpdateSandboxRequest) (*api.UpdateSandboxResponse, error) {
log.Debugf("update sandbox request: %+v", req)
return &api.UpdateSandboxResponse{}, nil
}
func (p *pauseService) PauseSandbox(ctx context.Context, req *api.PauseSandboxRequest) (*api.PauseSandboxResponse, error) {
log.Debugf("pause sandbox request: %+v", req)
return &api.PauseSandboxResponse{}, nil
}
func (p *pauseService) ResumeSandbox(ctx context.Context, req *api.ResumeSandboxRequest) (*api.ResumeSandboxResponse, error) {
log.Debugf("resume sandbox request: %+v", req)
return &api.ResumeSandboxResponse{}, nil
}
func (p *pauseService) SandboxStatus(ctx context.Context, req *api.SandboxStatusRequest) (*api.SandboxStatusResponse, error) {
log.Debugf("sandbox status request: %+v", req)
return &api.SandboxStatusResponse{}, nil
}
func (p *pauseService) PingSandbox(ctx context.Context, req *api.PingRequest) (*api.PingResponse, error) {
return &api.PingResponse{}, nil
}

View File

@ -194,6 +194,10 @@ type ShimProcess interface {
ID() string ID() string
// Namespace of this shim. // Namespace of this shim.
Namespace() string Namespace() string
// Bundle is a file system path to shim's bundle.
Bundle() string
// Client returns the underlying TTRPC client for this shim.
Client() *ttrpc.Client
} }
type shim struct { type shim struct {
@ -210,6 +214,10 @@ func (s *shim) Namespace() string {
return s.bundle.Namespace return s.bundle.Namespace
} }
func (s *shim) Bundle() string {
return s.bundle.Path
}
func (s *shim) Close() error { func (s *shim) Close() error {
return s.client.Close() return s.client.Close()
} }
@ -243,6 +251,10 @@ type shimTask struct {
task task.TaskService task task.TaskService
} }
func (s *shimTask) Client() *ttrpc.Client {
return s.client
}
func (s *shimTask) Shutdown(ctx context.Context) error { func (s *shimTask) Shutdown(ctx context.Context) error {
_, err := s.task.Shutdown(ctx, &task.ShutdownRequest{ _, err := s.task.Shutdown(ctx, &task.ShutdownRequest{
ID: s.ID(), ID: s.ID(),
@ -271,7 +283,7 @@ func (s *shimTask) PID(ctx context.Context) (uint32, error) {
return response.TaskPid, nil return response.TaskPid, nil
} }
func (s *shimTask) delete(ctx context.Context, removeTask func(ctx context.Context, id string)) (*runtime.Exit, error) { func (s *shimTask) delete(ctx context.Context, sandboxed bool, removeTask func(ctx context.Context, id string)) (*runtime.Exit, error) {
response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{ response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{
ID: s.ID(), ID: s.ID(),
}) })
@ -299,9 +311,13 @@ func (s *shimTask) delete(ctx context.Context, removeTask func(ctx context.Conte
removeTask(ctx, s.ID()) removeTask(ctx, s.ID())
} }
// Don't shutdown sandbox as there may be other containers running.
// Let controller decide when to shutdown.
if !sandboxed {
if err := s.waitShutdown(ctx); err != nil { if err := s.waitShutdown(ctx); err != nil {
log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to shutdown shim task") log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to shutdown shim task")
} }
}
if err := s.shim.delete(ctx); err != nil { if err := s.shim.delete(ctx); err != nil {
log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete shim") log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete shim")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
package containerd.task.v2;
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";
import weak "gogoproto/gogo.proto";
import "github.com/containerd/containerd/api/types/mount.proto";
// Sandbox is an optional interface that shim may implement to support sandboxes environments.
// A typical example of sandbox is microVM or pause container - an entity that groups containers and/or
// holds resources relevant for this group.
service Sandbox {
// StartSandbox will create/start a new sandbox instance
rpc StartSandbox(StartSandboxRequest) returns (StartSandboxResponse);
// StopSandbox will stop existing sandbox instance
rpc StopSandbox(StopSandboxRequest) returns (StopSandboxResponse);
// WaitSandbox blocks until sanbox exits.
rpc WaitSandbox(WaitSandboxRequest) returns (WaitSandboxResponse);
// Update can be used to amend the state of currently running sandbox instance (depending on
// implementation this can be used to resize/reacquire needed resources like RAM/CPU).
rpc UpdateSandbox(UpdateSandboxRequest) returns (UpdateSandboxResponse);
// PauseSandbox will suspend currently running sandbox instance.
rpc PauseSandbox(PauseSandboxRequest) returns (PauseSandboxResponse);
// ResumeSandbox will resuyme previously suspended sandbox instance.
rpc ResumeSandbox(ResumeSandboxRequest) returns (ResumeSandboxResponse);
// SandboxStatus will return current status of the running sandbox instance
rpc SandboxStatus(SandboxStatusRequest) returns (SandboxStatusResponse);
// PingSandbox is a lightweight API call to check whether sandbox alive.
rpc PingSandbox(PingRequest) returns (PingResponse);
}
message StartSandboxRequest {
string sandbox_id = 1;
string bundle_path = 2;
repeated containerd.types.Mount rootfs = 3;
google.protobuf.Any options = 4;
}
message StartSandboxResponse {
uint32 pid = 1;
}
message StopSandboxRequest {
string sandbox_id = 1;
uint32 timeout_secs = 2;
}
message StopSandboxResponse {}
message UpdateSandboxRequest {
string sandbox_id = 1;
google.protobuf.Any resources = 2;
map<string, string> annotations = 3;
}
message WaitSandboxRequest {
string sandbox_id = 1;
}
message WaitSandboxResponse {
uint32 exit_status = 1;
google.protobuf.Timestamp exited_at = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
}
message UpdateSandboxResponse {}
message SandboxStatusRequest {
string sandbox_id = 1;
}
message PauseSandboxRequest {
string sandbox_id = 1;
}
message PauseSandboxResponse {}
message ResumeSandboxRequest {
string sandbox_id = 1;
}
message ResumeSandboxResponse {}
message SandboxStatusResponse {
string id = 1;
uint32 pid = 2;
string state = 3;
uint32 exit_status = 4;
google.protobuf.Timestamp exited_at = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
google.protobuf.Any extra = 6;
}
message PingRequest {
string sandbox_id = 1;
}
message PingResponse {}

239
sandbox.go Normal file
View File

@ -0,0 +1,239 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
"context"
"fmt"
"time"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
api "github.com/containerd/containerd/sandbox"
"github.com/containerd/typeurl"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
)
// Sandbox is a high level client to containerd's sandboxes.
type Sandbox interface {
// ID is a sandbox identifier
ID() string
// PID returns sandbox's process PID or error if its not yet started.
PID() (uint32, error)
// NewContainer creates new container that will belong to this sandbox
NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error)
// Labels returns the labels set on the sandbox
Labels(ctx context.Context) (map[string]string, error)
// Start starts new sandbox instance
Start(ctx context.Context) error
// Stop sends stop request to the shim instance.
Stop(ctx context.Context) error
// Wait blocks until sandbox process exits.
Wait(ctx context.Context) (<-chan ExitStatus, error)
// Delete removes sandbox from the metadata store.
Delete(ctx context.Context) error
}
type sandboxClient struct {
pid *uint32
client *Client
metadata api.Sandbox
}
func (s *sandboxClient) ID() string {
return s.metadata.ID
}
func (s *sandboxClient) PID() (uint32, error) {
if s.pid == nil {
return 0, fmt.Errorf("sandbox not started")
}
return *s.pid, nil
}
func (s *sandboxClient) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) {
return s.client.NewContainer(ctx, id, append(opts, WithSandbox(s.ID()))...)
}
func (s *sandboxClient) Labels(ctx context.Context) (map[string]string, error) {
sandbox, err := s.client.SandboxStore().Get(ctx, s.ID())
if err != nil {
return nil, err
}
return sandbox.Labels, nil
}
func (s *sandboxClient) Start(ctx context.Context) error {
pid, err := s.client.SandboxController().Start(ctx, s.ID())
if err != nil {
return err
}
s.pid = &pid
return nil
}
func (s *sandboxClient) Wait(ctx context.Context) (<-chan ExitStatus, error) {
c := make(chan ExitStatus, 1)
go func() {
defer close(c)
resp, err := s.client.SandboxController().Wait(ctx, s.ID())
if err != nil {
c <- ExitStatus{
code: UnknownExitStatus,
err: err,
}
return
}
c <- ExitStatus{
code: resp.ExitStatus,
exitedAt: resp.ExitedAt,
}
}()
return c, nil
}
func (s *sandboxClient) Stop(ctx context.Context) error {
return s.client.SandboxController().Shutdown(ctx, s.ID())
}
func (s *sandboxClient) Delete(ctx context.Context) error {
return s.client.SandboxStore().Delete(ctx, s.ID())
}
// NewSandbox creates new sandbox client
func (c *Client) NewSandbox(ctx context.Context, sandboxID string, opts ...NewSandboxOpts) (Sandbox, error) {
if sandboxID == "" {
return nil, errors.New("sandbox ID must be specified")
}
newSandbox := api.Sandbox{
ID: sandboxID,
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
}
for _, opt := range opts {
if err := opt(ctx, c, &newSandbox); err != nil {
return nil, err
}
}
metadata, err := c.SandboxStore().Create(ctx, newSandbox)
if err != nil {
return nil, err
}
return &sandboxClient{
pid: nil, // Not yet started
client: c,
metadata: metadata,
}, nil
}
// LoadSandbox laods existing sandbox metadata object using the id
func (c *Client) LoadSandbox(ctx context.Context, id string) (Sandbox, error) {
sandbox, err := c.SandboxStore().Get(ctx, id)
if err != nil {
return nil, err
}
status, err := c.SandboxController().Status(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to load sandbox %s, status request failed: %w", id, err)
}
return &sandboxClient{
pid: &status.Pid,
client: c,
metadata: sandbox,
}, nil
}
// NewSandboxOpts is a sandbox options and extensions to be provided by client
type NewSandboxOpts func(ctx context.Context, client *Client, sandbox *api.Sandbox) error
// WithSandboxRuntime allows a user to specify the runtime to be used to run a sandbox
func WithSandboxRuntime(name string, options interface{}) NewSandboxOpts {
return func(ctx context.Context, client *Client, s *api.Sandbox) error {
if options == nil {
options = &types.Empty{}
}
opts, err := typeurl.MarshalAny(options)
if err != nil {
return errors.Wrap(err, "failed to marshal sandbox runtime options")
}
s.Runtime = api.RuntimeOpts{
Name: name,
Options: opts,
}
return nil
}
}
// WithSandboxSpec will provide the sandbox runtime spec
func WithSandboxSpec(s *oci.Spec, opts ...oci.SpecOpts) NewSandboxOpts {
return func(ctx context.Context, client *Client, sandbox *api.Sandbox) error {
c := &containers.Container{ID: sandbox.ID}
if err := oci.ApplyOpts(ctx, client, c, s, opts...); err != nil {
return err
}
spec, err := typeurl.MarshalAny(s)
if err != nil {
return errors.Wrap(err, "failed to marshal spec")
}
sandbox.Spec = spec
return nil
}
}
// WithSandboxExtension attaches an extension to sandbox
func WithSandboxExtension(name string, ext interface{}) NewSandboxOpts {
return func(ctx context.Context, client *Client, s *api.Sandbox) error {
if s.Extensions == nil {
s.Extensions = make(map[string]typeurl.Any)
}
any, err := typeurl.MarshalAny(ext)
if err != nil {
return errors.Wrap(err, "failed to marshal sandbox extension")
}
s.Extensions[name] = any
return err
}
}
// WithSandboxLabels attaches map of labels to sandbox
func WithSandboxLabels(labels map[string]string) NewSandboxOpts {
return func(ctx context.Context, client *Client, sandbox *api.Sandbox) error {
sandbox.Labels = labels
return nil
}
}

41
sandbox/controller.go Normal file
View File

@ -0,0 +1,41 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"context"
"github.com/containerd/containerd/api/services/sandbox/v1"
)
// Controller is an interface to manage sandboxes at runtime.
// When running in sandbox mode, shim expected to implement `SandboxService`.
// Shim lifetimes are now managed manually via sandbox API by the containerd's client.
type Controller interface {
// Start will start new sandbox instance.
// containerd will run new shim runtime instance and will invoke Start to create a sandbox process.
// This routine must be invoked before scheduling containers on this instance.
// Once started clients may run containers via Task service (additionally specifying sandbox id the container will belong to).
Start(ctx context.Context, sandboxID string) (uint32, error)
// Shutdown deletes and cleans all tasks and sandbox instance.
Shutdown(ctx context.Context, sandboxID string) error
// Wait blocks until sandbox process exits.
Wait(ctx context.Context, sandboxID string) (*sandbox.ControllerWaitResponse, error)
// Status will query sandbox process status. It is heavier than Ping call and must be used whenever you need to
// gather metadata about current sandbox state (status, uptime, resource use, etc).
Status(ctx context.Context, sandboxID string) (*sandbox.ControllerStatusResponse, error)
}

68
sandbox/helpers.go Normal file
View File

@ -0,0 +1,68 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/protobuf"
"github.com/containerd/typeurl"
gogo_types "github.com/gogo/protobuf/types"
)
// ToProto will map Sandbox struct to it's protobuf definition
func ToProto(sandbox *Sandbox) types.Sandbox {
extensions := make(map[string]gogo_types.Any)
for k, v := range sandbox.Extensions {
extensions[k] = *protobuf.FromAny(v)
}
return types.Sandbox{
SandboxID: sandbox.ID,
Runtime: types.Sandbox_Runtime{
Name: sandbox.Runtime.Name,
Options: protobuf.FromAny(sandbox.Runtime.Options),
},
Labels: sandbox.Labels,
CreatedAt: sandbox.CreatedAt,
UpdatedAt: sandbox.UpdatedAt,
Extensions: extensions,
Spec: protobuf.FromAny(sandbox.Spec),
}
}
// FromProto map protobuf sandbox definition to Sandbox struct
func FromProto(sandboxpb *types.Sandbox) Sandbox {
runtime := RuntimeOpts{
Name: sandboxpb.Runtime.Name,
Options: sandboxpb.Runtime.Options,
}
extensions := make(map[string]typeurl.Any)
for k, v := range sandboxpb.Extensions {
v := v
extensions[k] = &v
}
return Sandbox{
ID: sandboxpb.SandboxID,
Labels: sandboxpb.Labels,
Runtime: runtime,
Spec: sandboxpb.Spec,
CreatedAt: sandboxpb.CreatedAt,
UpdatedAt: sandboxpb.UpdatedAt,
Extensions: extensions,
}
}

66
sandbox/store.go Normal file
View File

@ -0,0 +1,66 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"context"
"time"
"github.com/containerd/typeurl"
)
// Sandbox is an object stored in metadata database
type Sandbox struct {
// ID uniquely identifies the sandbox in a namespace
ID string
// Labels provide metadata extension for a sandbox
Labels map[string]string
// Runtime shim to use for this sandbox
Runtime RuntimeOpts
// Spec carries the runtime specification used to implement the sandbox
Spec typeurl.Any
// CreatedAt is the time at which the sandbox was created
CreatedAt time.Time
// UpdatedAt is the time at which the sandbox was updated
UpdatedAt time.Time
// Extensions stores client-specified metadata
Extensions map[string]typeurl.Any
}
// RuntimeOpts holds runtime specific information
type RuntimeOpts struct {
Name string
Options typeurl.Any
}
// Store is a storage interface for sandbox metadata objects
type Store interface {
// Create a sandbox record in the store
Create(ctx context.Context, sandbox Sandbox) (Sandbox, error)
// Update the sandbox with the provided sandbox object and fields
Update(ctx context.Context, sandbox Sandbox, fieldpaths ...string) (Sandbox, error)
// Get sandbox metadata using the id
Get(ctx context.Context, id string) (Sandbox, error)
// List returns sandboxes that match one or more of the provided filters
List(ctx context.Context, filters ...string) ([]Sandbox, error)
// Delete a sandbox from metadata store using the id
Delete(ctx context.Context, id string) error
}

73
sandbox_controller.go Normal file
View File

@ -0,0 +1,73 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
"context"
api "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/errdefs"
sb "github.com/containerd/containerd/sandbox"
)
// sandboxRemoteController is a low level GRPC client for containerd's sandbox controller service
type sandboxRemoteController struct {
client api.ControllerClient
}
var _ sb.Controller = (*sandboxRemoteController)(nil)
// NewSandboxRemoteController creates client for sandbox controller
func NewSandboxRemoteController(client api.ControllerClient) sb.Controller {
return &sandboxRemoteController{client: client}
}
func (s *sandboxRemoteController) Start(ctx context.Context, sandboxID string) (uint32, error) {
resp, err := s.client.Start(ctx, &api.ControllerStartRequest{SandboxID: sandboxID})
if err != nil {
return 0, errdefs.FromGRPC(err)
}
return resp.Pid, nil
}
func (s *sandboxRemoteController) Shutdown(ctx context.Context, sandboxID string) error {
_, err := s.client.Shutdown(ctx, &api.ControllerShutdownRequest{SandboxID: sandboxID})
if err != nil {
return errdefs.FromGRPC(err)
}
return nil
}
func (s *sandboxRemoteController) Wait(ctx context.Context, sandboxID string) (*api.ControllerWaitResponse, error) {
resp, err := s.client.Wait(ctx, &api.ControllerWaitRequest{SandboxID: sandboxID})
if err != nil {
return nil, errdefs.FromGRPC(err)
}
return resp, nil
}
func (s *sandboxRemoteController) Status(ctx context.Context, sandboxID string) (*api.ControllerStatusResponse, error) {
resp, err := s.client.Status(ctx, &api.ControllerStatusRequest{SandboxID: sandboxID})
if err != nil {
return nil, errdefs.FromGRPC(err)
}
return resp, nil
}

95
sandbox_store.go Normal file
View File

@ -0,0 +1,95 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package containerd
import (
"context"
api "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/errdefs"
sb "github.com/containerd/containerd/sandbox"
)
// remoteSandboxStore is a low-level containerd client to manage sandbox environments metadata
type remoteSandboxStore struct {
client api.StoreClient
}
var _ sb.Store = (*remoteSandboxStore)(nil)
// NewRemoteSandboxStore create client for sandbox store
func NewRemoteSandboxStore(client api.StoreClient) sb.Store {
return &remoteSandboxStore{client: client}
}
func (s *remoteSandboxStore) Create(ctx context.Context, sandbox sb.Sandbox) (sb.Sandbox, error) {
resp, err := s.client.Create(ctx, &api.StoreCreateRequest{
Sandbox: sb.ToProto(&sandbox),
})
if err != nil {
return sb.Sandbox{}, errdefs.FromGRPC(err)
}
return sb.FromProto(&resp.Sandbox), nil
}
func (s *remoteSandboxStore) Update(ctx context.Context, sandbox sb.Sandbox, fieldpaths ...string) (sb.Sandbox, error) {
resp, err := s.client.Update(ctx, &api.StoreUpdateRequest{
Sandbox: sb.ToProto(&sandbox),
Fields: fieldpaths,
})
if err != nil {
return sb.Sandbox{}, errdefs.FromGRPC(err)
}
return sb.FromProto(&resp.Sandbox), nil
}
func (s *remoteSandboxStore) Get(ctx context.Context, id string) (sb.Sandbox, error) {
resp, err := s.client.Get(ctx, &api.StoreGetRequest{
SandboxID: id,
})
if err != nil {
return sb.Sandbox{}, errdefs.FromGRPC(err)
}
return sb.FromProto(resp.Sandbox), nil
}
func (s *remoteSandboxStore) List(ctx context.Context, filters ...string) ([]sb.Sandbox, error) {
resp, err := s.client.List(ctx, &api.StoreListRequest{
Filters: filters,
})
if err != nil {
return nil, errdefs.FromGRPC(err)
}
out := make([]sb.Sandbox, len(resp.List))
for i := range resp.List {
out[i] = sb.FromProto(&resp.List[i])
}
return out, nil
}
func (s *remoteSandboxStore) Delete(ctx context.Context, id string) error {
_, err := s.client.Delete(ctx, &api.StoreDeleteRequest{
SandboxID: id,
})
return errdefs.FromGRPC(err)
}

View File

@ -22,12 +22,14 @@ import (
imagesapi "github.com/containerd/containerd/api/services/images/v1" imagesapi "github.com/containerd/containerd/api/services/images/v1"
introspectionapi "github.com/containerd/containerd/api/services/introspection/v1" introspectionapi "github.com/containerd/containerd/api/services/introspection/v1"
namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1" namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1"
sandboxsapi "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/api/services/tasks/v1" "github.com/containerd/containerd/api/services/tasks/v1"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases" "github.com/containerd/containerd/leases"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/sandbox"
"github.com/containerd/containerd/services/introspection" "github.com/containerd/containerd/services/introspection"
"github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots"
) )
@ -43,6 +45,8 @@ type services struct {
eventService EventService eventService EventService
leasesService leases.Manager leasesService leases.Manager
introspectionService introspection.Service introspectionService introspection.Service
sandboxStore sandbox.Store
sandboxController sandbox.Controller
} }
// ServicesOpt allows callers to set options on the services // ServicesOpt allows callers to set options on the services
@ -155,3 +159,17 @@ func WithIntrospectionService(in introspection.Service) ServicesOpt {
s.introspectionService = in s.introspectionService = in
} }
} }
// WithSandboxStore sets the sandbox store.
func WithSandboxStore(client sandboxsapi.StoreClient) ServicesOpt {
return func(s *services) {
s.sandboxStore = NewRemoteSandboxStore(client)
}
}
// WithSandboxController sets the sandbox controller.
func WithSandboxController(client sandboxsapi.ControllerClient) ServicesOpt {
return func(s *services) {
s.sandboxController = NewSandboxRemoteController(client)
}
}

View File

@ -54,6 +54,7 @@ func containerToProto(container *containers.Container) api.Container {
CreatedAt: container.CreatedAt, CreatedAt: container.CreatedAt,
UpdatedAt: container.UpdatedAt, UpdatedAt: container.UpdatedAt,
Extensions: extensions, Extensions: extensions,
Sandbox: container.SandboxID,
} }
} }
@ -79,5 +80,6 @@ func containerFromProto(containerpb *api.Container) containers.Container {
Snapshotter: containerpb.Snapshotter, Snapshotter: containerpb.Snapshotter,
SnapshotKey: containerpb.SnapshotKey, SnapshotKey: containerpb.SnapshotKey,
Extensions: extensions, Extensions: extensions,
SandboxID: containerpb.Sandbox,
} }
} }

View File

@ -0,0 +1,196 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"context"
"fmt"
api "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/events"
"github.com/containerd/containerd/events/exchange"
"github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/runtime"
v2 "github.com/containerd/containerd/runtime/v2"
"github.com/containerd/containerd/runtime/v2/task"
proto "github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/containerd/sandbox"
"github.com/containerd/containerd/services"
"google.golang.org/grpc"
)
func init() {
plugin.Register(&plugin.Registration{
Type: plugin.ServicePlugin,
ID: services.SandboxControllerService,
Requires: []plugin.Type{
plugin.RuntimePluginV2,
plugin.MetadataPlugin,
plugin.EventPlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
shimPlugin, err := ic.GetByID(plugin.RuntimePluginV2, "shim")
if err != nil {
return nil, err
}
metadataPlugin, err := ic.Get(plugin.MetadataPlugin)
if err != nil {
return nil, err
}
exchangePlugin, err := ic.GetByID(plugin.EventPlugin, "exchange")
if err != nil {
return nil, err
}
var (
shims = shimPlugin.(*v2.ShimManager)
publisher = exchangePlugin.(*exchange.Exchange)
db = metadataPlugin.(*metadata.DB)
store = metadata.NewSandboxStore(db)
)
return &controllerLocal{
shims: shims,
store: store,
publisher: publisher,
}, nil
},
})
}
type controllerLocal struct {
shims *v2.ShimManager
store sandbox.Store
publisher events.Publisher
}
var _ api.ControllerClient = (*controllerLocal)(nil)
func (c *controllerLocal) Start(ctx context.Context, in *api.ControllerStartRequest, opts ...grpc.CallOption) (*api.ControllerStartResponse, error) {
if _, err := c.shims.Get(ctx, in.SandboxID); err == nil {
return nil, fmt.Errorf("sandbox %s already running: %w", in.SandboxID, errdefs.ErrAlreadyExists)
}
info, err := c.store.Get(ctx, in.SandboxID)
if err != nil {
return nil, fmt.Errorf("failed to query sandbox metadata from store: %w", err)
}
shim, err := c.shims.Start(ctx, in.SandboxID, runtime.CreateOpts{
Spec: info.Spec,
RuntimeOptions: info.Runtime.Options,
Runtime: info.Runtime.Name,
TaskOptions: nil,
})
if err != nil {
return nil, fmt.Errorf("failed to start new sandbox: %w", err)
}
svc := task.NewSandboxClient(shim.Client())
resp, err := svc.StartSandbox(ctx, &proto.StartSandboxRequest{
SandboxID: in.SandboxID,
BundlePath: shim.Bundle(),
Rootfs: in.Rootfs,
Options: in.Options,
})
if err != nil {
return nil, fmt.Errorf("failed to start sandbox %s: %w", in.SandboxID, err)
}
return &api.ControllerStartResponse{
SandboxID: in.SandboxID,
Pid: resp.Pid,
}, nil
}
func (c *controllerLocal) Shutdown(ctx context.Context, in *api.ControllerShutdownRequest, opts ...grpc.CallOption) (*api.ControllerShutdownResponse, error) {
svc, err := c.getSandbox(ctx, in.SandboxID)
if err != nil {
return nil, err
}
if _, err := svc.StopSandbox(ctx, &proto.StopSandboxRequest{
SandboxID: in.SandboxID,
TimeoutSecs: in.TimeoutSecs,
}); err != nil {
return nil, fmt.Errorf("failed to stop sandbox: %w", err)
}
if err := c.shims.Delete(ctx, in.SandboxID); err != nil {
return nil, fmt.Errorf("failed to delete sandbox shim: %w", err)
}
return &api.ControllerShutdownResponse{}, nil
}
func (c *controllerLocal) Wait(ctx context.Context, in *api.ControllerWaitRequest, opts ...grpc.CallOption) (*api.ControllerWaitResponse, error) {
svc, err := c.getSandbox(ctx, in.SandboxID)
if err != nil {
return nil, err
}
resp, err := svc.WaitSandbox(ctx, &proto.WaitSandboxRequest{
SandboxID: in.SandboxID,
})
if err != nil {
return nil, fmt.Errorf("failed to wait sandbox %s: %w", in.SandboxID, err)
}
return &api.ControllerWaitResponse{
ExitStatus: resp.ExitStatus,
ExitedAt: resp.ExitedAt,
}, nil
}
func (c *controllerLocal) Status(ctx context.Context, in *api.ControllerStatusRequest, opts ...grpc.CallOption) (*api.ControllerStatusResponse, error) {
svc, err := c.getSandbox(ctx, in.SandboxID)
if err != nil {
return nil, err
}
resp, err := svc.SandboxStatus(ctx, &proto.SandboxStatusRequest{SandboxID: in.SandboxID})
if err != nil {
return nil, fmt.Errorf("failed to query sandbox %s status: %w", in.SandboxID, err)
}
return &api.ControllerStatusResponse{
ID: resp.ID,
Pid: resp.Pid,
State: resp.State,
ExitStatus: resp.ExitStatus,
ExitedAt: resp.ExitedAt,
Extra: resp.Extra,
}, nil
}
func (c *controllerLocal) getSandbox(ctx context.Context, id string) (task.SandboxService, error) {
shim, err := c.shims.Get(ctx, id)
if err != nil {
return nil, errdefs.ErrNotFound
}
svc := task.NewSandboxClient(shim.Client())
return svc, nil
}

View File

@ -0,0 +1,89 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"context"
"errors"
api "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services"
"google.golang.org/grpc"
)
func init() {
plugin.Register(&plugin.Registration{
Type: plugin.GRPCPlugin,
ID: "sandbox-controllers",
Requires: []plugin.Type{
plugin.ServicePlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
plugins, err := ic.GetByType(plugin.ServicePlugin)
if err != nil {
return nil, err
}
p, ok := plugins[services.SandboxControllerService]
if !ok {
return nil, errors.New("sandbox service not found")
}
i, err := p.Instance()
if err != nil {
return nil, err
}
return &controllerService{
local: i.(api.ControllerClient),
}, nil
},
})
}
type controllerService struct {
local api.ControllerClient
}
var _ api.ControllerServer = (*controllerService)(nil)
func (s *controllerService) Register(server *grpc.Server) error {
api.RegisterControllerServer(server, s)
return nil
}
func (s *controllerService) Start(ctx context.Context, req *api.ControllerStartRequest) (*api.ControllerStartResponse, error) {
log.G(ctx).WithField("req", req).Debug("start sandbox")
return s.local.Start(ctx, req)
}
func (s *controllerService) Shutdown(ctx context.Context, req *api.ControllerShutdownRequest) (*api.ControllerShutdownResponse, error) {
log.G(ctx).WithField("req", req).Debug("delete sandbox")
return s.local.Shutdown(ctx, req)
}
func (s *controllerService) Wait(ctx context.Context, req *api.ControllerWaitRequest) (*api.ControllerWaitResponse, error) {
log.G(ctx).WithField("req", req).Debug("wait sandbox")
return s.local.Wait(ctx, req)
}
func (s *controllerService) Status(ctx context.Context, req *api.ControllerStatusRequest) (*api.ControllerStatusResponse, error) {
log.G(ctx).WithField("req", req).Debug("sandbox status")
return s.local.Status(ctx, req)
}

View File

@ -0,0 +1,111 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"context"
"github.com/containerd/containerd/services"
"google.golang.org/grpc"
api "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/events"
"github.com/containerd/containerd/metadata"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/sandbox"
)
func init() {
plugin.Register(&plugin.Registration{
Type: plugin.ServicePlugin,
ID: services.SandboxStoreService,
Requires: []plugin.Type{
plugin.MetadataPlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
m, err := ic.Get(plugin.MetadataPlugin)
if err != nil {
return nil, err
}
db := m.(*metadata.DB)
return &sandboxLocal{
store: metadata.NewSandboxStore(db),
publisher: ic.Events,
}, nil
},
})
}
type sandboxLocal struct {
store sandbox.Store
publisher events.Publisher
}
var _ = (api.StoreClient)(&sandboxLocal{})
func (s *sandboxLocal) Create(ctx context.Context, in *api.StoreCreateRequest, _ ...grpc.CallOption) (*api.StoreCreateResponse, error) {
sb, err := s.store.Create(ctx, sandbox.FromProto(&in.Sandbox))
if err != nil {
return nil, errdefs.ToGRPC(err)
}
return &api.StoreCreateResponse{Sandbox: sandbox.ToProto(&sb)}, nil
}
func (s *sandboxLocal) Update(ctx context.Context, in *api.StoreUpdateRequest, _ ...grpc.CallOption) (*api.StoreUpdateResponse, error) {
sb, err := s.store.Update(ctx, sandbox.FromProto(&in.Sandbox), in.Fields...)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
return &api.StoreUpdateResponse{Sandbox: sandbox.ToProto(&sb)}, nil
}
func (s *sandboxLocal) Get(ctx context.Context, in *api.StoreGetRequest, _ ...grpc.CallOption) (*api.StoreGetResponse, error) {
resp, err := s.store.Get(ctx, in.SandboxID)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
desc := sandbox.ToProto(&resp)
return &api.StoreGetResponse{Sandbox: &desc}, nil
}
func (s *sandboxLocal) List(ctx context.Context, in *api.StoreListRequest, _ ...grpc.CallOption) (*api.StoreListResponse, error) {
resp, err := s.store.List(ctx, in.Filters...)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
list := make([]types.Sandbox, len(resp))
for i := range resp {
list[i] = sandbox.ToProto(&resp[i])
}
return &api.StoreListResponse{List: list}, nil
}
func (s *sandboxLocal) Delete(ctx context.Context, in *api.StoreDeleteRequest, _ ...grpc.CallOption) (*api.StoreDeleteResponse, error) {
if err := s.store.Delete(ctx, in.SandboxID); err != nil {
return nil, errdefs.ToGRPC(err)
}
return &api.StoreDeleteResponse{}, nil
}

View File

@ -0,0 +1,90 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandbox
import (
"context"
"errors"
"google.golang.org/grpc"
api "github.com/containerd/containerd/api/services/sandbox/v1"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/services"
)
func init() {
plugin.Register(&plugin.Registration{
Type: plugin.GRPCPlugin,
ID: "sandboxes",
Requires: []plugin.Type{
plugin.ServicePlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
plugins, err := ic.GetByType(plugin.ServicePlugin)
if err != nil {
return nil, err
}
p, ok := plugins[services.SandboxStoreService]
if !ok {
return nil, errors.New("sandbox store service not found")
}
i, err := p.Instance()
if err != nil {
return nil, err
}
return &sandboxService{local: i.(api.StoreClient)}, nil
},
})
}
type sandboxService struct {
local api.StoreClient
}
var _ api.StoreServer = (*sandboxService)(nil)
func (s *sandboxService) Register(server *grpc.Server) error {
api.RegisterStoreServer(server, s)
return nil
}
func (s *sandboxService) Create(ctx context.Context, req *api.StoreCreateRequest) (*api.StoreCreateResponse, error) {
log.G(ctx).WithField("req", req).Debug("create sandbox")
return s.local.Create(ctx, req)
}
func (s *sandboxService) Update(ctx context.Context, req *api.StoreUpdateRequest) (*api.StoreUpdateResponse, error) {
log.G(ctx).WithField("req", req).Debug("update sandbox")
return s.local.Update(ctx, req)
}
func (s *sandboxService) List(ctx context.Context, req *api.StoreListRequest) (*api.StoreListResponse, error) {
log.G(ctx).WithField("req", req).Debug("list sandboxes")
return s.local.List(ctx, req)
}
func (s *sandboxService) Get(ctx context.Context, req *api.StoreGetRequest) (*api.StoreGetResponse, error) {
log.G(ctx).WithField("req", req).Debug("get sandbox")
return s.local.Get(ctx, req)
}
func (s *sandboxService) Delete(ctx context.Context, req *api.StoreDeleteRequest) (*api.StoreDeleteResponse, error) {
log.G(ctx).WithField("req", req).Debug("delete sandbox")
return s.local.Delete(ctx, req)
}

View File

@ -33,4 +33,8 @@ const (
DiffService = "diff-service" DiffService = "diff-service"
// IntrospectionService is the id of introspection service // IntrospectionService is the id of introspection service
IntrospectionService = "introspection-service" IntrospectionService = "introspection-service"
// SandboxStoreService is the id of Sandbox's store service
SandboxStoreService = "sandbox-store-service"
// SandboxControllerService is the id of Sandbox's controller service
SandboxControllerService = "sandbox-controller-service"
) )

View File

@ -200,6 +200,7 @@ func (l *local) Create(ctx context.Context, r *api.CreateTaskRequest, _ ...grpc.
Runtime: container.Runtime.Name, Runtime: container.Runtime.Name,
RuntimeOptions: container.Runtime.Options, RuntimeOptions: container.Runtime.Options,
TaskOptions: r.Options, TaskOptions: r.Options,
SandboxID: container.SandboxID,
} }
if r.RuntimePath != "" { if r.RuntimePath != "" {
opts.Runtime = r.RuntimePath opts.Runtime = r.RuntimePath