Skip to content

Commit

Permalink
Test deploy adjustments (#280)
Browse files Browse the repository at this point in the history
Test Deploy fixes, timeout logic adjustments, docs.
  • Loading branch information
andrey18106 authored Apr 30, 2024
1 parent 16ce746 commit af00e17
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 12 deletions.
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

0 comments on commit af00e17

Please sign in to comment.