-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: script to perform cold start of the poller
- Loading branch information
Showing
4 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
Copyright 2025 Adobe. All rights reserved. | ||
This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. You may obtain a copy | ||
of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
OF ANY KIND, either express or implied. See the License for the specific language | ||
governing permissions and limitations under the License. | ||
*/ | ||
|
||
const stateLib = require('@adobe/aio-lib-state'); | ||
|
||
async function main({ op, key, value }) { | ||
const state = await stateLib.init(); | ||
let result; | ||
|
||
switch (op) { | ||
case 'get': | ||
result = await state.get(key); | ||
break; | ||
case 'put': | ||
result = await state.put(key, value); | ||
break; | ||
case 'delete': | ||
result = await state.delete(key); | ||
break; | ||
case 'stats': | ||
result = await state.stats(); | ||
// eslint-disable-next-line no-fallthrough | ||
case 'list': | ||
default: { | ||
result = []; | ||
for await (const { keys } of state.list()) { | ||
result.push(...keys); | ||
} | ||
} | ||
} | ||
|
||
return { op, key, result }; | ||
} | ||
|
||
exports.main = main; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
/* | ||
Copyright 2025 Adobe. All rights reserved. | ||
This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. You may obtain a copy | ||
of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
OF ANY KIND, either express or implied. See the License for the specific language | ||
governing permissions and limitations under the License. | ||
*/ | ||
|
||
// Mock modules before requiring the main file | ||
const mockOpenWhiskInstance = { | ||
actions: { | ||
invoke: jest.fn() | ||
}, | ||
rules: { | ||
disable: jest.fn(), | ||
enable: jest.fn() | ||
} | ||
}; | ||
|
||
jest.mock('openwhisk', () => { | ||
return jest.fn(() => mockOpenWhiskInstance); | ||
}); | ||
|
||
jest.mock('dotenv', () => ({ | ||
config: jest.fn() | ||
})); | ||
|
||
jest.mock('commander', () => { | ||
const mockProgram = { | ||
option: jest.fn().mockReturnThis(), | ||
parse: jest.fn().mockReturnThis(), | ||
opts: jest.fn().mockReturnValue({ keys: 'en,fr' }) | ||
}; | ||
return { program: mockProgram }; | ||
}); | ||
// Store original process.env | ||
const originalEnv = process.env; | ||
|
||
describe('Refresh PDP Tool Tests', () => { | ||
let mainModule; | ||
|
||
beforeEach(() => { | ||
// Reset process.env before each test | ||
process.env = { ...originalEnv }; | ||
process.env.AIO_RUNTIME_NAMESPACE = 'test-namespace'; | ||
process.env.AIO_RUNTIME_AUTH = 'test-auth'; | ||
|
||
// Clear all mocks | ||
jest.clearAllMocks(); | ||
jest.resetModules(); | ||
|
||
// Reset mock functions | ||
mockOpenWhiskInstance.actions.invoke.mockReset(); | ||
mockOpenWhiskInstance.rules.disable.mockReset(); | ||
mockOpenWhiskInstance.rules.enable.mockReset(); | ||
|
||
// Mock exit and console | ||
process.exit = jest.fn(); | ||
console.log = jest.fn(); | ||
console.error = jest.fn(); | ||
}); | ||
|
||
afterEach(() => { | ||
process.env = originalEnv; | ||
}); | ||
|
||
describe('Environment Variables', () => { | ||
it('should exit if AIO_RUNTIME_NAMESPACE is missing', () => { | ||
delete process.env.AIO_RUNTIME_NAMESPACE; | ||
require('../tools/refresh-pdps'); | ||
|
||
expect(process.exit).toHaveBeenCalledWith(1); | ||
expect(console.log).toHaveBeenCalledWith( | ||
'Missing required environment variables AIO_RUNTIME_AUTH and AIO_RUNTIME_NAMESPACE' | ||
); | ||
}); | ||
|
||
it('should exit if AIO_RUNTIME_AUTH is missing', () => { | ||
delete process.env.AIO_RUNTIME_AUTH; | ||
require('../tools/refresh-pdps'); | ||
|
||
expect(process.exit).toHaveBeenCalledWith(1); | ||
expect(console.log).toHaveBeenCalledWith( | ||
'Missing required environment variables AIO_RUNTIME_AUTH and AIO_RUNTIME_NAMESPACE' | ||
); | ||
}); | ||
}); | ||
|
||
describe('checkState function', () => { | ||
beforeEach(() => { | ||
mainModule = require('../tools/refresh-pdps'); | ||
}); | ||
|
||
it('should return state value for given locale', async () => { | ||
const expectedState = { value: 'false' }; | ||
mockOpenWhiskInstance.actions.invoke.mockResolvedValueOnce(expectedState); | ||
|
||
const result = await mainModule.checkState('en'); | ||
|
||
expect(mockOpenWhiskInstance.actions.invoke).toHaveBeenCalledWith({ | ||
name: 'state-manager', | ||
params: { key: 'en', op: 'get' }, | ||
blocking: true, | ||
result: true | ||
}); | ||
expect(result).toBe('false'); | ||
}); | ||
|
||
it('should handle errors when checking state', async () => { | ||
mockOpenWhiskInstance.actions.invoke.mockRejectedValueOnce(new Error('API Error')); | ||
await expect(mainModule.checkState('en')).rejects.toThrow('API Error'); | ||
}); | ||
}); | ||
|
||
describe('flushStoreState function', () => { | ||
beforeEach(() => { | ||
mainModule = require('../tools/refresh-pdps'); | ||
}); | ||
|
||
it('should invoke delete operation for given locale', async () => { | ||
await mainModule.flushStoreState('en'); | ||
|
||
expect(mockOpenWhiskInstance.actions.invoke).toHaveBeenCalledWith({ | ||
name: 'state-manager', | ||
params: { key: 'en', op: 'delete' }, | ||
blocking: true, | ||
result: true | ||
}); | ||
}); | ||
|
||
it('should handle errors when flushing state', async () => { | ||
mockOpenWhiskInstance.actions.invoke.mockRejectedValueOnce(new Error('Delete Error')); | ||
await expect(mainModule.flushStoreState('en')).rejects.toThrow('Delete Error'); | ||
}); | ||
}); | ||
|
||
describe('main function', () => { | ||
beforeEach(() => { | ||
mainModule = require('../tools/refresh-pdps'); | ||
mockOpenWhiskInstance.rules.disable.mockResolvedValue({}); | ||
mockOpenWhiskInstance.rules.enable.mockResolvedValue({}); | ||
mockOpenWhiskInstance.actions.invoke | ||
.mockResolvedValueOnce({ value: 'false' }) | ||
.mockResolvedValueOnce({}) | ||
.mockResolvedValueOnce({}) | ||
.mockResolvedValueOnce({ value: 'true' }); | ||
|
||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should successfully complete the state management cycle', async () => { | ||
const mainPromise = mainModule.main(); | ||
jest.runAllTimers(); | ||
await mainPromise; | ||
|
||
expect(mockOpenWhiskInstance.rules.disable).toHaveBeenCalledWith({ | ||
name: 'poll_every_minute' | ||
}); | ||
expect(mockOpenWhiskInstance.rules.enable).toHaveBeenCalledWith({ | ||
name: 'poll_every_minute' | ||
}); | ||
expect(process.exit).toHaveBeenCalledWith(0); | ||
}); | ||
|
||
it('should timeout if running state never becomes true', async () => { | ||
mockOpenWhiskInstance.actions.invoke.mockResolvedValue({ value: 'false' }); | ||
|
||
const timeout = 1500; // to simulate in the test env | ||
const mainPromise = mainModule.main(timeout); | ||
jest.advanceTimersByTime(1500); | ||
await mainPromise; | ||
|
||
expect(console.error).toHaveBeenCalledWith( | ||
'Timeout: running state did not become true within 30 minutes.' | ||
); | ||
expect(process.exit).toHaveBeenCalledWith(1); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
Copyright 2025 Adobe. All rights reserved. | ||
This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. You may obtain a copy | ||
of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
OF ANY KIND, either express or implied. See the License for the specific language | ||
governing permissions and limitations under the License. | ||
*/ | ||
|
||
const openwhisk = require('openwhisk'); | ||
const { program } = require('commander'); | ||
const { exit } = require('process'); | ||
|
||
require('dotenv').config(); | ||
|
||
const { | ||
AIO_RUNTIME_NAMESPACE, | ||
AIO_RUNTIME_AUTH, | ||
} = process.env; | ||
|
||
if (!AIO_RUNTIME_NAMESPACE || !AIO_RUNTIME_AUTH) { | ||
console.log('Missing required environment variables AIO_RUNTIME_AUTH and AIO_RUNTIME_NAMESPACE'); | ||
exit(1); | ||
} | ||
|
||
const [AIO_STATE_MANAGER_ACTION_NAME, AIO_POLLER_RULE_NAME] = ['state-manager', 'poll_every_minute']; | ||
|
||
const ow = openwhisk({ | ||
apihost: 'https://adobeioruntime.net', | ||
api_key: AIO_RUNTIME_AUTH, | ||
namespace: AIO_RUNTIME_NAMESPACE, | ||
}); | ||
|
||
let targetLocales = []; | ||
|
||
program | ||
.requiredOption('-l, --locales <locales>', 'Comma-separated list of locales (or stores) to target. For example: en,fr,de') | ||
.parse(process.argv); | ||
|
||
const options = program.opts(); | ||
|
||
if (!options.keys) { | ||
console.error('No locales provided to delete.'); | ||
exit(1); | ||
} | ||
|
||
targetLocales = options.keys.split(','); | ||
|
||
async function checkState(locale) { | ||
const state = await ow.actions.invoke({ | ||
name: AIO_STATE_MANAGER_ACTION_NAME, | ||
params: { key: locale, op: 'get' }, | ||
blocking: true, | ||
result: true, | ||
}); | ||
return state.value; | ||
} | ||
|
||
async function flushStoreState(locale) { | ||
await ow.actions.invoke({ | ||
name: AIO_STATE_MANAGER_ACTION_NAME, | ||
params: { key: locale, op: 'delete' }, | ||
blocking: true, | ||
result: true, | ||
}); | ||
} | ||
|
||
async function main(timeout = 20 * 60 * 1000) { | ||
|
||
|
||
|
||
try { | ||
// Disable the rule | ||
await ow.rules.disable({ name: AIO_POLLER_RULE_NAME }); | ||
|
||
// Wait until 'running' is not 'true' | ||
let running = await checkState('running'); | ||
while (running === 'true') { | ||
console.log('Waiting for running state to be false...'); | ||
await new Promise(resolve => setTimeout(resolve, 60000)); // Wait 1 minute | ||
running = await checkState('running'); | ||
} | ||
|
||
// Delete specified keys | ||
for (const key of targetLocales) { | ||
await flushStoreState(key); | ||
} | ||
|
||
// Re-enable the rule to trigger the poller cycle start | ||
await ow.rules.enable({ name: AIO_POLLER_RULE_NAME }); | ||
|
||
// Check periodically until 'running' is 'true' | ||
const startTime = Date.now(); | ||
while (true) { | ||
running = await checkState('running'); | ||
if (running === 'true') { | ||
console.log('Running state is true. Exiting...'); | ||
exit(0); | ||
} | ||
if (Date.now() - startTime > timeout) { | ||
console.error('Timeout: running state did not become true within 30 minutes.'); | ||
exit(1); | ||
} | ||
await new Promise(resolve => setTimeout(resolve, 60000)); // Wait 1 minute | ||
} | ||
} catch (error) { | ||
console.error('Error:', error); | ||
exit(1); | ||
} | ||
} | ||
|
||
module.exports = { | ||
checkState, | ||
flushStoreState, | ||
main, | ||
targetLocales, | ||
}; | ||
|
||
// Only call main if this file is being run directly | ||
if (require.main === module) { | ||
main(); | ||
} |