From 857d15d2be687f94014be5b1bced3ba5e7651b12 Mon Sep 17 00:00:00 2001 From: Ravin Perera <33562092+ravinsp@users.noreply.github.com> Date: Wed, 3 May 2023 15:25:18 +0530 Subject: [PATCH] Spawn and Status commands with refactoring. (#21) --- docker/scripts/cluster.sh | 64 +++++++++++++++++++-- npm/index.js | 52 ++++++++++------- npm/lib/{command-handler.js => commands.js} | 38 ++++++++---- npm/lib/{common.js => docker-helpers.js} | 37 +++++++----- npm/package-lock.json | 4 +- npm/package.json | 2 +- npm/scripts/install.js | 2 +- 7 files changed, 143 insertions(+), 56 deletions(-) rename npm/lib/{command-handler.js => commands.js} (79%) rename npm/lib/{common.js => docker-helpers.js} (73%) diff --git a/docker/scripts/cluster.sh b/docker/scripts/cluster.sh index eeab654..de638d1 100644 --- a/docker/scripts/cluster.sh +++ b/docker/scripts/cluster.sh @@ -14,12 +14,12 @@ user_port_begin=$HP_USER_PORT_BEGIN peer_port_begin=$HP_PEER_PORT_BEGIN if [ "$command" = "create" ] || [ "$command" = "bindmesh" ] || [ "$command" = "destroy" ] || \ - [ "$command" = "start" ] || [ "$command" = "stop" ] || \ - [ "$command" = "logs" ] || [ "$command" = "sync" ] ; then + [ "$command" = "start" ] || [ "$command" = "stop" ] || [ "$command" = "spawn" ] || \ + [ "$command" = "logs" ] || [ "$command" = "sync" ] || [ "$command" = "status" ] ; then echo "sub-command: $command" else echo "Invalid sub-command." - echo "Expected: create | destroy | start | stop | logs | sync" + echo "Expected: create | destroy | start | stop | spawn | logs | sync | status" exit 1 fi @@ -159,7 +159,7 @@ function bind_mesh { local contract_dir=$(contract_dir_mount_path $node) local cfg_file=$contract_dir/cfg/hp.cfg local peers=$(joinarr all_peers $instance_count $i) - jq ".contract.unl=$unl | .mesh.known_peers=$peers" $cfg_file > $cfg_file.tmp && mv $cfg_file.tmp $cfg_file + jq ".contract.unl=$unl | .mesh.known_peers=$peers | .contract.consensus.mode=\"public\"" $cfg_file > $cfg_file.tmp && mv $cfg_file.tmp $cfg_file done } @@ -202,6 +202,47 @@ function destroy_cluster { exists "network" $network && docker network rm $network } +function spawn_node { + ensure_cluser_exists + + local old_count=$(get_container_count) + local new_id=$((old_count+1)) + create_instance $new_id + + local tmp_dir=$(mktemp -d /tmp/hpdevkit.spawn.XXXXXX) + local node1_cfg=$(contract_dir_mount_path 1)/cfg/hp.cfg + local new_cfg=$(contract_dir_mount_path $new_id)/cfg/hp.cfg + + # Override new node's contract id and unl from the information from node1. + + # Copy node1 cfg to temp file and remove the information we don't need. + jq 'del(.contract,.node.public_key,.node.private_key)' $node1_cfg > $tmp_dir/a.json + jq '{contract:{id:.contract.id,execute:.contract.execute,unl:.contract.unl,log}}' $node1_cfg > $tmp_dir/b.json + jq -s '.[0] * .[1]' $tmp_dir/a.json $tmp_dir/b.json > $tmp_dir/from-node1.json + + # Copy new cfg to temp file and remove the information we don't need. + jq 'del(.contract.unl,.contract.id)' $new_cfg > $tmp_dir/from-newnode.json + + jq -s '.[0] * .[1]' $tmp_dir/from-node1.json $tmp_dir/from-newnode.json > $tmp_dir/merged.json + + # Inject the list of all known peers. + local all_peers + for ((i=1; i<=$old_count; i++)); + do + # Assign peer ports in incrementing order. + let peer_port=$(($peer_port_begin + $i - 1)) + all_peers[i]="node$i:${peer_port}" + done + local peers_json=$(joinarr all_peers $old_count -1) + jq ".mesh.known_peers=$peers_json" $tmp_dir/merged.json > $new_cfg + + # Cleanup temp dir. + rm -r $tmp_dir + + # Start the new node. + change_cluster_status start $new_id +} + function change_cluster_status { ensure_cluser_exists @@ -220,7 +261,9 @@ function change_cluster_status { function attach_logs { ! cluster_exists && echo "Cluster '$cluster' does not exist." && exit 1 - local container_name="${container_prefix}_$1" + local node=$1 + [ "$1" -eq "999999" ] && node=$(get_container_count) + local container_name="${container_prefix}_$node" docker logs -f --tail=5 $container_name } @@ -250,6 +293,9 @@ function sync_contract_bundle { sync_instance $i & done wait + + # Cleanup the original contract bundle dir which is used to sync from. + rm -r $bundle_mount/* } function validate_port_begin { @@ -257,6 +303,10 @@ function validate_port_begin { ! validate_port $peer_port_begin && echo "Invalid peer port begin." && exit 1 } +function show_status { + docker ps -a --format "table {{.Names}}\\t{{.State}}" | grep $container_prefix | sort +} + if [ $command = "create" ]; then ! validate_node_num_arg $cluster_size && echo "Invalid cluster size." && exit 1 validate_port_begin @@ -270,9 +320,13 @@ elif [ $command = "start" ]; then change_cluster_status start $2 elif [ $command = "stop" ]; then change_cluster_status stop $2 +elif [ $command = "spawn" ]; then + spawn_node elif [ $command = "logs" ]; then ! validate_node_num_arg $2 && echo "Usage: logs " && exit 1 attach_logs $2 elif [ $command = "sync" ]; then sync_contract_bundle +elif [ $command = "status" ]; then + show_status fi \ No newline at end of file diff --git a/npm/index.js b/npm/index.js index 22728a3..9ee80e7 100755 --- a/npm/index.js +++ b/npm/index.js @@ -1,52 +1,62 @@ #! /usr/bin/env node const { program } = require('commander'); -const { version, codeGen, deploy, clean, logs, start, stop, update, uninstall } = require('./lib/command-handler'); +const commands = require('./lib/commands'); program .command('version') - .description('hpdevkit version') - .action(version); + .description('Display the hpdevkit version.') + .action(commands.version); program .command('gen ') - .description('hpdevkit gen ') - .action(codeGen); + .description('Generate HotPocket application development projects.') + .action(commands.codeGen); program .command('deploy ') - .description('hpdevkit deploy ') - .action(deploy); + .description('Deploy the specified directory to a HotPocket cluster.') + .action(commands.deploy); program .command('clean') - .description('hpdevkit clean') - .action(clean); + .description('Destroy the HotPocket cluster.') + .action(commands.clean); program .command('logs ') - .description('hpdevkit logs ') - .action(logs); + .description('Display logs of the specified node.') + .action(commands.logs); program - .command('start ') - .description('hpdevkit start ') - .action(start); + .command('start [node-number]') + .description('Start the specified node. Starts all nodes if unspecified.') + .action(commands.start); program - .command('stop ') - .description('hpdevkit stop ') - .action(stop); + .command('stop [node-number]') + .description('Stop the specified node. Stops all nodes if unspecified.') + .action(commands.stop); + +program + .command('spawn') + .description('Create a fresh node which connects to the existing cluster.') + .action(commands.spawn); + +program + .command('status') + .description('Display status of running nodes.') + .action(commands.status); program .command('update') - .description('hpdevkit update') - .action(update); + .description('Update hpdevkit.') + .action(commands.update); program .command('uninstall') - .description('uninstall') - .action(uninstall); + .description('Uninstall hpdevkit.') + .action(commands.uninstall); try { program.parse(); diff --git a/npm/lib/command-handler.js b/npm/lib/commands.js similarity index 79% rename from npm/lib/command-handler.js rename to npm/lib/commands.js index ee20703..37cfa76 100644 --- a/npm/lib/command-handler.js +++ b/npm/lib/commands.js @@ -4,12 +4,12 @@ const { exec } = require('./child-proc'); const { CONSTANTS, initializeDeploymentCluster, - runOnContainer, - executeOnContainer, + runOnNewContainer, + executeOnManagementContainer, teardownDeploymentCluster, isExists, updateDockerImages -} = require('./common'); +} = require('./docker-helpers'); const { success, error, info, warn } = require('./logger'); function version() { @@ -37,7 +37,7 @@ function codeGen(platform, apptype, projName) { } try { - runOnContainer(CONSTANTS.codegenContainerName, null, null, null, null, `${platform} ${apptype} ${projName}`, 'codegen'); + runOnNewContainer(CONSTANTS.codegenContainerName, null, null, null, null, `${platform} ${apptype} ${projName}`, 'codegen'); exec(`docker cp ${CONSTANTS.codegenContainerName}:${CONSTANTS.codegenOutputDir} ./${projName}`); success(`Project '${projName}' created.`); } @@ -60,18 +60,24 @@ function deploy(contractPath) { `rm -rf ${CONSTANTS.bundleMount}` : `mkdir -p ${CONSTANTS.bundleMount} && rm -rf ${CONSTANTS.bundleMount}/* ${CONSTANTS.bundleMount}/.??*`; - executeOnContainer(CONSTANTS.deploymentContainerName, prepareBundleDir); - exec(`docker cp ${contractPath} "${CONSTANTS.deploymentContainerName}:${CONSTANTS.bundleMount}"`); + executeOnManagementContainer(prepareBundleDir); + exec(`docker cp ${contractPath} "${CONSTANTS.managementContainerName}:${CONSTANTS.bundleMount}"`); // Sync contract bundle to all instance directories in the cluster. - executeOnContainer(CONSTANTS.deploymentContainerName, 'cluster stop ; cluster sync ; cluster start'); + executeOnManagementContainer('cluster stop ; cluster sync ; cluster start'); if (appenv.defaultNode > 0) { info(`Streaming logs of node ${appenv.defaultNode}:`); - executeOnContainer(CONSTANTS.deploymentContainerName, `cluster logs ${appenv.defaultNode}`); + executeOnManagementContainer(`cluster logs ${appenv.defaultNode}`); } } +function spawn() { + info(`command: spawn (cluster: ${appenv.cluster})`); + + executeOnManagementContainer('cluster spawn && cluster logs 999999'); +} + function clean() { info(`command: clean (cluster: ${appenv.cluster})`); @@ -81,19 +87,25 @@ function clean() { function logs(nodeNumber) { info(`command: logs (cluster: ${appenv.cluster})`); - runOnContainer(null, null, true, true, null, `logs ${nodeNumber}`, 'cluster'); + executeOnManagementContainer(`cluster logs ${nodeNumber}`); } function start(nodeNumber) { info(`command: start (cluster: ${appenv.cluster})`); - runOnContainer(null, null, true, true, null, `start ${nodeNumber}`, 'cluster'); + executeOnManagementContainer(`cluster start ${nodeNumber}`); } function stop(nodeNumber) { info(`command: stop (cluster: ${appenv.cluster})`); - runOnContainer(null, null, true, true, null, `stop ${nodeNumber}`, 'cluster'); + executeOnManagementContainer(`cluster stop ${nodeNumber}`); +} + +function status() { + info(`command: status (cluster: ${appenv.cluster})`); + + executeOnManagementContainer(`cluster status`); } function update() { @@ -124,7 +136,7 @@ function uninstall() { exec(`npm -g uninstall ${CONSTANTS.npmPackageName}`, true); // Remove deployment cluster if exist. - if (isExists(CONSTANTS.deploymentContainerName)) { + if (isExists(CONSTANTS.managementContainerName)) { info('\nCleaning the deployed contracts...'); teardownDeploymentCluster(); } @@ -151,6 +163,8 @@ module.exports = { logs, start, stop, + spawn, + status, update, uninstall }; \ No newline at end of file diff --git a/npm/lib/common.js b/npm/lib/docker-helpers.js similarity index 73% rename from npm/lib/common.js rename to npm/lib/docker-helpers.js index 7e4bf6e..0a31710 100644 --- a/npm/lib/common.js +++ b/npm/lib/docker-helpers.js @@ -3,7 +3,6 @@ const { exec } = require("./child-proc"); const { log, info } = require("./logger"); const GLOBAL_PREFIX = "hpdevkit"; -const VERSION = "0.1.0"; const CONSTANTS = { npmPackageName: `hpdevkit`, @@ -12,7 +11,7 @@ const CONSTANTS = { network: `${GLOBAL_PREFIX}_${appenv.cluster}_net`, containerPrefix: `${GLOBAL_PREFIX}_${appenv.cluster}_node`, bundleMount: `${GLOBAL_PREFIX}_vol/contract_bundle`, - deploymentContainerName: `${GLOBAL_PREFIX}_${appenv.cluster}_deploymgr`, + managementContainerName: `${GLOBAL_PREFIX}_${appenv.cluster}_deploymgr`, confOverrideFile: "hp.cfg.override", codegenOutputDir: "/codegen-output", codegenContainerName: `${GLOBAL_PREFIX}_codegen`, @@ -20,7 +19,7 @@ const CONSTANTS = { prerequisiteInstaller: "install.sh" }; -function runOnContainer(name, detached, autoRemove, mountStock, mountVolume, entryCmd, entryPoint, interactive = true, restart = null) { +function runOnNewContainer(name, detached, autoRemove, mountSock, mountVolume, entryCmd, entryPoint, interactive = true, restart = null) { command = `docker run`; if (interactive) @@ -35,7 +34,7 @@ function runOnContainer(name, detached, autoRemove, mountStock, mountVolume, ent if (autoRemove) command += " --rm"; - if (mountStock) + if (mountSock) command += " --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock"; if (mountVolume) @@ -71,6 +70,15 @@ function executeOnContainer(name, cmd) { exec(`docker exec ${name} /bin/bash -c "${cmd}"`, true); } +function executeOnManagementContainer(cmd) { + if (!isExists(CONSTANTS.managementContainerName)) { + info(`cluster '${appenv.cluster}' not found.`) + return; + } + + executeOnContainer(CONSTANTS.managementContainerName, cmd) +} + function isExists(name, type = null) { try { const res = exec(`docker ${type === 'image' ? 'image ' : ''}inspect ${name}`); @@ -85,26 +93,26 @@ function isExists(name, type = null) { } function initializeDeploymentCluster() { - if (!isExists(CONSTANTS.deploymentContainerName)) { + if (!isExists(CONSTANTS.managementContainerName)) { log("\nInitializing deployment cluster"); // Stop cluster if running. Create cluster if not exists. - runOnContainer(CONSTANTS.deploymentContainerName, null, true, true, null, 'cluster stop ; cluster create', null); + runOnNewContainer(CONSTANTS.managementContainerName, null, true, true, null, 'cluster stop ; cluster create', null); // Spin up management container. - runOnContainer(CONSTANTS.deploymentContainerName, true, false, true, true, null, null, true, 'unless-stopped'); + runOnNewContainer(CONSTANTS.managementContainerName, true, false, true, true, null, null, true, 'unless-stopped'); // Bind the instance mesh network config together. - executeOnContainer(CONSTANTS.deploymentContainerName, 'cluster bindmesh'); + executeOnContainer(CONSTANTS.managementContainerName, 'cluster bindmesh'); } } function teardownDeploymentCluster() { - if (isExists(CONSTANTS.deploymentContainerName)) { - exec(`docker stop ${CONSTANTS.deploymentContainerName}`); - exec(`docker rm ${CONSTANTS.deploymentContainerName}`); + if (isExists(CONSTANTS.managementContainerName)) { + exec(`docker stop ${CONSTANTS.managementContainerName}`); + exec(`docker rm ${CONSTANTS.managementContainerName}`); } - runOnContainer(null, null, true, true, null, "cluster stop ; cluster destroy", null, false); + runOnNewContainer(null, null, true, true, null, "cluster stop ; cluster destroy", null, false); } function updateDockerImages() { @@ -112,15 +120,16 @@ function updateDockerImages() { exec(`docker pull ${appenv.instanceImage}`, true); // Clear if there's already deployed cluster since they are outdated now. - if (isExists(CONSTANTS.deploymentContainerName)) { + if (isExists(CONSTANTS.managementContainerName)) { info('\nCleaning the deployed contracts...'); teardownDeploymentCluster(); } } module.exports = { - runOnContainer, + runOnNewContainer, executeOnContainer, + executeOnManagementContainer, isExists, initializeDeploymentCluster, teardownDeploymentCluster, diff --git a/npm/package-lock.json b/npm/package-lock.json index 021dcd7..a91abc3 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -1,12 +1,12 @@ { "name": "hpdevkit", - "version": "0.5.6", + "version": "0.5.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "hpdevkit", - "version": "0.5.6", + "version": "0.5.7", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/npm/package.json b/npm/package.json index f6f714a..068692e 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "hpdevkit", - "version": "0.5.6", + "version": "0.5.7", "description": "Developer toolkit for HotPocket smart contract development", "scripts": { "install": "node scripts/install.js" diff --git a/npm/scripts/install.js b/npm/scripts/install.js index fadade2..c812e18 100644 --- a/npm/scripts/install.js +++ b/npm/scripts/install.js @@ -1,3 +1,3 @@ -const { updateDockerImages } = require("../lib/common"); +const { updateDockerImages } = require("../lib/docker-helpers"); updateDockerImages(); \ No newline at end of file