Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion js/app_api-adminSettings.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/app_api-adminSettings.js.map

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion lib/DeployActions/DockerActions.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,12 @@ public function deployExAppHarp(ExApp $exApp, DaemonConfig $daemonConfig, array
'restart_policy' => $this->appConfig->getValueString(Application::APP_ID, 'container_restart_policy', 'unless-stopped', lazy: true),
'compute_device' => $computeDevice,
'mount_points' => $mountPoints,
'start_container' => true,
'start_container' => true
];

if (isset($params['container_params']['resourceLimits']) && !empty($params['container_params']['resourceLimits'])) {
$createPayload['resource_limits'] = $params['container_params']['resourceLimits'];
}

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

if (isset($params['resourceLimits'])) {
if (isset($params['resourceLimits']['memory']) && $params['resourceLimits']['memory'] > 0) {
// memory in bytes
$containerParams['HostConfig']['Memory'] = $params['resourceLimits']['memory'];
}
if (isset($params['resourceLimits']['nanoCPUs']) && $params['resourceLimits']['nanoCPUs'] > 0) {
$containerParams['HostConfig']['NanoCPUs'] = $params['resourceLimits']['nanoCPUs'];
}
}

$url = $this->buildApiUrl($dockerUrl, sprintf('containers/create?name=%s', urlencode($this->buildExAppContainerName($params['name']))));
try {
$options['json'] = $containerParams;
Expand Down Expand Up @@ -1107,6 +1121,7 @@ public function buildDeployParams(DaemonConfig $daemonConfig, array $appInfo): a
'devices' => $devices,
'deviceRequests' => $deviceRequests,
'mounts' => $appInfo['external-app']['mounts'] ?? [],
'resourceLimits' => $deployConfig['resourceLimits'] ?? []
];

return [
Expand Down
24 changes: 24 additions & 0 deletions src/components/DaemonConfig/DaemonConfigDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p><b>{{ t('app_api', 'Name') }}: </b>{{ daemon.name }}</p>
<p><b>{{ t('app_api', 'Protocol') }}: </b>{{ daemon.protocol }}</p>
<p><b>{{ t('app_api', 'Host') }}: </b>{{ daemon.host }}</p>
<p v-if="daemon.deploy_config.harp"><b>{{ t('app_api', 'ExApp direct communication (FRP disabled)') }}: </b>{{ daemon.deploy_config.harp.exapp_direct ?? false }}</p>

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / node-build

Expected 1 line break before closing tag (`</p>`), but no line breaks found

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / node-build

Expected 1 line break after opening tag (`<p>`), but no line breaks found

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

Expected 1 line break before closing tag (`</p>`), but no line breaks found

Check warning on line 23 in src/components/DaemonConfig/DaemonConfigDetailsModal.vue

View workflow job for this annotation

GitHub Actions / NPM build

Expected 1 line break after opening tag (`<p>`), but no line breaks found

<h3>{{ t('app_api', 'Deploy config') }}</h3>
<p><b>{{ t('app_api', 'Docker network') }}: </b>{{ daemon.deploy_config.net }}</p>
Expand All @@ -41,6 +41,9 @@
<p v-if="daemon.deploy_config.computeDevice">
<b>{{ t('app_api', 'Compute device') }}:</b> {{ daemon.deploy_config?.computeDevice?.label }}
</p>
<p><b>{{ t('app_api', 'Memory limit') }}:</b> {{ formatMemoryLimit(daemon.deploy_config?.resourceLimits?.memory) }}</p>

<p><b>{{ t('app_api', 'CPU limit') }}:</b> {{ formatCpuLimit(daemon.deploy_config?.resourceLimits?.nanoCPUs) }}</p>

<div v-if="daemon.deploy_config.additional_options" class="additional-options">
<h3>{{ t('app_api', 'Additional options') }}</h3>
Expand Down Expand Up @@ -129,6 +132,27 @@
console.debug(err)
})
},
formatMemoryLimit(memoryBytes) {
if (!memoryBytes) {
return t('app_api', 'Unlimited')
}
const memoryMiB = memoryBytes / (1024 * 1024)
if (memoryMiB >= 1024) {
const memoryGiB = memoryMiB / 1024
return t('app_api', '{size} GiB', { size: memoryGiB.toFixed(1) })
}
return t('app_api', '{size} MiB', { size: Math.round(memoryMiB) })
},
formatCpuLimit(nanoCpus) {
if (!nanoCpus) {
return t('app_api', 'Unlimited')
}
const cpus = nanoCpus / 1000000000
if (cpus === 1) {
return t('app_api', '1 CPU')
}
return t('app_api', '{cpus} CPUs', { cpus: cpus.toFixed(2) })
},
},
}
</script>
Expand Down
88 changes: 87 additions & 1 deletion src/components/DaemonConfig/ManageDaemonConfigModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,36 @@
:aria-label="t('app_api', 'Compute device')"
:options="computeDevices" />
</div>
<div class="external-label" :aria-label="t('app_api', 'Memory limit')">
<label for="memory-limit">
{{ t('app_api', 'Memory limit (in MiB)') }}
<InfoTooltip :text="t('app_api', 'Maximum memory that the ExApp container can use in mebibytes')" />
</label>
<NcInputField
id="memory-limit"
ref="memory-limit"
class="ex-input-field"
:value.sync="memoryLimit"
:placeholder="t('app_api', 'Memory limit (in MiB)')"
:aria-label="t('app_api', 'Memory limit (in MiB)')"
:error="isMemoryLimitValid === false"
:helper-text="isMemoryLimitValid === false ? t('app_api', 'Must be a positive integer') : ''" />
</div>
<div class="external-label" :aria-label="t('app_api', 'CPU limit')">
<label for="cpu-limit">
{{ t('app_api', 'CPU limit') }}
<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)')" />
</label>
<NcInputField
id="cpu-limit"
ref="cpu-limit"
class="ex-input-field"
:value.sync="cpuLimit"
:placeholder="t('app_api', 'CPU limit as decimal value')"
:aria-label="t('app_api', 'CPU limit')"
:error="isCpuLimitValid === false"
:helper-text="isCpuLimitValid === false ? t('app_api', 'Must be a positive number') : ''" />
</div>
<template v-if="additionalOptions.length > 0">
<div class="row" style="flex-direction: column;">
<div
Expand Down Expand Up @@ -413,12 +443,31 @@ export default {
data.defaultDaemon = this.isDefaultDaemon
data.additionalOptions = Object.entries(this.daemon.deploy_config.additional_options ?? {}).map(([key, value]) => ({ key, value }))
data.deployConfigSettingsOpened = true
if (data.deployConfig.resourceLimits) {
if (data.deployConfig.resourceLimits.memory) {
// memory in bytes
data.deployConfig.resourceLimits.memoryMiB = data.deployConfig.resourceLimits.memory / (1024 * 1024)
delete data.deployConfig.resourceLimits.memory
} else {
data.deployConfig.resourceLimits.memoryMiB = null
}
if (data.deployConfig.resourceLimits.nanoCPUs) {
data.deployConfig.resourceLimits.cpus = data.deployConfig.resourceLimits.nanoCPUs / 1000000000
delete data.deployConfig.resourceLimits.nanoCPUs
} else {
data.deployConfig.resourceLimits.cpus = null
}
}
}
if (!data.deployConfig.harp) {
data.deployConfig.harp = null
data.deployConfigSettingsOpened = false
}

if (!data.deployConfig.resourceLimits) {
data.deployConfig.resourceLimits = { memoryMiB: null, cpus: null }
}

return data
},
computed: {
Expand All @@ -437,6 +486,32 @@ export default {
daemonProtocol() {
return this.httpsEnabled ? 'https' : 'http'
},
memoryLimit: {
get() {
return this.deployConfig.resourceLimits.memoryMiB || ''
},
set(value) {
this.deployConfig.resourceLimits.memoryMiB = value === '' ? null : value
},
},
cpuLimit: {
get() {
return this.deployConfig.resourceLimits.cpus || ''
},
set(value) {
this.deployConfig.resourceLimits.cpus = value === '' ? null : value
},
},
isMemoryLimitValid() {
if (this.memoryLimit === '' || this.memoryLimit === null) return true
const str = String(this.memoryLimit).trim()
return /^[1-9]\d*$/.test(str)
},
isCpuLimitValid() {
if (this.cpuLimit === '' || this.cpuLimit === null) return true
const str = String(this.cpuLimit).trim()
return /^\d*\.?\d+$/.test(str)
},
isDaemonNameInvalid() {
return this.daemons.some(daemon => daemon.name === this.name && daemon.name !== this.daemon?.name)
},
Expand All @@ -463,7 +538,7 @@ export default {
return t('app_api', 'The docker network that the deployed ex-apps would use.')
},
cannotRegister() {
return this.isDaemonNameInvalid === true || this.isHaProxyPasswordValid === false || (this.isHarp && !this.deployConfig.net)
return this.isDaemonNameInvalid === true || this.isHaProxyPasswordValid === false || (this.isHarp && !this.deployConfig.net) || this.isMemoryLimitValid === false || this.isCpuLimitValid === false
},
isAdditionalOptionValid() {
return this.additionalOption.key.trim() !== '' && this.additionalOption.value.trim() !== ''
Expand Down Expand Up @@ -619,6 +694,17 @@ export default {
registries: this.deployConfig.registries || null,
},
}

const resourceLimits = {}
if (this.deployConfig.resourceLimits.memoryMiB && this.isMemoryLimitValid) {
// memory in bytes
resourceLimits.memory = Number(this.deployConfig.resourceLimits.memoryMiB) * 1024 * 1024
}
if (this.deployConfig.resourceLimits.cpus && this.isCpuLimitValid) {
resourceLimits.nanoCPUs = Number(this.deployConfig.resourceLimits.cpus) * 1000000000
}
params.deploy_config.resourceLimits = resourceLimits

if (this.additionalOptions.length > 0) {
params.deploy_config.additional_options = this.additionalOptions.reduce((acc, option) => {
acc[option.key] = option.value
Expand Down
32 changes: 32 additions & 0 deletions src/constants/daemonTemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'localhost:8782',
docker_socket_port: 24000,
Expand All @@ -43,6 +47,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'appapi-harp:8782',
docker_socket_port: 24000,
Expand All @@ -67,6 +75,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'nextcloud-aio-harp:8782',
docker_socket_port: 24000,
Expand All @@ -91,6 +103,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: {
frp_address: 'localhost:8782',
docker_socket_port: 24000,
Expand All @@ -115,6 +131,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand All @@ -135,6 +155,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand All @@ -155,6 +179,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand All @@ -175,6 +203,10 @@ export const DAEMON_TEMPLATES = [
id: 'cpu',
label: 'CPU',
},
resourceLimits: {
memory: null,
nanoCPUs: null,
},
harp: null,
},
deployConfigSettingsOpened: false,
Expand Down