This is an automated email from the ASF dual-hosted git repository. alexkli pushed a commit to branch concurrency-not-supported in repository https://gitbox.apache.org/repos/asf/openwhisk-wskdebug.git
commit c24b163a4f0158317f83a07f2afa89a9d586b4a6 Author: Alexander Klimetschek <[email protected]> AuthorDate: Tue Mar 31 01:19:41 2020 -0700 Concurrency error when using wskdebug with IBM Cloud Functions #7 if concurrency > 1 is not supported, fallback to activation db agent --- src/agentmgr.js | 21 ++++++- test/agentmgr.test.js | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/test.js | 5 ++ 3 files changed, 187 insertions(+), 3 deletions(-) diff --git a/src/agentmgr.js b/src/agentmgr.js index 8b59f28..15ea202 100644 --- a/src/agentmgr.js +++ b/src/agentmgr.js @@ -158,7 +158,7 @@ class AgentMgr { console.log("This OpenWhisk does not support action concurrency. Debugging will be a bit slower. Consider using '--ngrok' which might be a faster option."); agentName = "polling activation db"; - agentCode = await this.getPollingActivationRecordAgent(); + agentCode = await this.getPollingActivationDbAgent(); } } @@ -185,7 +185,17 @@ class AgentMgr { }); } - await this.pushAgent(action, agentCode, backupName); + try { + await this.pushAgent(action, agentCode, backupName); + } catch (e) { + // openwhisk does not support concurrent nodejs actions, try with another + if (e.statusCode === 400 && e.error && typeof e.error.error === "string" && e.error.error.includes("concurrency")) { + console.log(`The Openwhisk server does not support concurrent actions, using alternative agent. Consider using --ngrok for a possibly faster agent.`); + this.concurrency = false; + agentCode = await this.getPollingActivationDbAgent(); + await this.pushAgent(action, agentCode, backupName); + } + } if (this.argv.verbose) { console.log(`Agent installed.`); @@ -259,6 +269,11 @@ class AgentMgr { const a = activations[0]; if (a.response && a.response.result && !this.activationsSeen[a.activationId]) { activation = a; + if (!activation.response.success) { + throw { + error: activation + }; + } break; } } @@ -389,7 +404,7 @@ class AgentMgr { return fs.readFileSync(`${__dirname}/../agent/agent-concurrency.js`, {encoding: 'utf8'}); } - async getPollingActivationRecordAgent() { + async getPollingActivationDbAgent() { // this needs 2 helper actions in addition to the agent in place of the action await this.createHelperAction(`${this.actionName}_wskdebug_invoked`, `${__dirname}/../agent/echo.js`); await this.createHelperAction(`${this.actionName}_wskdebug_completed`, `${__dirname}/../agent/echo.js`); diff --git a/test/agentmgr.test.js b/test/agentmgr.test.js new file mode 100644 index 0000000..cf069b7 --- /dev/null +++ b/test/agentmgr.test.js @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file 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 CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-env mocha */ + +'use strict'; + +const Debugger = require("../src/debugger"); + +const test = require('./test'); + +describe('agentmgr', function() { + this.timeout(30000); + + before(function() { + test.isDockerInstalled(); + }); + + beforeEach(async function() { + await test.beforeEach(); + }); + + afterEach(function() { + test.afterEach(); + }); + + it("should use non-concurrrent agent if openwhisk does not support concurrency", async function() { + const action = "myaction"; + const code = `const main = () => ({ msg: 'WRONG' });`; + + test.mockAction(action, code); + + test.mockCreateBackupAction(action); + + // wskdebug overwriting the action with the agent + test.openwhiskNock() + .put( + `${test.openwhiskApiUrlActions()}/${action}?overwrite=true`, + body => body.annotations.some(v => v.key === "wskdebug" && v.value === true) + ) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(400, { + code: 'df940ccf1d076f103c3743685c25d2b2', + error: 'The request content was malformed:\nrequirement failed: concurrency 200 exceeds allowed threshold of 1' + }); + + // another wskdebug with non-concurrent action + test.openwhiskNock() + .put( + `${test.openwhiskApiUrlActions()}/${action}?overwrite=true`, + body => body.annotations.some(v => v.key === "wskdebug" && v.value === true) + ) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, test.nodejsActionDescription(action)); + + // helper actions for non-concurrent action + test.openwhiskNock() + .put(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_invoked?overwrite=true`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, test.nodejsActionDescription(action)); + test.openwhiskNock() + .put(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_completed?overwrite=true`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, test.nodejsActionDescription(action)); + + + // invocation + test.openwhiskNock() + .get(`${test.openwhiskApiUrl()}/activations`) + .query(query => query.name === `${action}_wskdebug_invoked`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, [{ + activationId: "dummy-invoked", + response: { + success: true, + result: { + $activationId: "1234567890" + } + } + }]); + + // completion of invocation + test.openwhiskNock() + .post(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_completed?blocking=true`, { + msg: "CORRECT", + $activationId: "1234567890" + }) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, test.nodejsActionDescription(action)); + + // abort polling + test.openwhiskNock() + .get(`${test.openwhiskApiUrl()}/activations`) + .query(query => query.name === `${action}_wskdebug_invoked`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, [{ + activationId: "dummy-invoked-2", + response: { + success: false, + result: { + error: { + code: 43 + }, + $activationId: "99999999999" + } + } + }]); + + // shutdown/restore process + test.mockReadBackupAction(action, code); + test.mockRestoreAction(action, code); + test.mockRemoveBackupAction(action); + test.openwhiskNock() + .get(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_invoked?code=false`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, {}); + test.openwhiskNock() + .delete(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_invoked`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, {}); + test.openwhiskNock() + .get(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_completed?code=false`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, {}); + test.openwhiskNock() + .delete(`${test.openwhiskApiUrlActions()}/${action}_wskdebug_completed`) + .matchHeader("authorization", test.openwhiskApiAuthHeader()) + .reply(200, {}); + + + process.chdir("test/nodejs/plain-flat"); + const argv = { + port: test.port, + action: "myaction", + sourcePath: `${process.cwd()}/action.js`, + invokeParams: '{ "key": "invocationOnSourceModification" }' + }; + + const dbgr = new Debugger(argv); + await dbgr.start(); + dbgr.run(); + + // wait a bit + await test.sleep(500); + + await dbgr.stop(); + + test.assertAllNocksInvoked(); + }); +}); diff --git a/test/test.js b/test/test.js index 5a266d7..34818e0 100644 --- a/test/test.js +++ b/test/test.js @@ -74,6 +74,10 @@ function openwhiskNock() { return openwhisk; } +function openwhiskApiUrl() { + return `/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}`; +} + function openwhiskApiUrlActions() { return `/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions`; } @@ -522,6 +526,7 @@ module.exports = { mockActionDoubleInvocation, // advanced openwhiskNock, + openwhiskApiUrl, openwhiskApiUrlActions, openwhiskApiAuthHeader, mockAction,
