Add GPU device request support (#84)

* add gpus to api.

* add gpus to gui.

* get gpus all configuration.

* correctly parse gpu device requests.
This commit is contained in:
Miroslav Šedivý
2023-01-11 21:58:30 +01:00
committed by GitHub
parent ac07cb5c74
commit bb8bf201fe
11 changed files with 153 additions and 50 deletions
+5
View File
@@ -349,6 +349,11 @@ components:
memory:
type: number
description: in bytes
gpus:
type: array
items:
type: string
example: all
RoomSettings:
type: object
+1 -1
View File
@@ -1 +1 @@
5.3.0-SNAPSHOT
6.3.0-SNAPSHOT
+27 -23
View File
@@ -13,13 +13,15 @@
*/
import { Configuration } from './configuration';
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import type { Configuration } from './configuration';
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
import type { RequestArgs } from './base';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base';
/**
*
@@ -47,14 +49,12 @@ export interface BrowserPolicy {
'content'?: BrowserPolicyContent;
}
/**
* @export
* @enum {string}
*/
export enum BrowserPolicyTypeEnum {
firefox = 'firefox',
chromium = 'chromium'
}
export const BrowserPolicyTypeEnum = {
firefox: 'firefox',
chromium: 'chromium'
} as const;
export type BrowserPolicyTypeEnum = typeof BrowserPolicyTypeEnum[keyof typeof BrowserPolicyTypeEnum];
/**
*
@@ -249,7 +249,7 @@ export interface RoomEntry {
*/
'is_outdated'?: boolean;
/**
*
* 0 when using mux
* @type {number}
* @memberof RoomEntry
*/
@@ -330,16 +330,14 @@ export interface RoomMount {
'container_path'?: string;
}
/**
* @export
* @enum {string}
*/
export enum RoomMountTypeEnum {
private = 'private',
template = 'template',
protected = 'protected',
public = 'public'
}
export const RoomMountTypeEnum = {
private: 'private',
template: 'template',
protected: 'protected',
public: 'public'
} as const;
export type RoomMountTypeEnum = typeof RoomMountTypeEnum[keyof typeof RoomMountTypeEnum];
/**
*
@@ -371,6 +369,12 @@ export interface RoomResources {
* @memberof RoomResources
*/
'memory'?: number;
/**
*
* @type {Array<string>}
* @memberof RoomResources
*/
'gpus'?: Array<string>;
}
/**
*
@@ -391,7 +395,7 @@ export interface RoomSettings {
*/
'neko_image'?: string;
/**
*
* 0 when using mux
* @type {number}
* @memberof RoomSettings
*/
+3 -2
View File
@@ -13,10 +13,11 @@
*/
import { Configuration } from "./configuration";
import type { Configuration } from './configuration';
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
export const BASE_PATH = "https://virtserver.swaggerhub.com/m1k1o/neko-rooms/1.0.0".replace(/\/+$/, "");
+28 -17
View File
@@ -13,9 +13,10 @@
*/
import { Configuration } from "./configuration";
import { RequiredError, RequestArgs } from "./base";
import { AxiosInstance } from 'axios';
import type { Configuration } from "./configuration";
import type { RequestArgs } from "./base";
import type { AxiosInstance, AxiosResponse } from 'axios';
import { RequiredError } from "./base";
/**
*
@@ -83,24 +84,34 @@ export const setOAuthToObject = async function (object: any, name: string, scope
}
}
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
if (typeof parameter === "object") {
if (Array.isArray(parameter)) {
(parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
}
else {
Object.keys(parameter).forEach(currentKey =>
setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
);
}
}
else {
if (urlSearchParams.has(key)) {
urlSearchParams.append(key, parameter);
}
else {
urlSearchParams.set(key, parameter);
}
}
}
/**
*
* @export
*/
export const setSearchParams = function (url: URL, ...objects: any[]) {
const searchParams = new URLSearchParams(url.search);
for (const object of objects) {
for (const key in object) {
if (Array.isArray(object[key])) {
searchParams.delete(key);
for (const item of object[key]) {
searchParams.append(key, item);
}
} else {
searchParams.set(key, object[key]);
}
}
}
setFlattenedQueryParams(searchParams, objects);
url.search = searchParams.toString();
}
@@ -131,8 +142,8 @@ export const toPathString = function (url: URL) {
* @export
*/
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
return axios.request(axiosRequestArgs);
return axios.request<T, R>(axiosRequestArgs);
};
}
+1
View File
@@ -173,6 +173,7 @@
<tr><th> Memory </th><td :title="settings.resources.memory"><span v-if="settings.resources.memory">{{ settings.resources.memory | memory }}</span><i v-else>--undefined--</i></td></tr>
<tr><th> CPU Shares </th><td :title="settings.resources.cpu_shares"><span v-if="settings.resources.cpu_shares">{{ settings.resources.cpu_shares }}</span><i v-else>--undefined--</i></td></tr>
<tr><th> Shared memory </th><td :title="settings.resources.shm_size">{{ settings.resources.shm_size | memory }}</td></tr>
<tr><th> GPUs </th><td><span v-if="settings.resources.gpus && settings.resources.gpus.length > 0">{{ settings.resources.gpus.join(",") }}</span><i v-else>--not used--</i></td></tr>
</tbody>
</template>
</v-simple-table>
+7
View File
@@ -358,6 +358,13 @@
</template>
</v-slider>
</v-col>
<v-col>
<v-checkbox
@change="$set(data.resources, 'gpus', $event ? ['all'] : [])"
label="Enable GPU support"
class="shrink ml-2 mt-0"
></v-checkbox>
</v-col>
</v-row>
</template>
+1
View File
@@ -28,6 +28,7 @@ require (
)
require (
github.com/docker/cli v20.10.22+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
+2
View File
@@ -104,6 +104,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/cli v20.10.22+incompatible h1:0E7UqWPcn4SlvLImMHyh6xwyNRUGdPxhstpHeh0bFL0=
github.com/docker/cli v20.10.22+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE=
+73 -3
View File
@@ -1,7 +1,9 @@
package room
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"os"
@@ -9,6 +11,7 @@ import (
"path/filepath"
"strings"
"github.com/docker/cli/opts"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
dockerMount "github.com/docker/docker/api/types/mount"
@@ -388,6 +391,31 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
paths[mount.ContainerPath] = true
}
//
// Set container device requests
//
var deviceRequests []container.DeviceRequest
if len(settings.Resources.Gpus) > 0 {
gpuOpts := opts.GpuOpts{}
// convert to csv
var buf bytes.Buffer
w := csv.NewWriter(&buf)
if err := w.Write(settings.Resources.Gpus); err != nil {
return "", err
}
w.Flush()
// set GPU opts
if err := gpuOpts.Set(buf.String()); err != nil {
return "", err
}
deviceRequests = append(deviceRequests, gpuOpts.Value()...)
}
//
// Set container configs
//
@@ -430,9 +458,10 @@ func (manager *RoomManagerCtx) Create(settings types.RoomSettings) (string, erro
Mounts: mounts,
// Resources contains container's resources (cgroups config, ulimits...)
Resources: container.Resources{
CPUShares: settings.Resources.CPUShares,
NanoCPUs: settings.Resources.NanoCPUs,
Memory: settings.Resources.Memory,
CPUShares: settings.Resources.CPUShares,
NanoCPUs: settings.Resources.NanoCPUs,
Memory: settings.Resources.Memory,
DeviceRequests: deviceRequests,
},
// Privileged
Privileged: isPrivilegedImage,
@@ -556,11 +585,52 @@ func (manager *RoomManagerCtx) GetSettings(id string) (*types.RoomSettings, erro
var roomResources types.RoomResources
if container.HostConfig != nil {
gpus := []string{}
for _, req := range container.HostConfig.DeviceRequests {
var isGpu bool
var caps []string
for _, cc := range req.Capabilities {
for _, c := range cc {
if c == "gpu" {
isGpu = true
continue
}
caps = append(caps, c)
}
}
if !isGpu {
continue
}
if req.Count > 1 {
gpus = append(gpus, fmt.Sprintf("count=%d", req.Count))
} else if req.Count == -1 {
gpus = append(gpus, "all")
}
if req.Driver != "" {
gpus = append(gpus, fmt.Sprintf("driver=%s", req.Driver))
}
if len(req.DeviceIDs) > 0 {
gpus = append(gpus, fmt.Sprintf("device=%s", strings.Join(req.DeviceIDs, ",")))
}
if len(caps) > 0 {
gpus = append(gpus, fmt.Sprintf("capabilities=%s", strings.Join(caps, ",")))
}
var opts []string
for key, val := range req.Options {
opts = append(opts, fmt.Sprintf("%s=%s", key, val))
}
if len(opts) > 0 {
gpus = append(gpus, fmt.Sprintf("options=%s", strings.Join(opts, ",")))
}
}
roomResources = types.RoomResources{
CPUShares: container.HostConfig.CPUShares,
NanoCPUs: container.HostConfig.NanoCPUs,
ShmSize: container.HostConfig.ShmSize,
Memory: container.HostConfig.Memory,
Gpus: gpus,
}
}
+5 -4
View File
@@ -60,10 +60,11 @@ type RoomMount struct {
}
type RoomResources struct {
CPUShares int64 `json:"cpu_shares"` // relative weight vs. other containers
NanoCPUs int64 `json:"nano_cpus"` // in units of 10^-9 CPUs
ShmSize int64 `json:"shm_size"` // in bytes
Memory int64 `json:"memory"` // in bytes
CPUShares int64 `json:"cpu_shares"` // relative weight vs. other containers
NanoCPUs int64 `json:"nano_cpus"` // in units of 10^-9 CPUs
ShmSize int64 `json:"shm_size"` // in bytes
Memory int64 `json:"memory"` // in bytes
Gpus []string `json:"gpus"` // gpu opts
}
type RoomSettings struct {