diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1ed3b5e5c..34f6374f1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,7 +7,7 @@ Thank you for your contribution! Code of Conduct: https://iofog.org/docs/contributing/code-of-conduct.html --> -- [ ] Does the iofog.org or README.md documentation need to change? +- Does the iofog.org or README.md documentation need to change? - [ ] Yes - [ ] No diff --git a/.travis.yml b/.travis.yml index b554956fa..2627b924a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,11 @@ notifications: email: false slack: rooms: - - secure: T6GjQi6ap7dvARwdxo619XBUO8aXyzeP4wB8LRpnLKgDQtd+EVw3hwMcHEB7C5+KBC9D0LE0i5HjnLKj2T9ih1L1zKM0QDYFVO8jTt+zhWbEs9Famdq0kmfGiTCbiVG/vxr+9bvFF4QkFhzK8kV44uyqZkmQrHuFnrCpJivsg7dsqvxZ6iU3FV1lMOLU0SJTBHwjbVnIOEqbYcbXmuBJerLMqU7kSlGdlDgBMX8xnzPo/Ccqft+CHDSEl5XU2RZZo2kGSBApO0jpfl1r+7LzWBiwHbuIkFAR5/bVecyuCRpRB4EGEXD7cxCeNUHYjvdr/vaJwTCiPspvSOkJv5YDWgGl7V3WtCV0oPDcQGOU844nHxUTp9FPCQ1bfsPqJ13tCHgv177/Oku3xH22ex6rgTBI6kkVqP3QWTgVtRW91opaKLeBM0ZigQyxQ/hUvQKsMmXzqDrWELG2CuXt2cnNBQ5Hw9wvzEdyY/T4mkXHlbd1UMJ0PUuaymICusfzcErTTnNeByTDkSjrd4dudQtH3jg+Vn3BSQ1R1U1T6DdUhkV75tt37Ej/cbL9ZN8iey7gqx0N3nzqXA0JsBAsJspjCOZWZ0ODdI2XP9FiD+4TUj+HTBgwHtx5kUqf2uuGlKX0S74zN/RjEmHa2riviW3IVur16Vf33U8oiwY+ilht0Zo= + - secure: amP0nLt6NoTtatbxzq1DXpkyVWCmwasGVrzwtyeTEkz/A4H8rA6nAOHdBIIiIVBySaTBVpR2uar9MFWTVy7kWuzRDnBLlkjuXia0Paykv9ENf8CysW0bddhRZNz0zp3SKjjCUCPXe5bmEszQjGKCBOj2XJrkpdnYQdk0L+AqNMGcI2xhu8/RSlww6Ld7OsnkvDyVvOnriclXinZu3V3f6mY7GuXcRva/qowIE8RMGmH70DPBOkCbo7D8g6WtGXcT7sgJVsTOiqPknmQRHJcHi9d55J1fr2cRNRxOCi3pz6B8jwx9BLxKbSttEAO9r9634/Yx6GByE8JaidgVkIm6OTvi6fkCHZZWJGgyxRke/EOOjhdMHSuhVQtBX6RQ4ADRs4Ja4+g9EplXsPaxY4KI7E5jV0HuBn5M7m8RrZ2npP36kqc5/T87tDrocQVKTw5TxYiHUh2GHVw90eA5XFS5eYxT/Ts39n+Jk0CznBQsV47xcUi78OdajP8dAHu/6uC8+xTuN3A00KodH+8H0rQIq6GemMF9FbqRZDjb5ouGu5sI2aiTamKomn1XkBQkDkkrRXLbFg1xfG2XwQHuT7+rBrLqc9QSS+55hz3YDoHvs2EGoYjHLbs/5N25V24LbFJfbg1dF5PxVQ4L58NR5A23zNduElWgzsME8UDtgT9/lac= on_success: always on_failure: always template: - - Repo `%{repository_slug}` *%{result}* build (<%{build_url}|#%{build_number}>) - for commit (<%{compare_url}|%{commit}>) on branch `%{branch}`. - - 'Execution time: *%{duration}*' - - 'Message: %{message}' + - "Build <%{build_url}|#%{build_number}> (<%{compare_url}|%{commit}>) of %{repository_slug}@%{branch} in PR <%{pull_request_url}|#%{pull_request_number}> by %{author} %{result} in %{duration}" stages: - name: build if: type = pull_request @@ -28,18 +25,21 @@ jobs: - stage: build script: - npm test - - NODE_ENV=production node /home/travis/build/ioFog/Controller/src/main.js start - npm run postman_test - stage: dev_deploy before_install: - sudo apt-get install sshpass script: - - sshpass -p $DEV_MACHINE_PASSWORD ssh -o StrictHostKeyChecking=no $DEV_MACHINE_USERNAME@$DEV_MACHINE_IP "cd /FogController; NODE_ENV=production node src/main.js stop; git pull; npm i; npm test; NODE_ENV=production node src/main.js start; npm run postman_test" + - sshpass -p $DEV_MACHINE_PASSWORD ssh -o StrictHostKeyChecking=no $DEV_MACHINE_USERNAME@$DEV_MACHINE_IP + "cd /FogController; NODE_ENV=production node src/main.js stop; git pull; npm + i; npm test; npm run postman_test; NODE_ENV=production node src/main.js start;" - stage: pre_release before_install: - sudo apt-get install sshpass script: - - sshpass -p $PREPROD_MACHINE_PASSWORD ssh -o StrictHostKeyChecking=no $PREPROD_MACHINE_USERNAME@$PREPROD_MACHINE_IP "cd /Controller; NODE_ENV=production node src/main.js stop; git pull; git checkout ${TRAVIS_BRANCH}; npm i; npm test; NODE_ENV=production node src/main.js start; npm run postman_test" + - sshpass -p $PREPROD_MACHINE_PASSWORD ssh -o StrictHostKeyChecking=no $PREPROD_MACHINE_USERNAME@$PREPROD_MACHINE_IP + "cd /Controller; NODE_ENV=production node src/main.js stop; git checkout $TRAVIS_BRANCH; npm i; + npm test; npm run postman_test; NODE_ENV=production node src/main.js start;" - stage: release #before_install: #- git clone "https://github.com/$TRAVIS_REPO_SLUG.git" "$TRAVIS_REPO_SLUG"; @@ -54,9 +54,10 @@ jobs: deploy: skip_cleanup: true provider: npm - email: ${NPM_EMAIL_ADDRESS} - api_key: ${NPM_AUTH_TOKEN} + email: "${NPM_EMAIL_ADDRESS}" + api_key: "${NPM_AUTH_TOKEN}" on: tags: false after_deploy: - - sshpass -p $PROD_MACHINE_PASSWORD ssh -o StrictHostKeyChecking=no $PROD_MACHINE_USERNAME@$PROD_MACHINE_IP "iofog-controller stop; npm update -g iofogcontroller; iofog-controller start" + - sshpass -p $PROD_MACHINE_PASSWORD ssh -o StrictHostKeyChecking=no $PROD_MACHINE_USERNAME@$PROD_MACHINE_IP + "iofog-controller stop; npm update -g iofogcontroller; iofog-controller start" diff --git a/package.json b/package.json index c5a125745..9f0e6c4ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iofogcontroller", - "version": "1.0.35", + "version": "1.0.36", "description": "ioFog Controller project for Eclipse IoFog @ iofog.org \\nCopyright (c) 2018 Edgeworx, Inc.", "main": "./src/main.js", "author": "Saeid Baghbidi", @@ -34,17 +34,17 @@ "url": "https://github.com/ioFog/Controller" }, "scripts": { - "start": "node scripts/start.js", - "start-dev": "node scripts/start-dev.js", - "build": "node scripts/init.js", - "preuninstall": "node scripts/preuninstall.js", - "postinstall": "node scripts/postinstall.js", + "start": "node scripts/scripts-api.js start", + "start-dev": "node scripts/scripts-api.js start-dev", + "build": "node scripts/scripts-api.js init", + "preuninstall": "node scripts/scripts-api.js preuninstall", + "postinstall": "node scripts/scripts-api.js postinstall", "lint": "./node_modules/.bin/eslint \"**/*.js\"", "automatic-release": "automatic-release", - "test": "node scripts/test.js && node scripts/cli-tests.js", - "postman_test": "node scripts/postmantest.js", - "cli-tests": "node scripts/cli-tests.js", - "coverage": "node scripts/coverage.js" + "test": "node scripts/scripts-api.js test", + "postman_test": "node scripts/scripts-api.js postmantest", + "cli-tests": "node scripts/scripts-api.js cli-tests", + "coverage": "node scripts/scripts-api.js coverage" }, "preferGlobal": true, "bin": { diff --git a/scripts/cli-tests.js b/scripts/cli-tests.js index 04e97d0a3..e2741afc7 100644 --- a/scripts/cli-tests.js +++ b/scripts/cli-tests.js @@ -12,6 +12,8 @@ */ const execSync = require('child_process').execSync; +const {init} = require('./init'); +const {restoreDBs, backupDBs} = require('./util'); const options = { env: { @@ -394,27 +396,41 @@ function responseContains(response, expectedResponsePart) { } } -try { - testControllerSection(); - testUserSection(); - testConfigSection(); - testConnectorSection(); - testTunnelSection(); - testIoFogSection(); - testCatalogSection(); - testFlowSection(); - testMicroserviceSection(); - testRegistrySection(); - testDiagnosticsSection(); -} catch (exception) { - console.log("\nException during execution: "); - console.error(exception); - process.exit(1); +function cliTest() { + try { + backupDBs(); + //create new DBs + init(); + + testControllerSection(); + testUserSection(); + testConfigSection(); + testConnectorSection(); + testTunnelSection(); + testIoFogSection(); + testCatalogSection(); + testFlowSection(); + testMicroserviceSection(); + testRegistrySection(); + testDiagnosticsSection(); + + restoreDBs(); + } catch (exception) { + restoreDBs(); + + console.log("\nException during execution: "); + console.error(exception); + process.exit(1); + } + + if (testsFailed > 0) { + console.log("\nFailed tests count: " + testsFailed); + process.exit(1); + } else { + console.log("\nCLI Tests passed successfully."); + } } -if (testsFailed > 0) { - console.log("\nFailed tests count: " + testsFailed); - process.exit(1); -} else { - console.log("\nCLI Tests passed successfully."); -} \ No newline at end of file +module.exports = { + cliTest: cliTest +}; \ No newline at end of file diff --git a/scripts/coverage.js b/scripts/coverage.js index fc23b0ecd..3a7dae7a3 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -13,12 +13,18 @@ const execSync = require('child_process').execSync; -const options = { - env: { - 'NODE_ENV': 'test', - "PATH": process.env.PATH - }, - stdio: [process.stdin, process.stdout, process.stderr] -}; +function coverage() { + const options = { + env: { + 'NODE_ENV': 'test', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; -execSync('nyc mocha', options); \ No newline at end of file + execSync('nyc mocha', options); +} + +module.exports = { + coverage: coverage +}; \ No newline at end of file diff --git a/scripts/init.js b/scripts/init.js index 2c1ee958a..3bdf92c54 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -13,12 +13,19 @@ const execSync = require('child_process').execSync; -const options = { - env: { - 'NODE_ENV': 'production', - "PATH": process.env.PATH - }, - stdio: [process.stdin, process.stdout, process.stderr] -}; -execSync('node ./src/main.js init', options); \ No newline at end of file +function init() { + const options = { + env: { + 'NODE_ENV': 'production', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; + + execSync('node ./src/main.js init', options); +} + +module.exports = { + init: init +}; \ No newline at end of file diff --git a/scripts/postinstall.js b/scripts/postinstall.js index f201e0db6..9d8c5c076 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -16,89 +16,49 @@ const execSync = require('child_process').execSync; const fs = require('fs'); const semver = require('semver'); const currentVersion = require('../package').version; +const {restoreDBs, restoreConfigs, INSTALLATION_VARIABLES_FILE} = require('./util'); -const rootDir = `${__dirname}/..`; -let installationVariablesFileName = 'iofogcontroller_install_variables'; -let tempDir = getTempDirLocation(); -const installationVariablesFile = tempDir + '/' + installationVariablesFileName; - +function postinstall() { //restore all files -const devDbBackup = `${tempDir}/dev_database.sqlite`; -const devDb = `${rootDir}/src/sequelize/dev_database.sqlite`; -moveFileIfExists(devDbBackup, devDb); - -const prodDbBackup = `${tempDir}/prod_database.sqlite`; -const prodDb = `${rootDir}/src/sequelize/prod_database.sqlite`; -moveFileIfExists(prodDbBackup, prodDb); - -const defConfigBackup = `${tempDir}/default_iofog_backup.json`; -const defConfig = `${rootDir}/src/config/default.json`; -moveFileIfExists(defConfigBackup, defConfig); - -const prodConfigBackup = `${tempDir}/production_iofog_backup.json`; -const prodConfig = `${rootDir}/src/config/production.json`; -moveFileIfExists(prodConfigBackup, prodConfig); - -const devConfigBackup = `${tempDir}/development_iofog_backup.json`; -const devConfig = `${rootDir}/src/config/development.json`; -moveFileIfExists(devConfigBackup, devConfig); + restoreDBs(); + restoreConfigs(); //process migrations -try { - const installationVarsStr = fs.readFileSync(installationVariablesFile); - const installationVars = JSON.parse(installationVarsStr); - const prevVersion = installationVars.prevVer; - - console.log(`previous version - ${prevVersion}`); - console.log(`new version - ${currentVersion}`); - - if (semver.satisfies(prevVersion, '<=1.0.0')) { - console.log('upgrading from version <= 1.0.0 :'); - insertSeeds(); - } - - if (semver.satisfies(prevVersion, '<=1.0.30')) { - console.log('upgrading from version <= 1.0.30 :'); - updateEncryptionMethod(); - } - - fs.unlinkSync(installationVariablesFile); -} catch (e) { - console.log('no previous version'); -} + try { + const installationVarsStr = fs.readFileSync(INSTALLATION_VARIABLES_FILE); + const installationVars = JSON.parse(installationVarsStr); + const prevVersion = installationVars.prevVer; -//init db -const options = { - env: { - 'NODE_ENV': 'production', - "PATH": process.env.PATH - }, - stdio: [process.stdin, process.stdout, process.stderr] -}; + console.log(`previous version - ${prevVersion}`); + console.log(`new version - ${currentVersion}`); -execSync('node ./src/main.js init', options); + if (semver.satisfies(prevVersion, '<=1.0.0')) { + console.log('upgrading from version <= 1.0.0 :'); + insertSeeds(); + } -//other functions definitions + if (semver.satisfies(prevVersion, '<=1.0.30')) { + console.log('upgrading from version <= 1.0.30 :'); + updateEncryptionMethod(); + } -function getTempDirLocation() { - let tempDir; - if (os.type() === 'Linux') { - tempDir = '/tmp'; - } else if (os.type() === 'Darwin') { - tempDir = '/tmp'; - } else if (os.type() === 'Windows_NT') { - tempDir = `${process.env.APPDATA}`; - } else { - throw new Error("Unsupported OS found: " + os.type()); + fs.unlinkSync(INSTALLATION_VARIABLES_FILE); + } catch (e) { + console.log('no previous version'); } - return tempDir; -} -function moveFileIfExists(from, to) { - if (fs.existsSync(from)) { - fs.renameSync(from, to); - } +//init db + const options = { + env: { + 'NODE_ENV': 'production', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; + + execSync('node ./src/main.js init', options); } +//other functions definitions function insertSeeds() { console.log(' inserting seeds meta info in db'); @@ -189,4 +149,8 @@ function updateEncryptionMethod() { updateEncryptionMethodForEmailService(defConfig, decryptTextVer30); updateEncryptionMethodForEmailService(devConfig, decryptTextVer30); updateEncryptionMethodForEmailService(prodConfig, decryptTextVer30); -} \ No newline at end of file +} + +module.exports = { + postinstall: postinstall +}; \ No newline at end of file diff --git a/scripts/postmantest.js b/scripts/postmantest.js index b3979329c..b695a8926 100644 --- a/scripts/postmantest.js +++ b/scripts/postmantest.js @@ -11,22 +11,41 @@ * */ - const newman = require('newman'); - +const newman = require('newman'); +const {init} = require('./init'); +const {restoreDBs, backupDBs} = require('./util'); +const {start} = require('./start'); +const {stop} = require('./stop'); + +function postmanTest() { + stop(); + backupDBs(); +//create new DBs + init(); + start(); // call newman.run to pass `options` object and wait for callback -newman.run({ - collection: require('../test/postman_collection.json'), - reporters: 'cli', - //abortOnError: true, - //abortOnFailure: true -}).on('start', function (err, args) { // on start of run, log to console - console.log('running a collection...'); -}).on('done', function (err, summary) { - if (err || summary.error || summary.run.failures.length != 0) { - console.error('collection run encountered an error. tests did not pass.'); - process.exitCode = 1; - } - else { - console.log('collection run completed.'); - } -}); + newman.run({ + collection: require('../test/postman_collection.json'), + reporters: 'cli', + //abortOnError: true, + //abortOnFailure: true + }).on('start', function (err, args) { // on start of run, log to console + console.log('running a collection...'); + }).on('done', function (err, summary) { + if (err || summary.error || summary.run.failures.length != 0) { + restoreDBs(); + stop(); + console.error('collection run encountered an error. tests did not pass.'); + process.exitCode = 1; + } + else { + restoreDBs(); + stop(); + console.log('collection run completed.'); + } + }); +} + +module.exports = { + postmanTest: postmanTest +}; diff --git a/scripts/preuninstall.js b/scripts/preuninstall.js index 1c8ed5106..f77b84df5 100644 --- a/scripts/preuninstall.js +++ b/scripts/preuninstall.js @@ -15,52 +15,19 @@ const os = require('os'); const execSync = require('child_process').execSync; const fs = require('fs'); const version = require('../package').version; +const {backupDBs, backupConfigs, INSTALLATION_VARIABLES_FILE} = require('./util'); -const rootDir = `${__dirname}/..`; -let installationVariablesFileName = 'iofogcontroller_install_variables'; -let installationVariablesFile; -let tempDir; +function preuninstall() { + const instalationVars = { + prevVer: version + }; -if (os.type() === 'Linux') { - tempDir = '/tmp'; -} else if (os.type() === 'Darwin') { - tempDir = '/tmp'; -} else if (os.type() === 'Windows_NT') { - tempDir = `${process.env.APPDATA}`; -} else { - throw new Error("Unsupported OS found: " + os.type()); -} - -installationVariablesFile = tempDir + '/' + installationVariablesFileName; - -const instalationVars = { - prevVer: version -}; - -fs.writeFileSync(installationVariablesFile, JSON.stringify(instalationVars)); - -const devDb = `${rootDir}/src/sequelize/dev_database.sqlite`; -if (fs.existsSync(devDb)) { - fs.renameSync(devDb, `${tempDir}/dev_database.sqlite`) -} - -const prodDb = `${rootDir}/src/sequelize/prod_database.sqlite`; -if (fs.existsSync(prodDb)) { - fs.renameSync(prodDb, `${tempDir}/prod_database.sqlite`) -} - -const defConfig = `${rootDir}/src/config/default.json`; -if (fs.existsSync(defConfig)) { - fs.renameSync(defConfig, `${tempDir}/default_iofog_backup.json`) -} - -const devConfig = `${rootDir}/src/config/development.json`; -if (fs.existsSync(devConfig)) { - fs.renameSync(devConfig, `${tempDir}/development_iofog_backup.json`) -} + fs.writeFileSync(INSTALLATION_VARIABLES_FILE, JSON.stringify(instalationVars)); -const prodConfig = `${rootDir}/src/config/production.json`; -if (fs.existsSync(prodConfig)) { - fs.renameSync(prodConfig, `${tempDir}/production_iofog_backup.json`) + backupDBs(); + backupConfigs(); } +module.exports = { + preuninstall: preuninstall +} \ No newline at end of file diff --git a/scripts/scripts-api.js b/scripts/scripts-api.js new file mode 100644 index 000000000..07428e991 --- /dev/null +++ b/scripts/scripts-api.js @@ -0,0 +1,56 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const {start} = require('./start'); +const {startDev} = require('./start-dev'); +const {init} = require('./init'); +const {preuninstall} = require('./preuninstall'); +const {postinstall} = require('./postinstall'); +const {test} = require('./test'); +const {cliTest} = require('./cli-tests'); +const {postmanTest} = require('./postmantest'); +const {coverage} = require('./coverage'); + +switch (process.argv[2]) { + case 'start': + start(); + break; + case 'start-dev': + startDev(); + break; + case 'init': + init(); + break; + case 'preuninstall': + preuninstall(); + break; + case 'postinstall': + postinstall(); + break; + case 'test': + test(); + cliTest(); + break; + case 'postmantest': + postmanTest(); + break; + case 'cli-tests': + cliTest(); + break; + case 'coverage': + coverage(); + break; + default: + console.log('no script for this command'); + break; +} \ No newline at end of file diff --git a/scripts/start-dev.js b/scripts/start-dev.js index 709f4d371..3a1416dc0 100644 --- a/scripts/start-dev.js +++ b/scripts/start-dev.js @@ -13,12 +13,18 @@ const execSync = require('child_process').execSync; -const options = { - env: { - 'NODE_ENV': 'development', - "PATH": process.env.PATH - }, - stdio: [process.stdin, process.stdout, process.stderr] -}; +function startDev() { + const options = { + env: { + 'NODE_ENV': 'development', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; -execSync('node ./src/main.js start', options); \ No newline at end of file + execSync('node ./src/main.js start', options); +} + +module.exports = { + startDev: startDev +}; \ No newline at end of file diff --git a/scripts/start.js b/scripts/start.js index f8f2f1f3f..208fab4d4 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -13,12 +13,18 @@ const execSync = require('child_process').execSync; -const options = { - env: { - 'NODE_ENV': 'production', - "PATH": process.env.PATH - }, - stdio: [process.stdin, process.stdout, process.stderr] -}; +function start() { + const options = { + env: { + 'NODE_ENV': 'production', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; -execSync('node ./src/main.js start', options); \ No newline at end of file + execSync('node ./src/main.js start', options); +} + +module.exports = { + start: start +}; \ No newline at end of file diff --git a/scripts/stop.js b/scripts/stop.js new file mode 100644 index 000000000..1771f8735 --- /dev/null +++ b/scripts/stop.js @@ -0,0 +1,30 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const execSync = require('child_process').execSync; + +function stop() { + const options = { + env: { + 'NODE_ENV': 'production', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; + + execSync('node ./src/main.js stop', options); +} + +module.exports = { + stop: stop +}; \ No newline at end of file diff --git a/scripts/test.js b/scripts/test.js index fc27d1aac..a185cdd73 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -13,12 +13,18 @@ const execSync = require('child_process').execSync; -const options = { - env: { - 'NODE_ENV': 'test', - "PATH": process.env.PATH - }, - stdio: [process.stdin, process.stdout, process.stderr] -}; +function test() { + const options = { + env: { + 'NODE_ENV': 'test', + "PATH": process.env.PATH + }, + stdio: [process.stdin, process.stdout, process.stderr] + }; -execSync('mocha', options); \ No newline at end of file + execSync('mocha', options); +} + +module.exports = { + test: test +}; \ No newline at end of file diff --git a/scripts/util.js b/scripts/util.js new file mode 100644 index 000000000..97d3f6d0a --- /dev/null +++ b/scripts/util.js @@ -0,0 +1,90 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const os = require('os'); +const fs = require('fs'); +const ROOT_DIR = `${__dirname}/..`; +const TEMP_DIR = getTempDir(); + +const DEV_DB = `${ROOT_DIR}/src/sequelize/dev_database.sqlite`; +const DEV_DB_BACKUP = `${TEMP_DIR}/dev_database.sqlite`; + +const PROD_DB = `${ROOT_DIR}/src/sequelize/prod_database.sqlite`; +const PROD_DB_BACKUP = `${TEMP_DIR}/prod_database.sqlite`; + +const DEFAULT_CONFIG = `${ROOT_DIR}/src/config/default.json`; +const DEVELOP_CONFIG = `${ROOT_DIR}/src/config/development.json`; +const PRODUCTION_CONFIG = `${ROOT_DIR}/src/config/production.json`; + +const DEFAULT_CONFIG_BACKUP = `${TEMP_DIR}/default_iofog_backup.json`; +const DEVELOP_CONFIG_BACKUP = `${TEMP_DIR}/development_iofog_backup.json`; +const PRODUCTION_CONFIG_BACKUP = `${TEMP_DIR}/production_iofog_backup.json`; + +const INSTALLATION_VARIABLES_FILE = TEMP_DIR + '/iofogcontroller_install_variables'; + + +function backupDBs() { + renameFile(DEV_DB, DEV_DB_BACKUP); + renameFile(PROD_DB, PROD_DB_BACKUP); +} + +function restoreDBs() { + renameFile(DEV_DB_BACKUP, DEV_DB); + renameFile(PROD_DB_BACKUP, PROD_DB); +} + +function renameFile(oldPath, newPath) { + if (fs.existsSync(oldPath)) { + fs.renameSync(oldPath, newPath) + } +} + +function getTempDir() { + let tempDir; + + if (os.type() === 'Linux') { + tempDir = '/tmp'; + } else if (os.type() === 'Darwin') { + tempDir = '/tmp'; + } else if (os.type() === 'Windows_NT') { + tempDir = `${process.env.APPDATA}`; + } else { + throw new Error("Unsupported OS found: " + os.type()); + } + + return tempDir; +} + +function backupConfigs() { + renameFile(DEFAULT_CONFIG, DEFAULT_CONFIG_BACKUP); + renameFile(DEVELOP_CONFIG, DEVELOP_CONFIG_BACKUP); + renameFile(PRODUCTION_CONFIG, PRODUCTION_CONFIG_BACKUP); +} + +function restoreConfigs() { + renameFile(DEFAULT_CONFIG_BACKUP, DEFAULT_CONFIG); + renameFile(DEVELOP_CONFIG_BACKUP, DEVELOP_CONFIG); + renameFile(PRODUCTION_CONFIG_BACKUP, PRODUCTION_CONFIG); +} + +module.exports = { + backupDBs: backupDBs, + restoreDBs: restoreDBs, + backupConfigs: backupConfigs, + restoreConfigs: restoreConfigs, + renameFile: renameFile, + getTempDir: getTempDir, + + TEMP_DIR: TEMP_DIR, + INSTALLATION_VARIABLES_FILE: INSTALLATION_VARIABLES_FILE +}; \ No newline at end of file diff --git a/src/cli/catalog.js b/src/cli/catalog.js index 07d5c03b4..42c61c72e 100644 --- a/src/cli/catalog.js +++ b/src/cli/catalog.js @@ -20,6 +20,7 @@ const AppHelper = require('../helpers/app-helper'); const AuthDecorator = require('../decorators/cli-decorator'); const Errors = require('../helpers/errors'); const ErrorMessages = require('../helpers/error-messages'); +const CliDataTypes = require('./cli-data-types'); const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ name: "string", @@ -63,7 +64,7 @@ class Catalog extends BaseCLIHandler { group: [constants.CMD_ADD, constants.CMD_UPDATE] }, { - name: 'item-id', alias: 'i', type: Number, description: 'Catalog item ID', + name: 'item-id', alias: 'i', type: CliDataTypes.Integer, description: 'Catalog item ID', group: [constants.CMD_UPDATE, constants.CMD_REMOVE, constants.CMD_INFO] }, { @@ -91,12 +92,12 @@ class Catalog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'disk-required', alias: 's', type: Number, + name: 'disk-required', alias: 's', type: CliDataTypes.Integer, description: 'Amount of disk required to run the microservice (MB)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'ram-required', alias: 'r', type: Number, + name: 'ram-required', alias: 'r', type: CliDataTypes.Integer, description: 'Amount of RAM required to run the microservice (MB)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, @@ -113,7 +114,8 @@ class Catalog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'registry-id', alias: 'g', type: Number, description: 'Catalog item docker registry ID', + name: 'registry-id', alias: 'g', type: CliDataTypes.Integer, + description: 'Catalog item docker registry ID', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { @@ -137,7 +139,7 @@ class Catalog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'user-id', alias: 'u', type: Number, description: 'User\'s id', + name: 'user-id', alias: 'u', type: CliDataTypes.Integer, description: 'User\'s id', group: [constants.CMD_ADD] }, ]; diff --git a/src/cli/cli-data-types.js b/src/cli/cli-data-types.js new file mode 100644 index 000000000..700c3ecce --- /dev/null +++ b/src/cli/cli-data-types.js @@ -0,0 +1,31 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +/** + * @return {number} + */ +function Integer(value) { + return Number(value) +} + +/** + * @return {number} + */ +function Float(value) { + return Number(value) +} + +module.exports = { + Integer: Integer, + Float: Float +} \ No newline at end of file diff --git a/src/cli/config.js b/src/cli/config.js index a6a6860ad..844bc64e5 100644 --- a/src/cli/config.js +++ b/src/cli/config.js @@ -18,6 +18,9 @@ const AppHelper = require('../helpers/app-helper'); const ErrorMessages = require('../helpers/error-messages'); const Validator = require('../schemas'); const logger = require('../logger'); +const Tracking = require('../tracking'); +const TrackingEventType = require('../enums/tracking-event-type'); +const CliDataTypes = require('./cli-data-types'); class Config extends BaseCLIHandler { constructor() { @@ -30,7 +33,7 @@ class Config extends BaseCLIHandler { group: constants.CMD }, { - name: 'port', alias: 'p', type: Number, description: 'Port', + name: 'port', alias: 'p', type: CliDataTypes.Integer, description: 'Port', group: constants.CMD_ADD }, { @@ -72,7 +75,7 @@ class Config extends BaseCLIHandler { group: constants.CMD_ADD }, { - name: 'log-size', alias: 'z', type: Number, + name: 'log-size', alias: 'z', type: CliDataTypes.Integer, description: 'Log files size (MB)', group: constants.CMD_ADD }, { @@ -244,10 +247,14 @@ const _listConfigOptions = function () { console.log(result) }; -const _changeDevModeState = function (options) { +const _changeDevModeState = async function (options) { const enableDevMode = AppHelper.validateBooleanCliOptions(options.on, options.off); config.set('Server:DevMode', enableDevMode); - logger.info('Dev mode state updated successfully.') + logger.info('Dev mode state updated successfully.'); + + //example of tracking for other config + const event = Tracking.buildEvent(TrackingEventType.CONFIG_CHANGED, `devMode was set to ${enableDevMode}`); + await Tracking.processEvent(event); }; const _changeEmailActivationState = function (options) { diff --git a/src/cli/diagnostics.js b/src/cli/diagnostics.js index dd3ab2593..fe7eb4bdb 100644 --- a/src/cli/diagnostics.js +++ b/src/cli/diagnostics.js @@ -17,6 +17,7 @@ const logger = require('../logger'); const DiagnosticService = require('../services/diagnostic-service'); const AppHelper = require('../helpers/app-helper'); const AuthDecorator = require('../decorators/cli-decorator'); +const CliDataTypes = require('./cli-data-types'); class Diagnostics extends BaseCLIHandler { @@ -51,7 +52,7 @@ class Diagnostics extends BaseCLIHandler { group: [constants.CMD_STRACE_FTP_POST] }, { - name: 'ftpPort', alias: 'p', type: Number, description: 'FTP port', + name: 'ftpPort', alias: 'p', type: CliDataTypes.Integer, description: 'FTP port', group: [constants.CMD_STRACE_FTP_POST] }, { diff --git a/src/cli/flow.js b/src/cli/flow.js index e61cfc9fb..9f3803181 100644 --- a/src/cli/flow.js +++ b/src/cli/flow.js @@ -18,6 +18,7 @@ const FlowService = require('../services/flow-service'); const AppHelper = require('../helpers/app-helper'); const logger = require('../logger'); const fs = require('fs'); +const CliDataTypes = require('./cli-data-types'); const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ name: "string", @@ -41,7 +42,7 @@ class Flow extends BaseCLIHandler { group: [constants.CMD_ADD, constants.CMD_UPDATE] }, { - name: 'flow-id', alias: 'i', type: Number, + name: 'flow-id', alias: 'i', type: CliDataTypes.Integer, description: 'Application flow ID', group: [constants.CMD_UPDATE, constants.CMD_REMOVE, constants.CMD_INFO] }, @@ -66,7 +67,7 @@ class Flow extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'user-id', alias: 'u', type: Number, + name: 'user-id', alias: 'u', type: CliDataTypes.Integer, description: 'User\'s id', group: [constants.CMD_ADD] } diff --git a/src/cli/iofog.js b/src/cli/iofog.js index 093765263..f02edbddb 100644 --- a/src/cli/iofog.js +++ b/src/cli/iofog.js @@ -18,6 +18,7 @@ const fs = require('fs'); const CliDecorator = require('../decorators/cli-decorator'); const AppHelper = require('../helpers/app-helper'); const FogService = require('../services/iofog-service'); +const CliDataTypes = require('./cli-data-types'); const JSON_SCHEMA = AppHelper.stringifyCliJsonSchema({ name: "string", @@ -74,12 +75,12 @@ class IOFog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'latitude', alias: 't', type: Number, + name: 'latitude', alias: 't', type: CliDataTypes.Float, description: 'ioFog node latitude', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'longitude', alias: 'g', type: Number, + name: 'longitude', alias: 'g', type: CliDataTypes.Float, description: 'ioFog node longitude', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, @@ -94,7 +95,7 @@ class IOFog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'disk-limit', alias: 'M', type: Number, + name: 'disk-limit', alias: 'M', type: CliDataTypes.Float, description: 'ioFog node disk usage limit (MB)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, @@ -104,17 +105,17 @@ class IOFog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'memory-limit', alias: 'm', type: Number, + name: 'memory-limit', alias: 'm', type: CliDataTypes.Float, description: 'ioFog node memory usage limit (MB)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'cpu-limit', alias: 'c', type: Number, + name: 'cpu-limit', alias: 'c', type: CliDataTypes.Float, description: 'ioFog node CPU usage limit (%)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'log-limit', alias: 'G', type: Number, + name: 'log-limit', alias: 'G', type: CliDataTypes.Float, description: 'ioFog node log size limit (MB)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, @@ -124,22 +125,22 @@ class IOFog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'log-file-count', alias: 'C', type: Number, + name: 'log-file-count', alias: 'C', type: CliDataTypes.Integer, description: 'ioFog node log files count', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'status-frequency', alias: 's', type: Number, + name: 'status-frequency', alias: 's', type: CliDataTypes.Integer, description: 'ioFog node status check frequency (seconds)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'change-frequency', alias: 'F', type: Number, + name: 'change-frequency', alias: 'F', type: CliDataTypes.Integer, description: 'ioFog node configuration change check frequency (seconds)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'device-frequency', alias: 'Q', type: Number, + name: 'device-frequency', alias: 'Q', type: CliDataTypes.Integer, description: 'ioFog node device scan frequency (seconds)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, @@ -179,7 +180,7 @@ class IOFog extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'fog-type', alias: 'y', type: Number, + name: 'fog-type', alias: 'y', type: CliDataTypes.Integer, description: 'ioFog node architecture type', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, @@ -189,7 +190,7 @@ class IOFog extends BaseCLIHandler { group: [constants.CMD_VERSION] }, { - name: 'user-id', alias: 'u', type: Number, + name: 'user-id', alias: 'u', type: CliDataTypes.Integer, description: 'User\'s id', group: [constants.CMD_ADD] } diff --git a/src/cli/microservice.js b/src/cli/microservice.js index fde3158c1..ab21361f3 100644 --- a/src/cli/microservice.js +++ b/src/cli/microservice.js @@ -19,6 +19,7 @@ const MicroserviceService = require('../services/microservices-service'); const fs = require('fs'); const AppHelper = require('../helpers/app-helper'); const CliDecorator = require('../decorators/cli-decorator'); +const CliDataTypes = require('./cli-data-types'); const JSON_SCHEMA_ADD = AppHelper.stringifyCliJsonSchema( { @@ -92,11 +93,11 @@ class Microservice extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'catalog-id', alias: 'c', type: Number, description: 'Catalog item ID', + name: 'catalog-id', alias: 'c', type: CliDataTypes.Integer, description: 'Catalog item ID', group: [constants.CMD_ADD] }, { - name: 'flow-id', alias: 'F', type: Number, description: 'Application flow ID', + name: 'flow-id', alias: 'F', type: CliDataTypes.Integer, description: 'Application flow ID', group: [constants.CMD_ADD] }, { @@ -112,7 +113,7 @@ class Microservice extends BaseCLIHandler { group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { - name: 'log-size', alias: 'l', type: Number, description: 'Log file size limit (MB)', + name: 'log-size', alias: 'l', type: CliDataTypes.Integer, description: 'Log file size limit (MB)', group: [constants.CMD_UPDATE, constants.CMD_ADD] }, { @@ -142,7 +143,7 @@ class Microservice extends BaseCLIHandler { group: [constants.CMD_ROUTE_CREATE, constants.CMD_ROUTE_REMOVE] }, { - name: 'internal-port', alias: 'b', type: Number, description: 'Internal port', + name: 'internal-port', alias: 'b', type: CliDataTypes.Integer, description: 'Internal port', group: [constants.CMD_PORT_MAPPING_REMOVE] }, { @@ -154,11 +155,11 @@ class Microservice extends BaseCLIHandler { group: [constants.CMD_REMOVE] }, { - name: 'user-id', alias: 'u', type: Number, description: 'User\'s id', + name: 'user-id', alias: 'u', type: CliDataTypes.Integer, description: 'User\'s id', group: [constants.CMD_ADD] }, { - name: 'mapping-id', alias: 'a', type: Number, description: 'Volume mapping id', + name: 'mapping-id', alias: 'a', type: CliDataTypes.Integer, description: 'Volume mapping id', group: [constants.CMD_VOLUME_MAPPING_REMOVE] } ]; @@ -358,8 +359,7 @@ const _createVolumeMapping = async function (obj, user) { const _removePortMapping = async function (obj, user) { try { - const internalPort = parseInt(obj.internalPort); - await MicroserviceService.deletePortMapping(obj.microserviceUuid, internalPort, user, true); + await MicroserviceService.deletePortMapping(obj.microserviceUuid, obj.internalPort, user, true); logger.info('Port mapping has been removed successfully.'); } catch (e) { logger.error(e.message); diff --git a/src/cli/registry.js b/src/cli/registry.js index 4b91da34c..4cfdde316 100644 --- a/src/cli/registry.js +++ b/src/cli/registry.js @@ -17,6 +17,7 @@ const logger = require('../logger'); const CliDecorator = require('../decorators/cli-decorator'); const RegistryService = require('../services/registry-service'); const AppHelper = require('../helpers/app-helper'); +const CliDataTypes = require('./cli-data-types'); class Registry extends BaseCLIHandler { constructor() { @@ -69,12 +70,12 @@ class Registry extends BaseCLIHandler { group: [constants.CMD_ADD, constants.CMD_UPDATE] }, { - name: 'user-id', alias: 'u', type: Number, + name: 'user-id', alias: 'u', type: CliDataTypes.Integer, description: 'User\'s id', group: [constants.CMD_ADD] }, { - name: 'item-id', alias: 'i', type: Number, + name: 'item-id', alias: 'i', type: CliDataTypes.Integer, description: 'Item\'s id', group: [constants.CMD_REMOVE, constants.CMD_UPDATE] } diff --git a/src/cli/tunnel.js b/src/cli/tunnel.js index 304426321..79501b189 100644 --- a/src/cli/tunnel.js +++ b/src/cli/tunnel.js @@ -20,7 +20,7 @@ const CliDecorator = require('../decorators/cli-decorator'); const Errors = require('../helpers/errors'); const ErrorMessages = require('../helpers/error-messages'); const AppHelper = require('../helpers/app-helper'); - +const CliDataTypes = require('./cli-data-types'); class Tunnel extends BaseCLIHandler { constructor() { @@ -54,7 +54,7 @@ class Tunnel extends BaseCLIHandler { group: [constants.CMD_UPDATE] }, { - name: 'port', alias: 'o', type: Number, + name: 'port', alias: 'o', type: CliDataTypes.Integer, description: 'Tunnel port', group: [constants.CMD_UPDATE] }, diff --git a/src/config/default.json b/src/config/default.json index f18f594eb..62d9aa2f4 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -15,6 +15,7 @@ "LogsFileSize": 1048576 }, "Settings": { + "DefaultJobIntervalSeconds": 120, "UserTokenExpirationIntervalSeconds": 3600, "FogTokenExpirationIntervalSeconds": 3600, "FogStatusUpdateIntervalSeconds": 120, diff --git a/src/controllers/agent-controller.js b/src/controllers/agent-controller.js index e1740f6e3..aefb4c678 100644 --- a/src/controllers/agent-controller.js +++ b/src/controllers/agent-controller.js @@ -120,6 +120,13 @@ const putImageSnapshotEndPoint = async function (req, fog) { return await AgentService.putImageSnapshot(req, fog); }; +async function postTrackingEndPoint(req, fog) { + const events = req.body.events; + + logger.info("Parameters: " + JSON.stringify(events)); + return await AgentService.postTracking(events, fog) +} + module.exports = { agentProvisionEndPoint: agentProvisionEndPoint, agentDeprovisionEndPoint: AuthDecorator.checkFogToken(agentDeprovisionEndPoint), @@ -138,5 +145,6 @@ module.exports = { updateHalUsbInfoEndPoint: AuthDecorator.checkFogToken(updateHalUsbInfoEndPoint), deleteNodeEndPoint: AuthDecorator.checkFogToken(deleteNodeEndPoint), getImageSnapshotEndPoint: AuthDecorator.checkFogToken(getImageSnapshotEndPoint), - putImageSnapshotEndPoint: AuthDecorator.checkFogToken(putImageSnapshotEndPoint) + putImageSnapshotEndPoint: AuthDecorator.checkFogToken(putImageSnapshotEndPoint), + postTrackingEndPoint: AuthDecorator.checkFogToken(postTrackingEndPoint) }; \ No newline at end of file diff --git a/src/decorators/tracking-decorator.js b/src/decorators/tracking-decorator.js new file mode 100644 index 000000000..e4083af07 --- /dev/null +++ b/src/decorators/tracking-decorator.js @@ -0,0 +1,35 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const { isTest} = require('../helpers/app-helper'); +const Tracking = require('../tracking'); + +function trackEvent(f, eventType) { + return async function() { + if (isTest()) { + return await f.apply(this, arguments); + } + + const fArgs = Array.prototype.slice.call(arguments); + const res = await f.apply(this, arguments); + const event = Tracking.buildEvent(eventType, res, fArgs, f.name); + await Tracking.processEvent(event, fArgs, res); + return res; + + } +} + + +module.exports = { + trackEvent: trackEvent +}; \ No newline at end of file diff --git a/src/enums/tracking-event-type.js b/src/enums/tracking-event-type.js new file mode 100644 index 000000000..64b0ab702 --- /dev/null +++ b/src/enums/tracking-event-type.js @@ -0,0 +1,27 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const trackingEventType = Object.freeze({ + USER_CREATED: 'user created', + RUNNING_TIME: 'running time', + INIT: 'init', + START: 'start', + IOFOG_CREATED: 'iofog created', + IOFOG_PROVISION: 'iofog provision', + MICROSERVICE_CREATED: 'microservice created', + CATALOG_CREATED: 'catalog created', + CONFIG_CHANGED: 'config changed', + OTHER: 'other', +}); + +module.exports = trackingEventType; \ No newline at end of file diff --git a/src/helpers/app-helper.js b/src/helpers/app-helper.js index 2cd8a0ad4..1a70210f2 100644 --- a/src/helpers/app-helper.js +++ b/src/helpers/app-helper.js @@ -163,10 +163,10 @@ function stringifyCliJsonSchema(json) { function handleCLIError(error) { switch (error.name) { case "UNKNOWN_OPTION": - console.log("Unknown parameter " + error.optionName); + console.log("Invalid argument '" + error.optionName.split('-').join('') + "'"); break; case "UNKNOWN_VALUE": - console.log("Unknown value " + error.value); + console.log("Invalid value " + error.value); break; case "InvalidArgumentError": console.log(error.message); @@ -189,6 +189,28 @@ function trimCertificate(cert) { return result; } +function argsArrayAsMap(args) { + let argsVars = args.join(' ').split(/(?= -{1,2}[^-]+)/); + const argsMap = new Map(); + argsVars + .map(pair => pair.trim()) + .map(pair => { + const spaceIndex = pair.indexOf(' '); + let key, values; + if (spaceIndex !== -1) { + key = pair.substr(0, pair.indexOf(' ')); + values = pair.substr(pair.indexOf(' ')+1).split(' '); + argsMap.set(key, values); + } else { + key = pair; + values = []; + } + argsMap.set(key, values); + + }); + return argsMap; +} + function validateParameters(command, commandDefinitions, args) { // 1st argument = command args.shift(); @@ -196,38 +218,60 @@ function validateParameters(command, commandDefinitions, args) { const possibleAliasesList = _getPossibleAliasesList(command, commandDefinitions); const possibleArgsList = _getPossibleArgsList(command, commandDefinitions); - let currentArgType; - let currwentArgName; + let expectedValueType; + let currentArgName; - for (const arg of args) { - // arg is [argument, alias, value] + if (args.length === 0) { + return + } + const argsMap = argsArrayAsMap(args); - if (arg.startsWith("--")) { // argument + argsMap.forEach((values, key) => { + if (key.startsWith("--")) { // argument // '--ssl-cert' format -> 'ssl-cert' format - const argument = arg.substr(2); + const argument = key.substr(2); _validateArg(argument, possibleArgsList); - currwentArgName = argument; - currentArgType = _getValType(argument, commandDefinitions); - } else if (arg.startsWith("-")) { // alias + currentArgName = argument; + expectedValueType = _getValType(argument, commandDefinitions); + } else if (key.startsWith("-")) { // alias // '-q' format -> 'q' format - const alias = arg.substr(1); + const alias = key.substr(1); _validateArg(alias, possibleAliasesList); - currwentArgName = alias; - currentArgType = _getValType(alias, commandDefinitions); - } else { - // value - let valType; - const nArg = new Number(arg); - if (isNaN(nArg)) { + currentArgName = alias; + expectedValueType = _getValType(alias, commandDefinitions); + } + + let valType; + if (values.length === 0) { + valType = 'boolean'; + } else if (values.length === 1) { + const firstVal = Number(values[0]); + if (Number.isNaN(firstVal.valueOf())) { valType = 'string'; + } else if (Number.isInteger(firstVal.valueOf())) { + valType = 'integer'; } else { - valType = 'number'; - } - if (valType !== currentArgType && currentArgType !== 'string') { - throw new Errors.InvalidArgumentTypeError(formatMessage(ErrorMessages.INVALID_CLI_ARGUMENT_TYPE, currwentArgName, currentArgType)) + valType = 'float' } } - } + //TODO else validate multiply parameters. Add after multiply parameters will be used in cli api + + let isValidType = true; + if (expectedValueType === 'string' && valType === 'boolean') { + isValidType = false; + } else if ((expectedValueType === 'float' || expectedValueType === 'number') + && (valType !== 'float' && valType !== 'number' && valType !== 'integer')) { + isValidType = false; + } else if (expectedValueType === 'integer' && valType !== 'integer') { + isValidType = false; + } else if (expectedValueType === 'boolean' && valType !== 'boolean') { + isValidType = false; + } + + if (!isValidType) { + throw new Errors.InvalidArgumentTypeError(formatMessage(ErrorMessages.INVALID_CLI_ARGUMENT_TYPE, currentArgName, expectedValueType)); + } + }) } function _validateArg(arg, aliasesList) { @@ -302,6 +346,13 @@ function isEmpty(obj) { return true; } +function isOnline() { + const daemon = require('../daemon'); + + let pid = daemon.status(); + return pid !== 0; +} + module.exports = { encryptText, decryptText, @@ -322,5 +373,6 @@ module.exports = { trimCertificate, validateParameters, isTest, - isEmpty + isEmpty, + isOnline }; diff --git a/src/helpers/error-messages.js b/src/helpers/error-messages.js index 7544a4145..9caebc8df 100644 --- a/src/helpers/error-messages.js +++ b/src/helpers/error-messages.js @@ -12,7 +12,7 @@ */ module.exports = { - INVALID_CLI_ARGUMENT_TYPE: 'Type of argument "{}" should be a {}', + INVALID_CLI_ARGUMENT_TYPE: 'Field "{}" is not of type(s) {}', DUPLICATE_NAME: "Duplicate name '{}'", ALREADY_EXISTS: 'Model already exists', INVALID_CATALOG_ITEM_ID: "Invalid catalog item id '{}'", @@ -24,6 +24,8 @@ module.exports = { INVALID_IOFOG_UUID: "Invalid ioFog UUID '{}'", INVALID_USER_EMAIL: 'Invalid user email', INVALID_MICROSERVICE_UUID: "Invalid microservice UUID '{}'", + INVALID_SOURCE_MICROSERVICE_UUID: "Invalid source microservice UUID '{}'", + INVALID_DEST_MICROSERVICE_UUID: "Invalid destination microservice UUID '{}'", INVALID_MICROSERVICE_STRACE: "Strace data for this microservice not found", INVALID_VOLUME_MAPPING_UUID: "Invalid volume mapping id '{}'", ACTIVATION_CODE_NOT_FOUND: 'Activation code not found', @@ -56,6 +58,7 @@ module.exports = { RESTRICTED_PUBLISHER: "You are not allowed to add catalog item as 'Eclipse ioFog' publisher", REQUIRED_FOG_NODE: 'ioFog node is required.', PORT_MAPPING_ALREADY_EXISTS: 'Port mapping already exists', + PORT_MAPPING_INTERNAL_PORT_NOT_PROVIDED: 'Internal port wasn\'t provided', VOLUME_MAPPING_ALREADY_EXISTS: 'Volume mapping already exists', INVALID_CONNECTOR_DOMAIN: 'Invalid connector domain {}', CERT_PROPERTY_REQUIRED: 'Property "certificate" is required if property "requiresCert" is set to true', @@ -76,8 +79,10 @@ module.exports = { INVALID_VERSION_COMMAND_UPGRADE: 'Can\'t upgrade version now. Latest is already installed', INVALID_VERSION_COMMAND_ROLLBACK: 'Can\'t rollback version now. There are no backups on agent', CATALOG_ITEM_IMAGES_IS_FROZEN: 'Can\'t update catalog item images for item used for running microservices', - SYSTEM_CATALOG_ITEM_UPDATE: 'Catalog item id {} is systemic and can\'t be updated', - SYSTEM_CATALOG_ITEM_DELETE: 'Catalog item id {} is systemic and can\'t be deleted', CATALOG_UPDATE_NO_FIELDS: 'Add some parameters which should be updated.', - CATALOG_UPDATE_REQUIRES_ID: "Parameter '--item-id' is missing, update requires Catalog id." + CATALOG_UPDATE_REQUIRES_ID: "Parameter '--item-id' is missing, update requires Catalog id.", + SYSTEM_CATALOG_ITEM_UPDATE: 'Catalog item id {} is system and can\'t be updated', + SYSTEM_CATALOG_ITEM_DELETE: 'Catalog item id {} is system and can\'t be deleted', + SYSTEM_MICROSERVICE_UPDATE: 'Microservice uuid {} is system and can\'t be updated', + SYSTEM_MICROSERVICE_DELETE: 'Microservice uuid {} is system and can\'t be deleted' }; diff --git a/src/jobs/base/base-job-handler.js b/src/jobs/base/base-job-handler.js index 0f356cbfa..81fac4dc2 100644 --- a/src/jobs/base/base-job-handler.js +++ b/src/jobs/base/base-job-handler.js @@ -15,7 +15,7 @@ const Config = require('../../config'); class BaseJobHandler { constructor() { - this.scheduleTime = Config.get('Settings:FogStatusUpdateIntervalSeconds') * 1000; + this.scheduleTime = Config.get('Settings:DefaultJobIntervalSeconds') * 1000; } run() { diff --git a/src/jobs/fog-status-job.js b/src/jobs/fog-status-job.js index 78aa256cb..25642df82 100644 --- a/src/jobs/fog-status-job.js +++ b/src/jobs/fog-status-job.js @@ -24,7 +24,8 @@ const Config = require('../config'); class FogStatusJob extends BaseJobHandler { constructor() { - super() + super(); + this.scheduleTime = Config.get('Settings:FogStatusUpdateIntervalSeconds') * 1000; } run() { diff --git a/src/jobs/send-tracking-job.js b/src/jobs/send-tracking-job.js new file mode 100644 index 000000000..2af0366d4 --- /dev/null +++ b/src/jobs/send-tracking-job.js @@ -0,0 +1,43 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const BaseJobHandler = require('./base/base-job-handler'); +const Tracking = require('../tracking'); +const TrackingEventType = require('../enums/tracking-event-type'); +const TrackingEventManager = require('../sequelize/managers/tracking-event-manager'); + +class SendTrackingJob extends BaseJobHandler { + + constructor() { + super(); + this.scheduleTime = intervalMin * 60 * 1000; + } + + run() { + setInterval(sendTracking, this.scheduleTime); + } +} + +const intervalMin = 5; + +async function sendTracking() { + const fakeTransactionObject = {fakeTransaction: true}; + const events = await TrackingEventManager.popAll(fakeTransactionObject); + try { + Tracking.sendEvents(events); + } catch (e) { + //TODO log only in file. add after logging will fixed + } +} + +module.exports = new SendTrackingJob(); \ No newline at end of file diff --git a/src/jobs/time-tracking-job.js b/src/jobs/time-tracking-job.js new file mode 100644 index 000000000..00f53c24c --- /dev/null +++ b/src/jobs/time-tracking-job.js @@ -0,0 +1,40 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const BaseJobHandler = require('./base/base-job-handler'); +const Tracking = require('../tracking'); +const TrackingEventType = require('../enums/tracking-event-type'); + +class TimeTrackingJob extends BaseJobHandler { + + constructor() { + super(); + this.scheduleTime = intervalMin * 60 * 1000; + } + + run() { + setInterval(trackTime, this.scheduleTime); + } +} + +let iteration = 0; +const intervalMin = 5; + +async function trackTime() { + iteration++; + const runningTime = iteration * intervalMin; + const event = Tracking.buildEvent(TrackingEventType.RUNNING_TIME, runningTime,); + await Tracking.processEvent(event); +} + +module.exports = new TimeTrackingJob(); \ No newline at end of file diff --git a/src/routes/agent.js b/src/routes/agent.js index b5d4daacf..4fd60ff19 100644 --- a/src/routes/agent.js +++ b/src/routes/agent.js @@ -442,6 +442,31 @@ module.exports = [ successCode, errorCodes); const responseObject = await putImageSnapshotEndPoint(req); + res + .status(responseObject.code) + .send(responseObject.body) + } + }, + { + method: 'post', + path: '/api/v3/agent/tracking', + middleware: async (req, res) => { + const successCode = constants.HTTP_CODE_NO_CONTENT; + const errorCodes = [ + { + code: constants.HTTP_CODE_UNAUTHORIZED, + errors: [Errors.AuthenticationError] + }, + { + code: constants.HTTP_CODE_BAD_REQUEST, + errors: [Errors.ValidationError] + } + ]; + + const postTracking = ResponseDecorator.handleErrors(AgentController.postTrackingEndPoint, + successCode, errorCodes); + const responseObject = await postTracking(req); + res .status(responseObject.code) .send(responseObject.body) diff --git a/src/schemas/agent.js b/src/schemas/agent.js index e6cc0e3e9..37b5498d5 100644 --- a/src/schemas/agent.js +++ b/src/schemas/agent.js @@ -151,8 +151,36 @@ const updateUsbInfo = { "additionalProperties": false }; +const trackingArray = { + "id": "/trackingArray", + "type": "array", + "items": {"$ref": "/trackingMessage"} +}; + +const trackingMessage = { + "id": "/trackingMessage", + "type": "object", + "properties": { + "uuid": {"type": "string"}, + "sourceType": {"type": "string"}, + "timestamp": {"type": "number"}, + "type": {"type": "string"}, + "data": {"$ref": "/trackingData"}, + }, + "required": ["uuid", "sourceType", "timestamp", "type", "data"], + "additionalProperties": false +}; + +const trackingData = { + "id": "/trackingData", + "type": "object", + "properties": { + }, + "additionalProperties": true +}; + module.exports = { mainSchemas: [agentProvision, agentDeprovision, updateAgentConfig, updateAgentStatus, updateAgentStrace, - updateHardwareInfo, updateUsbInfo], - innerSchemas: [straceData, microserviceStatus] + updateHardwareInfo, updateUsbInfo, trackingArray], + innerSchemas: [straceData, microserviceStatus, trackingData, trackingMessage] }; \ No newline at end of file diff --git a/src/sequelize/managers/microservice-manager.js b/src/sequelize/managers/microservice-manager.js index 00d73f571..4a4e78b26 100644 --- a/src/sequelize/managers/microservice-manager.js +++ b/src/sequelize/managers/microservice-manager.js @@ -220,13 +220,19 @@ class MicroserviceManager extends BaseManager { }, {transaction: transaction}) } - findOneWithStatus(where, transaction) { + findOneWithStatusAndCategory(where, transaction) { return Microservice.findOne({ include: [ { model: MicroserviceStatus, as: 'microserviceStatus', - required: true + required: false + }, + { + model: CatalogItem, + as: 'catalogItem', + required: true, + attributes: ['category'] } ], where: where @@ -239,7 +245,7 @@ class MicroserviceManager extends BaseManager { { model: MicroserviceStatus, as: 'microserviceStatus', - required: true + required: false } ], where: where @@ -288,6 +294,20 @@ class MicroserviceManager extends BaseManager { transaction: transaction }); } + + findOneWithCategory(where, transaction) { + return Microservice.findOne({ + include: [ + { + model: CatalogItem, + as: 'catalogItem', + required: true, + attributes: ['category'] + } + ], + where: where + }, {transaction: transaction}) + } } const instance = new MicroserviceManager(); diff --git a/src/sequelize/managers/tracking-event-manager.js b/src/sequelize/managers/tracking-event-manager.js new file mode 100644 index 000000000..bc08ec828 --- /dev/null +++ b/src/sequelize/managers/tracking-event-manager.js @@ -0,0 +1,31 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const BaseManager = require('./base-manager'); +const models = require('./../models'); +const TrackingEvent = models.TrackingEvent; + +class TrackingEventManager extends BaseManager { + getEntity() { + return TrackingEvent; + } + + async popAll(transaction) { + const res = await this.findAll({}, transaction); + await this.delete({} ,transaction); + return res; + } +} + +const instance = new TrackingEventManager(); +module.exports = instance; \ No newline at end of file diff --git a/src/sequelize/migrations/20190117110458-create-tracking-event.js b/src/sequelize/migrations/20190117110458-create-tracking-event.js new file mode 100644 index 000000000..701278d18 --- /dev/null +++ b/src/sequelize/migrations/20190117110458-create-tracking-event.js @@ -0,0 +1,38 @@ +'use strict'; +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.createTable('TrackingEvents', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + uuid: { + type: Sequelize.TEXT, + allowNull: false, + field: 'uuid' + }, + sourceType: { + type: Sequelize.TEXT, + field: 'source_type' + }, + timestamp: { + type: Sequelize.BIGINT, + field: 'timestamp' + }, + type: { + type: Sequelize.TEXT, + field: 'type' + }, + data: { + type: Sequelize.TEXT, + field: 'data' + }, + }); + }, + down: (queryInterface, Sequelize) => { + return queryInterface.dropTable('TrackingEvents'); + } +}; \ No newline at end of file diff --git a/src/sequelize/models/trackingevent.js b/src/sequelize/models/trackingevent.js new file mode 100644 index 000000000..0973e8e42 --- /dev/null +++ b/src/sequelize/models/trackingevent.js @@ -0,0 +1,40 @@ +'use strict'; +module.exports = (sequelize, DataTypes) => { + const TrackingEvent = sequelize.define('TrackingEvent', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'id' + }, + uuid: { + type: DataTypes.TEXT, + allowNull: false, + field: 'uuid' + }, + sourceType: { + type: DataTypes.TEXT, + field: 'source_type' + }, + timestamp: { + type: DataTypes.BIGINT, + field: 'timestamp' + }, + type: { + type: DataTypes.TEXT, + field: 'type' + }, + data: { + type: DataTypes.TEXT, + field: 'data' + } + }, { + timestamps: false, + underscored: true + }); + TrackingEvent.associate = function(models) { + // associations can be defined here + }; + return TrackingEvent; +}; \ No newline at end of file diff --git a/src/sequelize/seeders/20190130112616-insert-logging-catalog-item.js b/src/sequelize/seeders/20190130112616-insert-logging-catalog-item.js new file mode 100644 index 000000000..4a0c405ef --- /dev/null +++ b/src/sequelize/seeders/20190130112616-insert-logging-catalog-item.js @@ -0,0 +1,45 @@ +'use strict'; + +module.exports = { + up: (queryInterface, Sequelize) => { + return queryInterface.bulkInsert('CatalogItems', [ + { + ID: 100, + name: 'Common Logging', + description: 'Container which gathers logs and provides REST API' + + ' for adding and querying logs from containers', + category: 'UTILITIES', + publisher: 'Eclipse ioFog', + disk_required: 0, + ram_required: 0, + picture: 'none.png', + config_example: '{"access_tokens": ["Some_Access_Token"], "cleanfrequency": "1h40m", "ttl": "24h"}', + is_public: 0, + registry_id: 1, + user_id: null + }] + ).then(() => { + return queryInterface.bulkInsert('CatalogItemImages', [ + { + ID: 101, + catalog_item_id: 100, + fog_type_id: 1, + container_image: 'iofog/common-logging' + }, + { + ID: 102, + catalog_item_id: 100, + fog_type_id: 2, + container_image: 'iofog/common-logging-arm' + } + ] + ) + }); + }, + + down: (queryInterface, Sequelize) => { + return queryInterface.bulkDelete('CatalogItems', {ID: 100}, {}).then(() => { + return queryInterface.bulkDelete('CatalogItemImages', {catalog_item_id: 100}) + }); + } +}; \ No newline at end of file diff --git a/src/server.js b/src/server.js index 50b4ab214..c3a26aa53 100644 --- a/src/server.js +++ b/src/server.js @@ -30,6 +30,9 @@ const packageJson = require('../package'); const app = express(); const Sentry = require('@sentry/node'); +const Tracking = require('./tracking'); +const TrackingEventType = require('./enums/tracking-event-type'); + Sentry.init({ dsn: 'https://a15f11352d404c2aa4c8f321ad9e759a@sentry.io/1378602' }); Sentry.configureScope(scope => { scope.setExtra('version', packageJson.version); @@ -139,3 +142,6 @@ if (!devMode && sslKey && sslCert && intermedKey) { } else { startHttpServer(app, port, jobs) } + +const event = Tracking.buildEvent(TrackingEventType.START, `devMode is ${devMode}`); +Tracking.processEvent(event); diff --git a/src/services/agent-service.js b/src/services/agent-service.js index d81e6a676..801987aff 100644 --- a/src/services/agent-service.js +++ b/src/services/agent-service.js @@ -37,6 +37,9 @@ const fs = require('fs'); const formidable = require('formidable'); const Sequelize = require('sequelize'); const Op = Sequelize.Op; +const TrackingDecorator = require('../decorators/tracking-decorator'); +const TrackingEventType = require('../enums/tracking-event-type'); +const TrackingEventManager = require('../sequelize/managers/tracking-event-manager'); const IncomingForm = formidable.IncomingForm; @@ -491,8 +494,21 @@ async function _checkMicroservicesFogType(fog, fogTypeId, transaction) { } } +async function postTracking(events, fog, transaction) { + await Validator.validate(events, Validator.schemas.trackingArray); + for (const event of events) { + event.data = JSON.stringify(event.data) + } + + await TrackingEventManager.bulkCreate(events, transaction); +} + +//decorated functions +const agentProvisionWithTracking = TrackingDecorator.trackEvent(agentProvision, TrackingEventType.IOFOG_PROVISION); + + module.exports = { - agentProvision: TransactionDecorator.generateFakeTransaction(agentProvision), + agentProvision: TransactionDecorator.generateFakeTransaction(agentProvisionWithTracking), agentDeprovision: TransactionDecorator.generateFakeTransaction(agentDeprovision), getAgentConfig: getAgentConfig, updateAgentConfig: TransactionDecorator.generateFakeTransaction(updateAgentConfig), @@ -509,5 +525,6 @@ module.exports = { updateHalUsbInfo: TransactionDecorator.generateFakeTransaction(updateHalUsbInfo), deleteNode: TransactionDecorator.generateFakeTransaction(deleteNode), getImageSnapshot: TransactionDecorator.generateFakeTransaction(getImageSnapshot), - putImageSnapshot: TransactionDecorator.generateFakeTransaction(putImageSnapshot) + putImageSnapshot: TransactionDecorator.generateFakeTransaction(putImageSnapshot), + postTracking: TransactionDecorator.generateFakeTransaction(postTracking), }; \ No newline at end of file diff --git a/src/services/catalog-service.js b/src/services/catalog-service.js index 8f7c57dd2..69aaf578b 100644 --- a/src/services/catalog-service.js +++ b/src/services/catalog-service.js @@ -25,6 +25,8 @@ const RegistryManager = require('../sequelize/managers/registry-manager'); const MicroserviceManager = require('../sequelize/managers/microservice-manager'); const ChangeTrackingService = require('./change-tracking-service'); const MicroseriveStates = require('../enums/microservice-state'); +const TrackingDecorator = require('../decorators/tracking-decorator'); +const TrackingEventType = require('../enums/tracking-event-type'); const createCatalogItem = async function (data, user, transaction) { await Validator.validate(data, Validator.schemas.catalogItemCreate); @@ -280,7 +282,7 @@ const _updateCatalogItem = async function (data, where, transaction) { catalogItem = AppHelper.deleteUndefinedFields(catalogItem); if (!catalogItem || AppHelper.isEmpty(catalogItem)) { - throw new Errors.NotFoundError(ErrorMessages.CATALOG_UPDATE_NO_FIELDS); + return } if (data.registryId) { const registry = await RegistryManager.findOne({id: data.registryId}, transaction); @@ -312,7 +314,11 @@ const _updateCatalogItemImages = async function (data, transaction) { await CatalogItemImageManager.updateOrCreate({ catalogItemId: data.id, fogTypeId: image.fogTypeId - }, image, transaction); + }, { + catalogItemId: data.id, + fogTypeId: image.fogTypeId, + containerImage: image.containerImage + }, transaction); } } }; @@ -338,8 +344,11 @@ const _updateCatalogItemIOTypes = async function (data, where, transaction) { } }; +//decorated functions +const createCatalogItemWithTracking = TrackingDecorator.trackEvent(createCatalogItem, TrackingEventType.CATALOG_CREATED); + module.exports = { - createCatalogItem: TransactionDecorator.generateTransaction(createCatalogItem), + createCatalogItem: TransactionDecorator.generateTransaction(createCatalogItemWithTracking), listCatalogItems: TransactionDecorator.generateTransaction(listCatalogItems), getCatalogItem: TransactionDecorator.generateTransaction(getCatalogItem), deleteCatalogItem: TransactionDecorator.generateTransaction(deleteCatalogItem), diff --git a/src/services/connector-port-service.js b/src/services/connector-port-service.js index bf309344f..235adf59e 100644 --- a/src/services/connector-port-service.js +++ b/src/services/connector-port-service.js @@ -89,7 +89,6 @@ async function closePortOnConnector(connector, ports) { let data = qs.stringify({ mappingid: ports.mappingId }); - console.log(data); let port = connector.devMode ? constants.CONNECTOR_HTTP_PORT : constants.CONNECTOR_HTTPS_PORT; @@ -115,7 +114,6 @@ async function closePortOnConnector(connector, ports) { async function _makeRequest(connector, options, data) { return new Promise((resolve, reject) => { let httpreq = (connector.devMode ? http : https).request(options, function (response) { - console.log(response.statusCode); let output = ''; response.setEncoding('utf8'); @@ -134,7 +132,6 @@ async function _makeRequest(connector, options, data) { }); httpreq.on('error', function (err) { - console.log(err); if (err instanceof Error) return reject(new Error(err.message)); else diff --git a/src/services/controller-service.js b/src/services/controller-service.js index d42aea13d..984f7eda3 100644 --- a/src/services/controller-service.js +++ b/src/services/controller-service.js @@ -15,6 +15,7 @@ const ioFogTypesManager = require('../sequelize/managers/iofog-type-manager'); const Config = require('../config'); const TransactionDecorator = require('../decorators/transaction-decorator'); const packageJson = require('../../package'); +const AppHelper = require('../helpers/app-helper'); const getFogTypes = async function (isCLI, transaction) { const ioFogTypes = await ioFogTypesManager.findAll({}, transaction); @@ -43,13 +44,12 @@ const emailActivation = async function (isCLI) { }; const statusController = async function (isCLI) { - const daemon = require('../daemon'); + let status; - let pid = daemon.status(); - if (pid === 0) { - status = 'offline' - } else { + if (AppHelper.isOnline()) { status = 'online' + } else { + status = 'offline' } return { diff --git a/src/services/iofog-service.js b/src/services/iofog-service.js index 62233c1a7..58adf14a7 100644 --- a/src/services/iofog-service.js +++ b/src/services/iofog-service.js @@ -25,6 +25,8 @@ const USBInfoManager = require('../sequelize/managers/usb-info-manager'); const CatalogService = require('../services/catalog-service'); const MicroserviceManager = require('../sequelize/managers/microservice-manager'); const FogStates = require('../enums/fog-state'); +const TrackingDecorator = require('../decorators/tracking-decorator'); +const TrackingEventType = require('../enums/tracking-event-type'); async function createFog(fogData, user, isCLI, transaction) { await Validator.validate(fogData, Validator.schemas.iofogCreate); @@ -424,8 +426,11 @@ async function _deleteBluetoothMicroserviceByFog(fogData, transaction) { await MicroserviceManager.delete(deleteBluetoothMicroserviceData, transaction) } +//decorated functions +const createFogWithTracking = TrackingDecorator.trackEvent(createFog, TrackingEventType.IOFOG_CREATED); + module.exports = { - createFog: TransactionDecorator.generateTransaction(createFog), + createFog: TransactionDecorator.generateTransaction(createFogWithTracking), updateFog: TransactionDecorator.generateTransaction(updateFog), deleteFog: TransactionDecorator.generateTransaction(deleteFog), getFogWithTransaction: TransactionDecorator.generateTransaction(getFog), diff --git a/src/services/microservices-service.js b/src/services/microservices-service.js index 8790dc3d7..15f1e5bdf 100644 --- a/src/services/microservices-service.js +++ b/src/services/microservices-service.js @@ -34,6 +34,8 @@ const RoutingManager = require('../sequelize/managers/routing-manager'); const Op = require('sequelize').Op; const fs = require('fs'); const _ = require('underscore'); +const TrackingDecorator = require('../decorators/tracking-decorator'); +const TrackingEventType = require('../enums/tracking-event-type'); async function listMicroservices(flowId, user, isCLI, transaction) { if (!isCLI) { @@ -142,11 +144,15 @@ async function updateMicroservice(microserviceUuid, microserviceData, user, isCL const microserviceDataUpdate = AppHelper.deleteUndefinedFields(microserviceToUpdate); - const microservice = await MicroserviceManager.findOne(query, transaction); + const microservice = await MicroserviceManager.findOneWithCategory(query, transaction); if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) } + if (microservice.catalogItem.category === "SYSTEM") { + throw new Errors.ValidationError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_UPDATE, microserviceUuid)) + } + if (microserviceDataUpdate.name) { const userId = isCLI ? microservice.userId : user.id; await _checkForDuplicateName(microserviceDataUpdate.name, {id: microserviceUuid}, userId, transaction); @@ -195,12 +201,15 @@ async function deleteMicroservice(microserviceUuid, microserviceData, user, isCL userId: user.id }; - const microservice = await MicroserviceManager.findOneWithStatus(where, transaction); + const microservice = await MicroserviceManager.findOneWithStatusAndCategory(where, transaction); if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)); } + if (!isCLI && microservice.catalogItem.category === "SYSTEM") { + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.SYSTEM_MICROSERVICE_DELETE, microserviceUuid)) + } - if (microservice.microserviceStatus.status === MicroserviceStates.NOT_RUNNING) { + if (!microservice.microserviceStatus || microservice.microserviceStatus.status === MicroserviceStates.NOT_RUNNING) { await deleteMicroserviceWithRoutesAndPortMappings(microserviceUuid, transaction); } else { await MicroserviceManager.update({ @@ -230,7 +239,7 @@ async function createRoute(sourceMicroserviceUuid, destMicroserviceUuid, user, i const sourceMicroservice = await MicroserviceManager.findOne(sourceWhere, transaction); if (!sourceMicroservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, sourceMicroserviceUuid)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_SOURCE_MICROSERVICE_UUID, sourceMicroserviceUuid)) } const destWhere = isCLI @@ -239,7 +248,7 @@ async function createRoute(sourceMicroserviceUuid, destMicroserviceUuid, user, i const destMicroservice = await MicroserviceManager.findOne(destWhere, transaction); if (!destMicroservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, destMicroserviceUuid)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_DEST_MICROSERVICE_UUID, destMicroserviceUuid)) } if (!sourceMicroservice.iofogUuid || !destMicroservice.iofogUuid) { @@ -279,7 +288,7 @@ async function deleteRoute(sourceMicroserviceUuid, destMicroserviceUuid, user, i const sourceMicroservice = await MicroserviceManager.findOne(sourceWhere, transaction); if (!sourceMicroservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, sourceMicroserviceUuid)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_SOURCE_MICROSERVICE_UUID, sourceMicroserviceUuid)) } const destWhere = isCLI @@ -288,7 +297,7 @@ async function deleteRoute(sourceMicroserviceUuid, destMicroserviceUuid, user, i const destMicroservice = await MicroserviceManager.findOne(destWhere, transaction); if (!destMicroservice) { - throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, destMicroserviceUuid)) + throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_DEST_MICROSERVICE_UUID, destMicroserviceUuid)) } const route = await RoutingManager.findOne({ @@ -357,6 +366,9 @@ async function deletePortMapping(microserviceUuid, internalPort, user, isCLI, tr ? {uuid: microserviceUuid} : {uuid: microserviceUuid, userId: user.id}; + if (!internalPort) { + throw new Errors.ValidationError(ErrorMessages.PORT_MAPPING_INTERNAL_PORT_NOT_PROVIDED); + } const microservice = await MicroserviceManager.findOne(where, transaction); if (!microservice) { throw new Errors.NotFoundError(AppHelper.formatMessage(ErrorMessages.INVALID_MICROSERVICE_UUID, microserviceUuid)) @@ -1057,8 +1069,11 @@ async function _buildLink(protocol, ip, port) { return `${protocol}://${ip}:${port}` } +//decorated functions +const createMicroserviceWithTracking = TrackingDecorator.trackEvent(createMicroservice, TrackingEventType.MICROSERVICE_CREATED); + module.exports = { - createMicroservice: TransactionDecorator.generateTransaction(createMicroservice), + createMicroservice: TransactionDecorator.generateTransaction(createMicroserviceWithTracking), listMicroservices: TransactionDecorator.generateTransaction(listMicroservices), getMicroservice: TransactionDecorator.generateTransaction(getMicroservice), updateMicroservice: TransactionDecorator.generateTransaction(updateMicroservice), diff --git a/src/services/user-service.js b/src/services/user-service.js index d67f7c643..cc895c026 100644 --- a/src/services/user-service.js +++ b/src/services/user-service.js @@ -25,6 +25,8 @@ const emailRecoveryTemplate = require('../views/email-temp'); const emailResetTemplate = require('../views/reset-password-temp'); const EmailActivationCodeService = require('./email-activation-code-service'); const AccessTokenService = require('./access-token-service'); +const TrackingDecorator = require('../decorators/tracking-decorator'); +const TrackingEventType = require('../enums/tracking-event-type'); const TransactionDecorator = require('../decorators/transaction-decorator'); const Validator = require('../schemas'); @@ -374,8 +376,11 @@ async function _getEmailData() { } +//decorated functions +const signUpWithTracking = TrackingDecorator.trackEvent(signUp, TrackingEventType.USER_CREATED); + module.exports = { - signUp: TransactionDecorator.generateTransaction(signUp), + signUp: TransactionDecorator.generateTransaction(signUpWithTracking), login: TransactionDecorator.generateTransaction(login), resendActivation: TransactionDecorator.generateTransaction(resendActivation), activateUser: TransactionDecorator.generateTransaction(activateUser), diff --git a/src/tracking/index.js b/src/tracking/index.js new file mode 100644 index 000000000..dbc91bc77 --- /dev/null +++ b/src/tracking/index.js @@ -0,0 +1,143 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +const {isOnline} = require('../helpers/app-helper'); +const https = require('https'); +const EventTypes = require('../enums/tracking-event-type'); +const os = require('os'); +const AppHelper = require('../helpers/app-helper'); +const crypto = require('crypto'); + +const TrackingEventManager = require('../sequelize/managers/tracking-event-manager'); +const Transaction = require('sequelize/lib/transaction'); + +const fakeTransactionObject = {fakeTransaction: true}; + +const trackingUuid = getUniqueTrackingUuid(); + +/** + * generate tracking event after service function was executed + * + * @param eventType - @see src/enum/tracking-event-type.js + * @param res - response of service function + * @param args - arguments of service function + * @param functionName - name of service function + * @returns {{sourceType: string, type: string, uuid: string, timestamp: number}} + */ +function buildEvent(eventType, res, args, functionName) { + let eventInfo = { + uuid: trackingUuid, + sourceType: 'controller', + timestamp: Date.now(), + type: eventType + }; + switch (eventType) { + case EventTypes.INIT: + eventInfo.data = {event: 'controller inited'}; + break; + case EventTypes.START: + eventInfo.data = {event: `controller started: ${res}`}; + break; + case EventTypes.USER_CREATED: + eventInfo.data = {event: 'user created'}; + break; + case EventTypes.RUNNING_TIME: + eventInfo.data = {event: `${res} min`}; + break; + case EventTypes.IOFOG_CREATED: + eventInfo.data = {event: 'iofog agent created'}; + break; + case EventTypes.IOFOG_PROVISION: + eventInfo.data = {event: 'iofog agent provisioned'}; + break; + case EventTypes.CATALOG_CREATED: + eventInfo.data = {event: 'catalog item was created'}; + break; + case EventTypes.MICROSERVICE_CREATED: + eventInfo.data = {event: 'microservice created'}; + break; + case EventTypes.CONFIG_CHANGED: + eventInfo.data = {event: `new config property '${res}'`}; + break; + case EventTypes.OTHER: + eventInfo.data = {event: `function ${functionName} was executed`}; + break; + } + return eventInfo; +} + +function sendEvents(events) { + for (const event of events) { + event.data = JSON.parse(event.data) + } + const body = { + events: events + } + const data = JSON.stringify(body); + let options = { + host: 'analytics.iofog.org', + path: '/post', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(data) + } + }; + + const request = https.request(options); + request.write(data); + request.end(); +} + +function getUniqueTrackingUuid() { + let uuid; + try { + let allMacs = ''; + const interfaces = os.networkInterfaces(); + for (const i in interfaces) { + if (!i.internal) { + allMacs += i.mac + '-' + } + } + uuid = crypto.createHash('md5').update(allMacs).digest("hex"); + } catch (e) { + uuid = 'random_' + AppHelper.generateRandomString(32) + } + return uuid; +} + +async function processEvent(event, fArgs) { + event.data = JSON.stringify(event.data); + if (isOnline()) { + //save in db, and send later by job + if (fArgs && fArgs.length > 0 && fArgs[fArgs.length - 1] instanceof Transaction) { + await TrackingEventManager.create(event, fArgs[fArgs.length - 1]); + } else { + await TrackingEventManager.create(event, fakeTransactionObject); + } + } else { + //just send + try { + sendEvents([event]); + } catch (e) { + //TODO log only in file. add after logging will fixed + } + } +} + +module.exports = { + trackingUuid: trackingUuid, + buildEvent: buildEvent, + sendEvents: sendEvents, + processEvent: processEvent +}; \ No newline at end of file diff --git a/test/src/controllers/agent-controller.test.js b/test/src/controllers/agent-controller.test.js index 5d0e400c8..55d7b2ec8 100644 --- a/test/src/controllers/agent-controller.test.js +++ b/test/src/controllers/agent-controller.test.js @@ -801,4 +801,40 @@ describe('Agent Controller', () => { }) }); + + describe('postTrackingEndPoint()', () => { + def('fog', () => 'fog!'); + + def('req', () => ({ + body: {events: []} + })); + def('response', () => Promise.resolve()); + def('subject', () => $subject.postTrackingEndPoint($req, $fog)); + + beforeEach(() => { + $sandbox.stub(AgentService, 'postTracking').returns($response); + }); + + it('calls AgentService.postTrackingEndPoint with correct args', async () => { + await $subject; + expect(AgentService.postTracking).to.have.been.calledWith($req.body.events, $fog); + }); + + context('when AgentService#postTrackingEndPoint fails', () => { + const error = 'Error!'; + + def('response', () => Promise.reject(error)); + + it(`fails with "${error}"`, () => { + return expect($subject).to.be.rejectedWith(error) + }) + }); + + context('when AgentService#postTrackingEndPoint succeeds', () => { + it(`succeeds`, () => { + return expect($subject).to.eventually.equal(undefined) + }) + }) + }); + }); \ No newline at end of file diff --git a/test/src/services/agent-service.test.js b/test/src/services/agent-service.test.js index e0fbb32eb..0cfbf5c74 100644 --- a/test/src/services/agent-service.test.js +++ b/test/src/services/agent-service.test.js @@ -25,6 +25,7 @@ const formidable = ('./incoming_form'); const IncomingForm = formidable.IncomingForm; const MicroserviceStates = require('../../../src/enums/microservice-state'); const FogStates = require('../../../src/enums/fog-state'); +const TrackingEventManager = require('../../../src/sequelize/managers/tracking-event-manager'); global.appRoot = path.resolve(__dirname); @@ -1447,4 +1448,55 @@ describe('Agent Service', () => { // TODO // describe('.putImageSnapshot()', () => { + describe('postTrackingEndPoint()', () => { + const error = 'Error!'; + + def('fog', () => 'fog!'); + def('events', () => []); + def('transaction', () => {}); + + def('subject', () => $subject.postTracking($events, $fog, $transaction)); + + def('validatorResponse', () => Promise.resolve(true)); + def('bulkCreateResponse', () => Promise.resolve()); + + beforeEach(() => { + $sandbox.stub(Validator, 'validate').returns($validatorResponse); + $sandbox.stub(TrackingEventManager, 'bulkCreate').returns($bulkCreateResponse); + }); + + it('calls Validator#validate() with correct args', async () => { + await $subject; + expect(Validator.validate).to.have.been.calledWith($events, Validator.schemas.trackingArray); + }); + + context('when Validator#validate() fails', () => { + def('validatorResponse', () => Promise.reject(error)); + + it(`fails with ${error}`, () => { + return expect($subject).to.be.rejectedWith(error); + }) + }); + + context('when Validator#validate() succeeds', () => { + it('calls TrackingEventManager#bulkCreate() with correct args', async () => { + await $subject; + expect(TrackingEventManager.bulkCreate).to.have.been.calledWith($events, $transaction); + + context('when TrackingEventManager#bulkCreate() fails', () => { + def('bulkCreateResponse', () => Promise.reject(error)); + + it(`fails with ${error}`, () => { + return expect($subject).to.be.equal(undefined); + }) + }); + + context('when TrackingEventManager#bulkCreate() succeeds', () => { + it(`succeeds`, () => { + return expect($subject).to.equal(undefined); + }) + }); + }); + }); + }); }); \ No newline at end of file diff --git a/test/src/services/catalog-service.test.js b/test/src/services/catalog-service.test.js index e612e87c7..ed505e6c4 100644 --- a/test/src/services/catalog-service.test.js +++ b/test/src/services/catalog-service.test.js @@ -491,11 +491,10 @@ describe('Catalog Service', () => { }); context('when AppHelper#isEmpty() fails', () => { - const err = new Errors.NotFoundError(ErrorMessages.CATALOG_UPDATE_NO_FIELDS); - def('isEmptyResponse', () => err); + def('isEmptyResponse', () => error); - it(`fails with ${err}`, () => { - return expect($subject).to.be.rejectedWith(ErrorMessages.CATALOG_UPDATE_NO_FIELDS); + it(`fails with ${error}`, () => { + return expect($subject).to.eventually.equal(undefined) }) }); @@ -561,7 +560,11 @@ describe('Catalog Service', () => { expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ catalogItemId: data.id, fogTypeId: image1.fogTypeId - }, updatedImage1, transaction); + }, { + catalogItemId: data.id, + fogTypeId: image1.fogTypeId, + containerImage: updatedImage1.containerImage + }, transaction); }); context('when CatalogItemImageManager#updateOrCreate() fails', () => { @@ -578,7 +581,11 @@ describe('Catalog Service', () => { expect(CatalogItemImageManager.updateOrCreate).to.have.been.calledWith({ catalogItemId: id, fogTypeId: image2.fogTypeId - }, updatedImage2, transaction); + }, { + catalogItemId: id, + fogTypeId: image2.fogTypeId, + containerImage: updatedImage2.containerImage + }, transaction); }); context('when CatalogItemImageManager#updateOrCreate() fails', () => { diff --git a/test/src/services/microservices-service.test.js b/test/src/services/microservices-service.test.js index 97a218f84..0cc587160 100644 --- a/test/src/services/microservices-service.test.js +++ b/test/src/services/microservices-service.test.js @@ -1039,7 +1039,7 @@ describe('Microservices Service', () => { // def('updateChangeTrackingResponse', () => Promise.resolve()); // // beforeEach(() => { -// $sandbox.stub(MicroserviceManager, 'findOneWithStatus').returns($findMicroserviceResponse); +// $sandbox.stub(MicroserviceManager, 'findOneWithStatusAndCategory').returns($findMicroserviceResponse); // $sandbox.stub(MicroservicePortManager, 'findAll').returns($findMicroservicePortResponse); // $sandbox.stub(MicroservicePortManager, 'delete').returns($deleteMicroservicePortResponse); // $sandbox.stub(MicroserviceManager, 'update').returns($updateMicroserviceResponse);