mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
chore: refactor device mapping to be compliant with Compose (CDI requests)
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||
)
|
||||
|
||||
func ServiceSpecFromCompose(project *types.Project, serviceName string) (api.ServiceSpec, error) {
|
||||
@@ -140,7 +141,27 @@ func resourcesFromCompose(service types.ServiceConfig) api.ContainerResources {
|
||||
Memory: int64(service.MemLimit),
|
||||
MemoryReservation: int64(service.MemReservation),
|
||||
Ulimits: ulimitsFromCompose(service.Ulimits),
|
||||
DeviceMappings: devicesFromCompose(service.Devices),
|
||||
}
|
||||
|
||||
// Convert device mappings, separating CDI devices from regular device mappings.
|
||||
// CDI devices are identified when Source == Target and the source is a qualified CDI name.
|
||||
var cdiDeviceNames []string
|
||||
for _, dev := range service.Devices {
|
||||
if dev.Source == dev.Target && cdi.IsQualifiedName(dev.Source) {
|
||||
cdiDeviceNames = append(cdiDeviceNames, dev.Source)
|
||||
continue
|
||||
}
|
||||
resources.Devices = append(resources.Devices, api.DeviceMapping{
|
||||
HostPath: dev.Source,
|
||||
ContainerPath: dev.Target,
|
||||
CgroupPermissions: dev.Permissions,
|
||||
})
|
||||
}
|
||||
if len(cdiDeviceNames) > 0 {
|
||||
resources.DeviceReservations = append(resources.DeviceReservations, container.DeviceRequest{
|
||||
Driver: "cdi",
|
||||
DeviceIDs: cdiDeviceNames,
|
||||
})
|
||||
}
|
||||
|
||||
// Convert GPU device requests from compose format, appending "gpu" capability.
|
||||
@@ -336,28 +357,6 @@ func ulimitsFromCompose(ulimits map[string]*types.UlimitsConfig) map[string]api.
|
||||
return res
|
||||
}
|
||||
|
||||
func devicesFromCompose(composeDevices []types.DeviceMapping) []container.DeviceMapping {
|
||||
mappings := make([]container.DeviceMapping, 0, len(composeDevices))
|
||||
|
||||
for _, dev := range composeDevices {
|
||||
mapping := container.DeviceMapping{
|
||||
PathOnHost: dev.Source,
|
||||
PathInContainer: dev.Target,
|
||||
CgroupPermissions: dev.Permissions,
|
||||
}
|
||||
|
||||
if mapping.PathInContainer == "" {
|
||||
mapping.PathInContainer = mapping.PathOnHost
|
||||
}
|
||||
if mapping.CgroupPermissions == "" {
|
||||
mapping.CgroupPermissions = "rwm"
|
||||
}
|
||||
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
return mappings
|
||||
}
|
||||
|
||||
// validateServicesExtensions validates extension combinations across all services in the project.
|
||||
func validateServicesExtensions(project *types.Project) error {
|
||||
for _, service := range project.Services {
|
||||
|
||||
@@ -1053,12 +1053,13 @@ services:
|
||||
|
||||
func TestServiceSpecFromCompose_Devices(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
composeYAML string
|
||||
expectedMappings []container.DeviceMapping
|
||||
name string
|
||||
composeYAML string
|
||||
expectedDevices []api.DeviceMapping
|
||||
expectedReservations []container.DeviceRequest
|
||||
}{
|
||||
{
|
||||
name: "devices_simple",
|
||||
name: "simple device",
|
||||
composeYAML: `
|
||||
services:
|
||||
test:
|
||||
@@ -1066,16 +1067,12 @@ services:
|
||||
devices:
|
||||
- /dev/dri
|
||||
`,
|
||||
expectedMappings: []container.DeviceMapping{
|
||||
{
|
||||
PathOnHost: "/dev/dri",
|
||||
PathInContainer: "/dev/dri",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
expectedDevices: []api.DeviceMapping{
|
||||
{HostPath: "/dev/dri", ContainerPath: "/dev/dri", CgroupPermissions: "rwm"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "devices_full",
|
||||
name: "device with target and permissions",
|
||||
composeYAML: `
|
||||
services:
|
||||
test:
|
||||
@@ -1083,16 +1080,12 @@ services:
|
||||
devices:
|
||||
- /dev/sda:/dev/xvda:r
|
||||
`,
|
||||
expectedMappings: []container.DeviceMapping{
|
||||
{
|
||||
PathOnHost: "/dev/sda",
|
||||
PathInContainer: "/dev/xvda",
|
||||
CgroupPermissions: "r",
|
||||
},
|
||||
expectedDevices: []api.DeviceMapping{
|
||||
{HostPath: "/dev/sda", ContainerPath: "/dev/xvda", CgroupPermissions: "r"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple_devices",
|
||||
name: "multiple devices",
|
||||
composeYAML: `
|
||||
services:
|
||||
test:
|
||||
@@ -1102,22 +1095,43 @@ services:
|
||||
- /dev/sda:/dev/xvda
|
||||
- "/dev/dri"
|
||||
`,
|
||||
expectedMappings: []container.DeviceMapping{
|
||||
{
|
||||
PathOnHost: "/dev/ttyUSB0",
|
||||
PathInContainer: "/dev/ttyUSB0",
|
||||
CgroupPermissions: "rw",
|
||||
},
|
||||
{
|
||||
PathOnHost: "/dev/sda",
|
||||
PathInContainer: "/dev/xvda",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
{
|
||||
PathOnHost: "/dev/dri",
|
||||
PathInContainer: "/dev/dri",
|
||||
CgroupPermissions: "rwm",
|
||||
},
|
||||
expectedDevices: []api.DeviceMapping{
|
||||
{HostPath: "/dev/ttyUSB0", ContainerPath: "/dev/ttyUSB0", CgroupPermissions: "rw"},
|
||||
{HostPath: "/dev/sda", ContainerPath: "/dev/xvda", CgroupPermissions: "rwm"},
|
||||
{HostPath: "/dev/dri", ContainerPath: "/dev/dri", CgroupPermissions: "rwm"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "CDI device",
|
||||
composeYAML: `
|
||||
services:
|
||||
test:
|
||||
image: nginx
|
||||
devices:
|
||||
- vendor.com/class=device1
|
||||
`,
|
||||
expectedReservations: []container.DeviceRequest{
|
||||
{Driver: "cdi", DeviceIDs: []string{"vendor.com/class=device1"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mixed CDI and regular devices",
|
||||
composeYAML: `
|
||||
services:
|
||||
test:
|
||||
image: nginx
|
||||
devices:
|
||||
- /dev/dri
|
||||
- vendor.com/class=device1
|
||||
- nvidia.com/gpu=0
|
||||
- /dev/sda:/dev/xvda:r
|
||||
`,
|
||||
expectedDevices: []api.DeviceMapping{
|
||||
{HostPath: "/dev/dri", ContainerPath: "/dev/dri", CgroupPermissions: "rwm"},
|
||||
{HostPath: "/dev/sda", ContainerPath: "/dev/xvda", CgroupPermissions: "r"},
|
||||
},
|
||||
expectedReservations: []container.DeviceRequest{
|
||||
{Driver: "cdi", DeviceIDs: []string{"vendor.com/class=device1", "nvidia.com/gpu=0"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1130,8 +1144,8 @@ services:
|
||||
spec, err := ServiceSpecFromCompose(project, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.expectedMappings, spec.Container.Resources.DeviceMappings,
|
||||
"DeviceMappings should match expected")
|
||||
assert.Equal(t, tt.expectedDevices, spec.Container.Resources.Devices)
|
||||
assert.Equal(t, tt.expectedReservations, spec.Container.Resources.DeviceReservations)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func EvalContainerSpecChange(current api.ServiceSpec, new api.ServiceSpec) Conta
|
||||
if !reflect.DeepEqual(current.Container.Resources.DeviceReservations, newResources.DeviceReservations) {
|
||||
return ContainerNeedsRecreate
|
||||
}
|
||||
if !reflect.DeepEqual(current.Container.Resources.DeviceMappings, newResources.DeviceMappings) {
|
||||
if !reflect.DeepEqual(current.Container.Resources.Devices, newResources.Devices) {
|
||||
return ContainerNeedsRecreate
|
||||
}
|
||||
|
||||
|
||||
@@ -1427,26 +1427,26 @@ func TestEvalContainerSpecChange_DeviceMappings(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "identical mapping",
|
||||
current: api.ContainerResources{DeviceMappings: []container.DeviceMapping{{PathOnHost: "/dev/foo", PathInContainer: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
new: api.ContainerResources{DeviceMappings: []container.DeviceMapping{{PathOnHost: "/dev/foo", PathInContainer: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
current: api.ContainerResources{Devices: []api.DeviceMapping{{HostPath: "/dev/foo", ContainerPath: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
new: api.ContainerResources{Devices: []api.DeviceMapping{{HostPath: "/dev/foo", ContainerPath: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
want: ContainerUpToDate,
|
||||
},
|
||||
{
|
||||
name: "add mapping",
|
||||
current: api.ContainerResources{},
|
||||
new: api.ContainerResources{DeviceMappings: []container.DeviceMapping{{PathOnHost: "/dev/foo", PathInContainer: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
new: api.ContainerResources{Devices: []api.DeviceMapping{{HostPath: "/dev/foo", ContainerPath: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
want: ContainerNeedsRecreate,
|
||||
},
|
||||
{
|
||||
name: "remove mapping",
|
||||
current: api.ContainerResources{DeviceMappings: []container.DeviceMapping{{PathOnHost: "/dev/foo", PathInContainer: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
current: api.ContainerResources{Devices: []api.DeviceMapping{{HostPath: "/dev/foo", ContainerPath: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
new: api.ContainerResources{},
|
||||
want: ContainerNeedsRecreate,
|
||||
},
|
||||
{
|
||||
name: "change mapping path",
|
||||
current: api.ContainerResources{DeviceMappings: []container.DeviceMapping{{PathOnHost: "/dev/foo", PathInContainer: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
new: api.ContainerResources{DeviceMappings: []container.DeviceMapping{{PathOnHost: "/dev/foo", PathInContainer: "/dev/bar", CgroupPermissions: "rwm"}}},
|
||||
current: api.ContainerResources{Devices: []api.DeviceMapping{{HostPath: "/dev/foo", ContainerPath: "/dev/foo", CgroupPermissions: "rwm"}}},
|
||||
new: api.ContainerResources{Devices: []api.DeviceMapping{{HostPath: "/dev/foo", ContainerPath: "/dev/bar", CgroupPermissions: "rwm"}}},
|
||||
want: ContainerNeedsRecreate,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user