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
1 change: 1 addition & 0 deletions src/ValidationErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ E('INVALID_TAG_SYNTAX', 'tag flag should not be specified with "refs/tags/" pref
E('JSON_PARSE_NUMBER', 'parsed flag value as a number')
E('BLANK_VARIABLE_VALUE', 'Blank variable values are not allowed. Use the proper flag if you intend to delete a variable.')
E('MALFORMED_NAME_VALUE_PAIR', 'Please provide correct values for flags')
E('MAX_RETRY_REACHED', 'Max retries reached')
33 changes: 33 additions & 0 deletions src/cloudmanager-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Config = require('@adobe/aio-lib-core-config')
const { init } = require('@adobe/aio-lib-cloudmanager')
const { cli } = require('cli-ux')
const { context, getToken, Ims } = require('@adobe/aio-lib-ims')
const logger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-cloudmanager', { provider: 'debug' })
const moment = require('moment')
const _ = require('lodash')
const { CLI } = require('@adobe/aio-lib-ims/src/context')
Expand Down Expand Up @@ -306,6 +307,37 @@ function handleError (_error, errorFn) {
})
}

async function executeWithRetries (fn, maxRetries = 5) {
let retries = 0
let startTime = Date.now()
while (retries < maxRetries) {
try {
return await fn()
} catch (error) {
if (error.sdkDetails && error.sdkDetails.response && (error.sdkDetails.response.status === 401 || error.sdkDetails.response.status === 403)) {
logger.debug('Received 401, 403 error, retrying.')
} else {
throw error
}
if (shouldResetRetires(startTime)) {
retries = 0
startTime = Date.now()
}
retries++
}
}
throw new validationCodes.MAX_RETRY_REACHED()
}

function shouldResetRetires (startTime, resetInterval = 3600000) {
const elapsedTime = Date.now() - startTime
if (elapsedTime >= resetInterval) {
logger.debug(`Resetting retries after ${resetInterval / 1000} seconds.`)
return true
}
return false
}

module.exports = {
getProgramId,
getOutputFormat,
Expand All @@ -328,4 +360,5 @@ module.exports = {
getActiveOrganizationId,
getFullOrgIdentity,
handleError,
executeWithRetries,
}
8 changes: 5 additions & 3 deletions src/commands/cloudmanager/environment/tail-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ governing permissions and limitations under the License.
*/

const BaseCommand = require('../../../base-command')
const { initSdk, getProgramId, sanitizeEnvironmentId } = require('../../../cloudmanager-helpers')
const { initSdk, getProgramId, sanitizeEnvironmentId, executeWithRetries } = require('../../../cloudmanager-helpers')
const commonFlags = require('../../../common-flags')
const commonArgs = require('../../../common-args')

Expand All @@ -31,8 +31,10 @@ class TailLog extends BaseCommand {
}

async tailLog (programId, environmentId, service, logName, imsContextName = null) {
const sdk = await initSdk(imsContextName)
return sdk.tailLog(programId, environmentId, service, logName, process.stdout)
return executeWithRetries(async () => {
const sdk = await initSdk(imsContextName)
return sdk.tailLog(programId, environmentId, service, logName, process.stdout)
})
}
}

Expand Down
65 changes: 64 additions & 1 deletion test/cloudmanager-helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ governing permissions and limitations under the License.

const { setCurrentOrgId, context } = require('@adobe/aio-lib-ims')
const { setStore } = require('@adobe/aio-lib-core-config')
const { initSdk, getOutputFormat, columnWithArray, disableCliAuth, enableCliAuth, formatDuration } = require('../src/cloudmanager-helpers')
const { initSdk, getOutputFormat, columnWithArray, disableCliAuth, enableCliAuth, formatDuration, executeWithRetries } = require('../src/cloudmanager-helpers')
const { init } = require('@adobe/aio-lib-cloudmanager')
const { setDecodedToken, resetDecodedToken } = require('jsonwebtoken')

Expand Down Expand Up @@ -133,3 +133,66 @@ test('formatDuration -- only finished', async () => {
finishedAt: '2021-05-01',
})).toEqual('')
})

describe('executeWithRetries', () => {
afterEach(() => {
jest.restoreAllMocks()
})

test('should not retry when no 401, 403 thrown', async () => {
const mockFn = jest.fn().mockResolvedValue('success')

executeWithRetries(mockFn)

expect(mockFn.mock.calls.length).toEqual(1)
})

test('should retry when 401 thrown', async () => {
const mockFn = jest.fn()
.mockRejectedValueOnce({ sdkDetails: { response: { status: 401 } } })
.mockResolvedValue('success')

await executeWithRetries(mockFn)

expect(mockFn.mock.calls.length).toEqual(2)
})

test('should retry when 403 thrown', async () => {
const mockFn = jest.fn()
.mockRejectedValueOnce({ sdkDetails: { response: { status: 401 } } })
.mockResolvedValue('success')

await executeWithRetries(mockFn)

expect(mockFn.mock.calls.length).toEqual(2)
})

test('should throw error when no 401, 403 thrown', async () => {
const mockFn = jest.fn()
.mockRejectedValue(new Error())

await expect(executeWithRetries(mockFn)).rejects.toThrow()

expect(mockFn.mock.calls.length).toEqual(1)
})

test('should retry 5 times and throw exception', async () => {
const mockFn = jest.fn().mockRejectedValue({ sdkDetails: { response: { status: 401 } } })

await expect(executeWithRetries(mockFn)).rejects.toThrow('[CloudManagerCLI:MAX_RETRY_REACHED] Max retries')

expect(mockFn.mock.calls.length).toEqual(5)
})

test('should reset retry counter after 1 hour', async () => {
jest.spyOn(Date, 'now')
.mockReturnValueOnce(new Date(Date.UTC(2024, 11, 1, 0, 0, 0)))
.mockReturnValueOnce(new Date(Date.UTC(2024, 11, 1, 0, 0, 0)))
.mockReturnValue(new Date(Date.UTC(2024, 11, 1, 1, 0, 0)))
const mockFn = jest.fn().mockRejectedValue({ sdkDetails: { response: { status: 401 } } })

await expect(executeWithRetries(mockFn)).rejects.toThrow('[CloudManagerCLI:MAX_RETRY_REACHED] Max retries')

expect(mockFn.mock.calls.length).toEqual(6)
})
})
11 changes: 11 additions & 0 deletions test/commands/environment/tail-log.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ test('tail-log - config', async () => {
await expect(mockSdk.tailLog.mock.calls.length).toEqual(1)
await expect(mockSdk.tailLog).toHaveBeenCalledWith('5', '17', 'author', 'aemerror', process.stdout)
})

test('tail-log - should retry 5 times and throw error', async () => {
setCurrentOrgId('good')
mockSdk.tailLog.mockRejectedValue({ sdkDetails: { response: { status: 401 } } })

expect.assertions(2)

const runResult = TailLog.run(['17', 'author', 'aemerror', '--programId', '5'])
await expect(runResult).rejects.toThrow('[CloudManagerCLI:MAX_RETRY_REACHED] Max retries reached')
await expect(mockSdk.tailLog.mock.calls.length).toEqual(5)
})
Loading