diff --git a/backend/lib/services/projects.js b/backend/lib/services/projects.js index c19d4ec030..8291209bac 100644 --- a/backend/lib/services/projects.js +++ b/backend/lib/services/projects.js @@ -138,7 +138,9 @@ exports.create = async function ({ user, body }) { if (type === 'DELETE') { throw new InternalServerError('Project resource has been deleted') } - return _.get(project, 'status.phase') === 'Ready' + return { + ok: _.get(project, 'status.phase') === 'Ready' + } } const timeout = exports.projectInitializationTimeout // must be the dashboardClient because rbac rolebinding does not exist yet diff --git a/backend/lib/services/terminals/index.js b/backend/lib/services/terminals/index.js index 051861a733..851ce25b6e 100644 --- a/backend/lib/services/terminals/index.js +++ b/backend/lib/services/terminals/index.js @@ -550,7 +550,17 @@ async function readTerminalUntilReady ({ user, namespace, name }) { } const podName = _.get(terminal, 'status.podName') const attachServiceAccountName = _.get(terminal, 'status.attachServiceAccountName') - return podName && attachServiceAccountName + + const isReady = podName && attachServiceAccountName + if (!isReady) { + const lastErrorDescription = _.get(terminal, 'status.lastError.description') + return { + ok: false, + reason: lastErrorDescription + } + } + + return { ok: true } } const asyncIterable = await client['dashboard.gardener.cloud'].terminals.watch(namespace, name) return asyncIterable.until(isTerminalReady, { timeout: 60 * 1000 }) diff --git a/frontend/src/components/GTerminal.vue b/frontend/src/components/GTerminal.vue index ee6f92fe13..edc4dee3a7 100644 --- a/frontend/src/components/GTerminal.vue +++ b/frontend/src/components/GTerminal.vue @@ -381,7 +381,6 @@ import GDisconnected from '@/components/icons/GDisconnected.vue' import GSplitVertically from '@/components/icons/GSplitVertically.vue' import GSplitHorizontally from '@/components/icons/GSplitHorizontally.vue' -import { isGatewayTimeout } from '@/utils/error' import { targetText, transformHtml, @@ -683,10 +682,7 @@ export default { try { await terminalSession.open() } catch (err) { - let message = get(err, 'response.data.message', err.message) - if (isGatewayTimeout(err)) { - message = 'Opening the terminal session timed out' - } + const message = get(err, 'response.data.message', err.message) this.showErrorSnackbarBottom(message) terminalSession.setDisconnectedState() } diff --git a/packages/kube-client/__tests__/http-client.test.js b/packages/kube-client/__tests__/http-client.test.js index 4283ec40e5..f70197b210 100644 --- a/packages/kube-client/__tests__/http-client.test.js +++ b/packages/kube-client/__tests__/http-client.test.js @@ -73,7 +73,10 @@ describe('kube-client', () => { expect(mockClient.stream).toBeCalledTimes(1) expect(mockClient.stream.mock.calls[0]).toEqual([url, { method }]) expect(typeof response.until).toBe('function') - const value = await response.until(event => [event.object > 3, event.object]) + const value = await response.until(event => ({ + ok: event.object > 3, + object: event.object + })) expect(value).toBe(4) }) @@ -85,7 +88,10 @@ describe('kube-client', () => { expect(mockClient.stream).toBeCalledTimes(1) expect(mockClient.stream.mock.calls[0]).toEqual([url, { method }]) expect(typeof response.until).toBe('function') - await expect(response.until(event => event.object > 9, { timeout: 10 })).rejects.toThrow(GatewayTimeout) + await expect(response.until( + event => ({ + ok: event.object > 9 + }), { timeout: 10 })).rejects.toThrow(GatewayTimeout) expect(destroySpy).toBeCalled() expect(destroySpy.mock.calls[0]).toHaveLength(1) expect(destroySpy.mock.calls[0][0].message).toMatch(/"dummies" .* 10 ms/) diff --git a/packages/kube-client/lib/HttpClient.js b/packages/kube-client/lib/HttpClient.js index 3eda404e63..c53d9a1a20 100644 --- a/packages/kube-client/lib/HttpClient.js +++ b/packages/kube-client/lib/HttpClient.js @@ -34,24 +34,33 @@ class HttpClient { static extendResponse (response) { const { names: { plural } = {} } = this - const createTimeoutError = timeout => { + const createTimeoutError = (timeout, reason) => { const forResource = plural ? ` for "${plural}"` : '' - return createError(504, `The condition${forResource} was not met within ${timeout} ms`) + let message = `The condition${forResource} was not met within ${timeout} ms` + if (reason) { + message += `: ${reason}` + } + return createError(504, message) } response.until = async (condition, { timeout = 60000 } = {}) => { let timeoutId + let timeoutReason if (timeout > 0 && timeout < Infinity) { - timeoutId = setTimeout(() => response.destroy(createTimeoutError(timeout)), timeout) + timeoutId = setTimeout(() => response.destroy(createTimeoutError(timeout, timeoutReason)), timeout) } try { for await (const event of response) { - let ok = condition(event) - let object - [ok, object] = Array.isArray(ok) ? ok : [ok, event.object] + const { + ok, + reason, + object = event.object + } = condition(event) if (ok) { return object } + + timeoutReason = reason } // If the response stream ends even though the condition has not yet been met, // also in this case a timeout error is thrown.