Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test deploy adjustments #280

Merged
merged 9 commits into from
Apr 30, 2024
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
3 changes: 2 additions & 1 deletion docs/TestDeploy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ The ExApp might have additional pre-configuration logic during this step.
Possible errors:

- ExApp failed to start a web server, e.g., if the port is already in use (this should be visible in the container logs)

- ExApp heartbeat_count keeps increasing, this may indicate that the ExApp couldn't start properly
- Nextcloud can not reach the ExApp container, e.g., due to a network issue or a firewall

Init
****
Expand Down
8 changes: 7 additions & 1 deletion lib/BackgroundJob/ExAppInitStatusCheckJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@ protected function run($argument): void {
// set status.progress=0 and status.error message with timeout error
try {
$exApps = $this->mapper->findAll();
$initTimeoutMinutes = intval($this->config->getAppValue(Application::APP_ID, 'init_timeout', '40'));
$initTimeoutMinutesSetting = intval($this->config->getAppValue(Application::APP_ID, 'init_timeout', '40'));
foreach ($exApps as $exApp) {
$status = $exApp->getStatus();
if (isset($status['init']) && $status['init'] !== 100) {
if (!isset($status['init_start_time'])) {
continue;
}
if ($exApp->getAppid() === Application::TEST_DEPLOY_APPID) {
// Check for smaller timeout for test deploy app
$initTimeoutMinutes = 0.5;
} else {
$initTimeoutMinutes = $initTimeoutMinutesSetting;
}
if ((time() >= ($status['init_start_time'] + $initTimeoutMinutes * 60)) && (empty($status['error']))) {
$this->service->setAppInitProgress(
$exApp, 0, sprintf('ExApp %s initialization timed out (%sm)', $exApp->getAppid(), $initTimeoutMinutes * 60)
Expand Down
2 changes: 1 addition & 1 deletion lib/Command/ExApp/Register.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
);
}

if (!$this->service->heartbeatExApp($exAppUrl, $auth)) {
if (!$this->service->heartbeatExApp($exAppUrl, $auth, $appId)) {
$this->logger->error(sprintf('ExApp %s heartbeat check failed. Make sure that Nextcloud instance and ExApp can reach it other.', $appId));
if ($outputConsole) {
$output->writeln(sprintf('ExApp %s heartbeat check failed. Make sure that Nextcloud instance and ExApp can reach it other.', $appId));
Expand Down
2 changes: 1 addition & 1 deletion lib/Command/ExApp/Update.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private function updateExApp(InputInterface $input, OutputInterface $output, str
);
}

if (!$this->service->heartbeatExApp($exAppUrl, $auth)) {
if (!$this->service->heartbeatExApp($exAppUrl, $auth, $appId)) {
$this->logger->error(sprintf('ExApp %s heartbeat check failed. Make sure that Nextcloud instance and ExApp can reach it other.', $appId));
if ($outputConsole) {
$output->writeln(sprintf('ExApp %s heartbeat check failed. Make sure that Nextcloud instance and ExApp can reach it other.', $appId));
Expand Down
19 changes: 18 additions & 1 deletion lib/Service/AppAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,15 @@ public function heartbeatExApp(
string $exAppUrl,
#[\SensitiveParameter]
array $auth,
string $appId,
): bool {
$heartbeatAttempts = 0;
$delay = 1;
$maxHeartbeatAttempts = 60 * 10 * $delay; // minutes for container initialization
if ($appId === Application::TEST_DEPLOY_APPID) {
$maxHeartbeatAttempts = 60 * $delay; // 1 minute for test deploy app
} else {
$maxHeartbeatAttempts = 60 * 10 * $delay; // minutes for container initialization
}

$options = [
'headers' => [
Expand All @@ -527,6 +532,10 @@ public function heartbeatExApp(
$heartbeatAttempts++;
$errorMsg = '';
$statusCode = 0;
$exApp = $this->exAppService->getExApp($appId);
if ($exApp === null) {
return false;
}
try {
$heartbeatResult = $this->client->get($exAppUrl . '/heartbeat', $options);
$statusCode = $heartbeatResult->getStatusCode();
Expand All @@ -545,6 +554,14 @@ public function heartbeatExApp(
$this->logger->warning(
sprintf('Failed heartbeat on %s for %d times. Most recent status=%d, error: %s', $exAppUrl, $failedHeartbeatCount, $statusCode, $errorMsg)
);
$status = $exApp->getStatus();
if (isset($status['heartbeat_count'])) {
$status['heartbeat_count'] += $failedHeartbeatCount;
} else {
$status['heartbeat_count'] = $failedHeartbeatCount;
}
$exApp->setStatus($status);
$this->exAppService->updateExApp($exApp, ['status']);
}
sleep($delay);
}
Expand Down
1 change: 1 addition & 0 deletions src/components/DaemonConfig/DaemonConfig.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<DaemonTestDeploy
v-if="showTestDeployDialog"
:show.sync="showTestDeployDialog"
:get-all-daemons="getAllDaemons"
:daemon="daemon" />
</template>
</div>
Expand Down
59 changes: 52 additions & 7 deletions src/components/DaemonConfig/DaemonTestDeploy.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
class="status-check">
<NcNoteCard
:type="getStatusCheckType(statusCheck)"
:heading="statusCheck?.progress ? statusCheck.title + ` (${statusCheck.progress}%)` : statusCheck.title"
:heading="getStatusCheckTitle(statusCheck)"
style="margin: 0 0 10px 0;">
<template #icon>
<NcLoadingIcon v-if="statusCheck.loading && !statusCheck.error" :size="20" />
Expand Down Expand Up @@ -117,6 +117,10 @@ export default {
required: true,
default: () => null,
},
getAllDaemons: {
type: Function,
required: true,
},
},
data() {
return {
Expand Down Expand Up @@ -144,6 +148,7 @@ export default {
error: false,
error_message: '',
help_url: 'https://cloud-py-api.github.io/app_api/TestDeploy.html#image-pull',
progress: null,
},
container_started: {
id: 'container_started',
Expand All @@ -164,6 +169,7 @@ export default {
error: false,
error_message: '',
help_url: 'https://cloud-py-api.github.io/app_api/TestDeploy.html#heartbeat',
heartbeat_count: null,
},
init: {
id: 'init',
Expand All @@ -174,6 +180,7 @@ export default {
error: false,
error_message: '',
help_url: 'https://cloud-py-api.github.io/app_api/TestDeploy.html#init',
progress: null,
},
enabled: {
id: 'enabled',
Expand All @@ -188,6 +195,17 @@ export default {
},
}
},
computed: {
heartbeatCountHeadingProgress() {
return `${this.statusChecks.heartbeat.title} (heartbeat_count: ${this.statusChecks.heartbeat.heartbeat_count || 0})`
},
imagePullHeadingProgress() {
return `${this.statusChecks.image_pull.title} (${this.statusChecks.image_pull.progress}%)`
},
initHeadingProgress() {
return `${this.statusChecks.init.title} (${this.statusChecks.init.progress}%)`
},
},
beforeMount() {
this.fetchTestDeployStatus()
},
Expand All @@ -205,8 +223,11 @@ export default {
statusCheck.passed = false
statusCheck.error = false
statusCheck.error_message = ''
if (statusCheck.progress) {
delete statusCheck.progress
if ('progress' in statusCheck) {
statusCheck.progress = null
}
if ('heartbeat_count' in statusCheck) {
statusCheck.heartbeat_count = null
}
})
this._startDeployTest().then((res) => {
Expand All @@ -233,6 +254,8 @@ export default {
}
this.clearTestRunning()
return err
}).finally(() => {
this.getAllDaemons()
})
},
startDeployTestPolling() {
Expand All @@ -254,6 +277,7 @@ export default {
clearInterval(this.polling)
}).finally(() => {
this.stoppingTest = false
this.getAllDaemons()
})
},
fetchTestDeployStatus() {
Expand All @@ -278,13 +302,21 @@ export default {
Object.keys(this.statusChecks).forEach(step => {
const statusCheck = this.statusChecks[step]
statusCheck.loading = step === currentStep
if (statusCheck.id === 'image_pull' && statusCheck.loading) {
statusCheck.progress = status.deploy
}
if (statusCheck.id === 'init' && statusCheck.loading) {
statusCheck.progress = status.init
}
if (statusCheck.id === 'heartbeat' && 'heartbeat_count' in status) {
statusCheck.heartbeat_count = status.heartbeat_count
}
switch (step) {
case 'register':
statusCheck.passed = true // at this point we're reading app status, so it's already registered
break
case 'image_pull':
statusCheck.passed = status.deploy >= 94
statusCheck.progress = status.deploy
break
case 'container_started':
statusCheck.passed = status.deploy >= 98
Expand All @@ -294,7 +326,6 @@ export default {
break
case 'init':
statusCheck.passed = status.init === 100
statusCheck.progress = status.init
break
case 'enabled':
statusCheck.passed = status.init === 100 && status.deploy === 100 && status.action === '' && status.error === ''
Expand All @@ -311,16 +342,18 @@ export default {
statusCheck.loading = false
statusCheck.passed = false
showError(t('app_api', 'Deploy test failed at step "{step}"', { step }))
this.clearTestRunning()
}
})
if (status.error !== '') {
this.clearTestRunning()
}
},
_detectCurrentStep(status) {
if (status.action === '' && status.deploy === 0 && status.init === 0) {
return 'register'
}
if (status.action === 'deploy') {
if (status.deploy > 0 && status.deploy < 94) {
if (status.deploy >= 0 && status.deploy < 94) {
return 'image_pull'
}
if (status.deploy >= 95 && status.deploy <= 97) {
Expand Down Expand Up @@ -350,6 +383,18 @@ export default {
}
return 'info'
},
getStatusCheckTitle(statusCheck) {
if (statusCheck.id === 'heartbeat' && this.statusChecks.heartbeat.heartbeat_count) {
return this.heartbeatCountHeadingProgress
}
if (statusCheck.id === 'image_pull' && this.statusChecks.image_pull.progress) {
return this.imagePullHeadingProgress
}
if (statusCheck.id === 'init' && this.statusChecks.init.progress) {
return this.initHeadingProgress
}
return statusCheck.title
},
clearTestRunning() {
this.testRunning = false
clearInterval(this.polling)
Expand Down
Loading