mirror of
https://github.com/m1k1o/neko-rooms.git
synced 2026-11-02 22:51:34 -06:00
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:
@@ -349,6 +349,11 @@ components:
|
||||
memory:
|
||||
type: number
|
||||
description: in bytes
|
||||
gpus:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: all
|
||||
|
||||
RoomSettings:
|
||||
type: object
|
||||
|
||||
@@ -1 +1 @@
|
||||
5.3.0-SNAPSHOT
|
||||
6.3.0-SNAPSHOT
|
||||
+27
-23
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user