Skip to content

Commit 40ecdd0

Browse files
committed
Adds option to set memory and cpu constraint
- Adds fields in admin UI - Sets HostConfig.Memory and HostConfig.NanoCPUs fields for Docker Socket Proxy - Passes memory and nanoCPU values to HaRP Proxy (requires changes in HaRP repo) Signed-off-by: Hephi2 <[email protected]> Update built assets after source changes Signed-off-by: Hephi2 <[email protected]> Fixes resetting memory limit bug, MB -> MiB Signed-off-by: Hephi2 <[email protected]>
1 parent cf91cf0 commit 40ecdd0

File tree

8 files changed

+167
-12
lines changed

8 files changed

+167
-12
lines changed

js/app_api-adminSettings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/app_api-adminSettings.js.license

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ SPDX-License-Identifier: BSD-3-Clause
55
SPDX-License-Identifier: BSD-2-Clause
66
SPDX-License-Identifier: AGPL-3.0-or-later
77
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
8-
SPDX-License-Identifier: ( AGPL-3.0-or-later)
98
SPDX-FileCopyrightText: xiaokai <[email protected]>
109
SPDX-FileCopyrightText: rhysd <[email protected]>
1110
SPDX-FileCopyrightText: inline-style-parser developers
@@ -48,7 +47,6 @@ SPDX-FileCopyrightText: Antoni Andre <[email protected]>
4847
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
4948
SPDX-FileCopyrightText: Andris Reinman
5049
SPDX-FileCopyrightText: Andrea Giammarchi
51-
SPDX-FileCopyrightText: Alexander Piskun <[email protected]>
5250
SPDX-FileCopyrightText: @nextcloud/dialogs developers
5351

5452

@@ -434,6 +432,6 @@ This file is generated from multiple sources. Included packages:
434432
- zwitch
435433
- version: 2.0.4
436434
- license: MIT
437-
- app_api
435+
- nextcloud
438436
- version: 1.0.0
439-
- license: ( AGPL-3.0-or-later)
437+
- license: AGPL-3.0-or-later

js/app_api-adminSettings.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/app_api-filesplugin.js.license

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ SPDX-License-Identifier: MIT
22
SPDX-License-Identifier: ISC
33
SPDX-License-Identifier: GPL-3.0-or-later
44
SPDX-License-Identifier: BSD-3-Clause
5+
SPDX-License-Identifier: AGPL-3.0-or-later
56
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
6-
SPDX-License-Identifier: ( AGPL-3.0-or-later)
77
SPDX-FileCopyrightText: escape-html developers
88
SPDX-FileCopyrightText: Tobias Koppers @sokra
99
SPDX-FileCopyrightText: T. Jameson Little <[email protected]>
1010
SPDX-FileCopyrightText: Roman Shtylman <[email protected]>
1111
SPDX-FileCopyrightText: Roeland Jago Douma
12+
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
1213
SPDX-FileCopyrightText: Matt Zabriskie
1314
SPDX-FileCopyrightText: James Halliday
1415
SPDX-FileCopyrightText: GitHub Inc.
1516
SPDX-FileCopyrightText: Feross Aboukhadijeh
1617
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <[email protected]> (https://cure53.de/)
1718
SPDX-FileCopyrightText: Christoph Wurst
1819
SPDX-FileCopyrightText: Alkemics
19-
SPDX-FileCopyrightText: Alexander Piskun <[email protected]>
2020

2121

2222
This file is generated from multiple sources. Included packages:
@@ -65,6 +65,6 @@ This file is generated from multiple sources. Included packages:
6565
- webpack
6666
- version: 5.94.0
6767
- license: MIT
68-
- app_api
68+
- nextcloud
6969
- version: 1.0.0
70-
- license: ( AGPL-3.0-or-later)
70+
- license: AGPL-3.0-or-later

lib/DeployActions/DockerActions.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,12 @@ public function deployExAppHarp(ExApp $exApp, DaemonConfig $daemonConfig, array
160160
'restart_policy' => $this->appConfig->getValueString(Application::APP_ID, 'container_restart_policy', 'unless-stopped', lazy: true),
161161
'compute_device' => $computeDevice,
162162
'mount_points' => $mountPoints,
163-
'start_container' => true,
163+
'start_container' => true
164164
];
165+
166+
if (isset($params['container_params']['resourceLimits']) && !empty($params['container_params']['resourceLimits'])) {
167+
$createPayload['resource_limits'] = $params['container_params']['resourceLimits'];
168+
}
165169

166170
$this->logger->debug(sprintf('Payload for /docker/exapp/create for %s: %s', $exAppName, json_encode($createPayload)));
167171
try {
@@ -556,6 +560,16 @@ public function createContainer(string $dockerUrl, string $imageId, DaemonConfig
556560
);
557561
}
558562

563+
if (isset($params['resourceLimits'])) {
564+
if (isset($params['resourceLimits']['memory']) && $params['resourceLimits']['memory'] > 0) {
565+
// memory in bytes
566+
$containerParams['HostConfig']['Memory'] = $params['resourceLimits']['memory'];
567+
}
568+
if (isset($params['resourceLimits']['nanoCPUs']) && $params['resourceLimits']['nanoCPUs'] > 0) {
569+
$containerParams['HostConfig']['NanoCPUs'] = $params['resourceLimits']['nanoCPUs'];
570+
}
571+
}
572+
559573
$url = $this->buildApiUrl($dockerUrl, sprintf('containers/create?name=%s', urlencode($this->buildExAppContainerName($params['name']))));
560574
try {
561575
$options['json'] = $containerParams;
@@ -1107,6 +1121,7 @@ public function buildDeployParams(DaemonConfig $daemonConfig, array $appInfo): a
11071121
'devices' => $devices,
11081122
'deviceRequests' => $deviceRequests,
11091123
'mounts' => $appInfo['external-app']['mounts'] ?? [],
1124+
'resourceLimits' => $deployConfig['resourceLimits'] ?? []
11101125
];
11111126

11121127
return [

src/components/DaemonConfig/DaemonConfigDetailsModal.vue

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
<p v-if="daemon.deploy_config.computeDevice">
4242
<b>{{ t('app_api', 'Compute device') }}:</b> {{ daemon.deploy_config?.computeDevice?.label }}
4343
</p>
44+
<p><b>{{ t('app_api', 'Memory limit') }}:</b> {{ formatMemoryLimit(daemon.deploy_config?.resourceLimits?.memory) }}</p>
45+
46+
<p><b>{{ t('app_api', 'CPU limit') }}:</b> {{ formatCpuLimit(daemon.deploy_config?.resourceLimits?.nanoCPUs) }}</p>
4447

4548
<div v-if="daemon.deploy_config.additional_options" class="additional-options">
4649
<h3>{{ t('app_api', 'Additional options') }}</h3>
@@ -129,6 +132,27 @@ export default {
129132
console.debug(err)
130133
})
131134
},
135+
formatMemoryLimit(memoryBytes) {
136+
if (!memoryBytes) {
137+
return t('app_api', 'Unlimited')
138+
}
139+
const memoryMiB = memoryBytes / (1024 * 1024)
140+
if (memoryMiB >= 1024) {
141+
const memoryGiB = memoryMiB / 1024
142+
return t('app_api', '{size} GiB', { size: memoryGiB.toFixed(1) })
143+
}
144+
return t('app_api', '{size} MiB', { size: Math.round(memoryMiB) })
145+
},
146+
formatCpuLimit(nanoCpus) {
147+
if (!nanoCpus) {
148+
return t('app_api', 'Unlimited')
149+
}
150+
const cpus = nanoCpus / 1000000000
151+
if (cpus === 1) {
152+
return t('app_api', '1 CPU')
153+
}
154+
return t('app_api', '{cpus} CPUs', { cpus: cpus.toFixed(2) })
155+
},
132156
},
133157
}
134158
</script>

src/components/DaemonConfig/ManageDaemonConfigModal.vue

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,36 @@
209209
:aria-label="t('app_api', 'Compute device')"
210210
:options="computeDevices" />
211211
</div>
212+
<div class="external-label" :aria-label="t('app_api', 'Memory limit')">
213+
<label for="memory-limit">
214+
{{ t('app_api', 'Memory limit (in MiB)') }}
215+
<InfoTooltip :text="t('app_api', 'Maximum memory that the ExApp container can use in mebibytes')" />
216+
</label>
217+
<NcInputField
218+
id="memory-limit"
219+
ref="memory-limit"
220+
class="ex-input-field"
221+
:value.sync="memoryLimit"
222+
:placeholder="t('app_api', 'Memory limit (in MiB)')"
223+
:aria-label="t('app_api', 'Memory limit (in MiB)')"
224+
:error="isMemoryLimitValid === false"
225+
:helper-text="isMemoryLimitValid === false ? t('app_api', 'Must be a positive integer') : ''" />
226+
</div>
227+
<div class="external-label" :aria-label="t('app_api', 'CPU limit')">
228+
<label for="cpu-limit">
229+
{{ t('app_api', 'CPU limit') }}
230+
<InfoTooltip :text="t('app_api', 'Maximum CPU cores that the ExApp container can use (e.g. 0.5 for half a core, 2 for two cores)')" />
231+
</label>
232+
<NcInputField
233+
id="cpu-limit"
234+
ref="cpu-limit"
235+
class="ex-input-field"
236+
:value.sync="cpuLimit"
237+
:placeholder="t('app_api', 'CPU limit as decimal value')"
238+
:aria-label="t('app_api', 'CPU limit')"
239+
:error="isCpuLimitValid === false"
240+
:helper-text="isCpuLimitValid === false ? t('app_api', 'Must be a positive number') : ''" />
241+
</div>
212242
<template v-if="additionalOptions.length > 0">
213243
<div class="row" style="flex-direction: column;">
214244
<div
@@ -413,12 +443,31 @@ export default {
413443
data.defaultDaemon = this.isDefaultDaemon
414444
data.additionalOptions = Object.entries(this.daemon.deploy_config.additional_options ?? {}).map(([key, value]) => ({ key, value }))
415445
data.deployConfigSettingsOpened = true
446+
if (data.deployConfig.resourceLimits) {
447+
if (data.deployConfig.resourceLimits.memory) {
448+
// memory in bytes
449+
data.deployConfig.resourceLimits.memoryMiB = data.deployConfig.resourceLimits.memory / (1024 * 1024)
450+
delete data.deployConfig.resourceLimits.memory
451+
} else {
452+
data.deployConfig.resourceLimits.memoryMiB = null
453+
}
454+
if (data.deployConfig.resourceLimits.nanoCPUs) {
455+
data.deployConfig.resourceLimits.cpus = data.deployConfig.resourceLimits.nanoCPUs / 1000000000
456+
delete data.deployConfig.resourceLimits.nanoCPUs
457+
} else {
458+
data.deployConfig.resourceLimits.cpus = null
459+
}
460+
}
416461
}
417462
if (!data.deployConfig.harp) {
418463
data.deployConfig.harp = null
419464
data.deployConfigSettingsOpened = false
420465
}
421466
467+
if (!data.deployConfig.resourceLimits) {
468+
data.deployConfig.resourceLimits = { memoryMiB: null, cpus: null }
469+
}
470+
422471
return data
423472
},
424473
computed: {
@@ -437,6 +486,32 @@ export default {
437486
daemonProtocol() {
438487
return this.httpsEnabled ? 'https' : 'http'
439488
},
489+
memoryLimit: {
490+
get() {
491+
return this.deployConfig.resourceLimits.memoryMiB || ''
492+
},
493+
set(value) {
494+
this.deployConfig.resourceLimits.memoryMiB = value === '' ? null : value
495+
},
496+
},
497+
cpuLimit: {
498+
get() {
499+
return this.deployConfig.resourceLimits.cpus || ''
500+
},
501+
set(value) {
502+
this.deployConfig.resourceLimits.cpus = value === '' ? null : value
503+
},
504+
},
505+
isMemoryLimitValid() {
506+
if (this.memoryLimit === '' || this.memoryLimit === null) return true
507+
const str = String(this.memoryLimit).trim()
508+
return /^[1-9]\d*$/.test(str)
509+
},
510+
isCpuLimitValid() {
511+
if (this.cpuLimit === '' || this.cpuLimit === null) return true
512+
const str = String(this.cpuLimit).trim()
513+
return /^\d*\.?\d+$/.test(str)
514+
},
440515
isDaemonNameInvalid() {
441516
return this.daemons.some(daemon => daemon.name === this.name && daemon.name !== this.daemon?.name)
442517
},
@@ -463,7 +538,7 @@ export default {
463538
return t('app_api', 'The docker network that the deployed ex-apps would use.')
464539
},
465540
cannotRegister() {
466-
return this.isDaemonNameInvalid === true || this.isHaProxyPasswordValid === false || (this.isHarp && !this.deployConfig.net)
541+
return this.isDaemonNameInvalid === true || this.isHaProxyPasswordValid === false || (this.isHarp && !this.deployConfig.net) || this.isMemoryLimitValid === false || this.isCpuLimitValid === false
467542
},
468543
isAdditionalOptionValid() {
469544
return this.additionalOption.key.trim() !== '' && this.additionalOption.value.trim() !== ''
@@ -619,6 +694,17 @@ export default {
619694
registries: this.deployConfig.registries || null,
620695
},
621696
}
697+
698+
const resourceLimits = {}
699+
if (this.deployConfig.resourceLimits.memoryMiB && this.isMemoryLimitValid) {
700+
// memory in bytes
701+
resourceLimits.memory = Number(this.deployConfig.resourceLimits.memoryMiB) * 1024 * 1024
702+
}
703+
if (this.deployConfig.resourceLimits.cpus && this.isCpuLimitValid) {
704+
resourceLimits.nanoCPUs = Number(this.deployConfig.resourceLimits.cpus) * 1000000000
705+
}
706+
params.deploy_config.resourceLimits = resourceLimits
707+
622708
if (this.additionalOptions.length > 0) {
623709
params.deploy_config.additional_options = this.additionalOptions.reduce((acc, option) => {
624710
acc[option.key] = option.value

src/constants/daemonTemplates.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export const DAEMON_TEMPLATES = [
1919
id: 'cpu',
2020
label: 'CPU',
2121
},
22+
resourceLimits: {
23+
memory: null,
24+
nanoCPUs: null,
25+
},
2226
harp: {
2327
frp_address: 'localhost:8782',
2428
docker_socket_port: 24000,
@@ -43,6 +47,10 @@ export const DAEMON_TEMPLATES = [
4347
id: 'cpu',
4448
label: 'CPU',
4549
},
50+
resourceLimits: {
51+
memory: null,
52+
nanoCPUs: null,
53+
},
4654
harp: {
4755
frp_address: 'appapi-harp:8782',
4856
docker_socket_port: 24000,
@@ -67,6 +75,10 @@ export const DAEMON_TEMPLATES = [
6775
id: 'cpu',
6876
label: 'CPU',
6977
},
78+
resourceLimits: {
79+
memory: null,
80+
nanoCPUs: null,
81+
},
7082
harp: {
7183
frp_address: 'nextcloud-aio-harp:8782',
7284
docker_socket_port: 24000,
@@ -91,6 +103,10 @@ export const DAEMON_TEMPLATES = [
91103
id: 'cpu',
92104
label: 'CPU',
93105
},
106+
resourceLimits: {
107+
memory: null,
108+
nanoCPUs: null,
109+
},
94110
harp: {
95111
frp_address: 'localhost:8782',
96112
docker_socket_port: 24000,
@@ -115,6 +131,10 @@ export const DAEMON_TEMPLATES = [
115131
id: 'cpu',
116132
label: 'CPU',
117133
},
134+
resourceLimits: {
135+
memory: null,
136+
nanoCPUs: null,
137+
},
118138
harp: null,
119139
},
120140
deployConfigSettingsOpened: false,
@@ -135,6 +155,10 @@ export const DAEMON_TEMPLATES = [
135155
id: 'cpu',
136156
label: 'CPU',
137157
},
158+
resourceLimits: {
159+
memory: null,
160+
nanoCPUs: null,
161+
},
138162
harp: null,
139163
},
140164
deployConfigSettingsOpened: false,
@@ -155,6 +179,10 @@ export const DAEMON_TEMPLATES = [
155179
id: 'cpu',
156180
label: 'CPU',
157181
},
182+
resourceLimits: {
183+
memory: null,
184+
nanoCPUs: null,
185+
},
158186
harp: null,
159187
},
160188
deployConfigSettingsOpened: false,
@@ -175,6 +203,10 @@ export const DAEMON_TEMPLATES = [
175203
id: 'cpu',
176204
label: 'CPU',
177205
},
206+
resourceLimits: {
207+
memory: null,
208+
nanoCPUs: null,
209+
},
178210
harp: null,
179211
},
180212
deployConfigSettingsOpened: false,

0 commit comments

Comments
 (0)