Skip to content

Commit d5a9a18

Browse files
feat: new command to save state files and upload state files (#849)
Signed-off-by: Jeffrey Tang <[email protected]> Signed-off-by: JeffreyDallas <[email protected]> Co-authored-by: Jeromy Cannon <[email protected]>
1 parent 0fb570b commit d5a9a18

File tree

12 files changed

+268
-24
lines changed

12 files changed

+268
-24
lines changed

README.md.template

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ An opinionated CLI tool to deploy and manage standalone test networks.
1818
* [Requirements](#requirements)
1919
* [Setup](#setup)
2020
* [Install Solo](#install-solo)
21-
* [Setup Kubernetes cluster](#setup-kubernetes-cluster)
22-
* [Generate Node Keys](#generate-node-keys)
23-
* [Standard keys (.pem file)](#standard-keys-pem-file)
24-
* [Examples](#examples)
25-
* [Example - 1: Deploy a standalone test network (version `0.54.0-alpha.4`)](#example---1-deploy-a-standalone-test-network-version-0540-alpha4)
21+
* [Use the Task tool to launch Solo](#use-the-task-tool-to-launch-solo)
22+
* [Advanced User Guide](#advanced-user-guide)
23+
* [Setup Kubernetes cluster](#setup-kubernetes-cluster)
24+
* [Step by Step Instructions](#step-by-step-instructions)
25+
* [For Hashgraph Developers](#for-hashgraph-developers)
26+
* [For Developers Working on Hedera Service Repo](#for-developers-working-on-hedera-service-repo)
27+
* [For Developers Working on Platform core](#for-developers-working-on-platform-core)
28+
* [Using IntelliJ remote debug with Solo](#using-intellij-remote-debug-with-solo)
29+
* [Retrieving Logs](#retrieving-logs)
30+
* [Save and reuse network state files](#save-and-reuse-network-state-files)
2631
* [Support](#support)
2732
* [Contributing](#contributing)
2833
* [Code of Conduct](#code-of-conduct)
@@ -53,17 +58,48 @@ nvm use lts/hydrogen
5358

5459
* Run `npm install -g @hashgraph/solo`
5560

56-
## Setup Kubernetes cluster
61+
## Use the Task tool to launch Solo
5762

58-
### Remote cluster
63+
First, install the cluster tool `kind` with this [link](https://kind.sigs.k8s.io/docs/user/quick-start#installation)
64+
65+
Then, install the task tool `task` with this [link](https://taskfile.dev/#/installation)
66+
67+
Then, use the following steps to install dependencies and build solo project.
68+
69+
```bash
70+
npm ci
71+
npm run build
72+
```
73+
Then, user can use one of the following three commands to quickly deploy a standalone solo network.
74+
75+
```bash
76+
# Option 1) deploy solo network with two nodes
77+
task default
78+
79+
# Option 2) deploy solo network with two nodes, and mirror node
80+
task default-with-mirror
81+
82+
# Option 3) deploy solo network with two nodes, mirror node, and JSON RPC relay
83+
task default-with-relay
84+
```
85+
To tear down the solo network
86+
```bash
87+
task clean
88+
```
89+
90+
## Advanced User Guide
91+
For those who would like to have more control or need some customized setups, here are some step by step instructions of how to setup and deploy a solo network.
92+
### Setup Kubernetes cluster
93+
94+
#### Remote cluster
5995

6096
* You may use remote kubernetes cluster. In this case, ensure kubernetes context is set up correctly.
6197

6298
```
6399
kubectl config use-context <context-name>
64100
```
65101

66-
### Local cluster
102+
#### Local cluster
67103

68104
* You may use [kind](https://kind.sigs.k8s.io/) or [microk8s](https://microk8s.io/) to create a cluster. In this case,
69105
ensure your Docker engine has enough resources (e.g. Memory >=8Gb, CPU: >=4). Below we show how you can use `kind` to create a cluster
@@ -116,9 +152,8 @@ You may now view pods in your cluster using `k9s -A` as below:
116152
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
117153
```
118154

119-
## Examples
120155

121-
### Example - 1: Deploy a standalone test network (version `0.54.0-alpha.4`)
156+
### Step by Step Instructions
122157

123158
* Initialize `solo` directories:
124159

@@ -318,8 +353,8 @@ Example output
318353
```
319354
$SOLO_RELAY_DEPLOY_OUTPUT
320355
```
321-
322-
## For Developers Working on Hedera Service Repo
356+
## For Hashgraph Developers
357+
### For Developers Working on Hedera Service Repo
323358

324359
First, please clone hedera service repo `https://github.com/hashgraph/hedera-services/` and build the code
325360
with `./gradlew assemble`. If need to running nodes with different versions or releases, please duplicate the repo or build directories in
@@ -335,20 +370,20 @@ solo node setup -i node1,node2,node3 -n "${SOLO_NAMESPACE}" --local-build-path <
335370
# example: solo node setup -i node1,node2,node3 -n "${SOLO_NAMESPACE}" --local-build-path node1=../hedera-services/hedera-node/data/,../hedera-services/hedera-node/data,node3=../hedera-services/hedera-node/data
336371
```
337372

338-
## For Developers Working on Platform core
373+
### For Developers Working on Platform core
339374

340375
To deploy node with local build PTT jar files, run the following command:
341376
```
342377
solo node setup -i node1,node2,node3 -n "${SOLO_NAMESPACE}" --local-build-path <default path to hedera repo>,node1=<custom build hedera repo>,node2=<custom build repo> --app PlatformTestingTool.jar --app-config <path-to-test-json1,path-to-test-json2>
343378

344379
# example: solo node setup -i node1,node2,node3 -n "${SOLO_NAMESPACE}" --local-build-path ../hedera-services/platform-sdk/sdk/data,node1=../hedera-services/platform-sdk/sdk/data,node2=../hedera-services/platform-sdk/sdk/data --app PlatformTestingTool.jar --app-config ../hedera-services/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/resources/FCMFCQ-Basic-2.5k-5m.json
345380
```
346-
## Logs
381+
### Retrieving Logs
347382
You can find log for running solo command under the directory `~/.solo/logs/`
348383
The file `solo.log` contains the logs for the solo command.
349384
The file `hashgraph-sdk.log` contains the logs from Solo client when sending transactions to network nodes.
350385

351-
## Using IntelliJ remote debug with Solo
386+
### Using IntelliJ remote debug with Solo
352387

353388
NOTE: the hedera-services path referenced '../hedera-services/hedera-node/data' may need to be updated based on what directory you are currently in. This also assumes that you have done an assemble/build and the directory contents are up-to-date.
354389

@@ -402,6 +437,31 @@ solo node setup -i node1,node2,node3,node4 --local-build-path ../hedera-services
402437
solo node start -i node1,node2,node3,node4 -n "${SOLO_NAMESPACE}"
403438
solo node delete --node-alias node2 --debug-node-alias node3 -n "${SOLO_NAMESPACE}"
404439
```
440+
### Save and reuse network state files
441+
442+
With the following command you can save the network state to a file.
443+
```bash
444+
# must stop hedera node operation first
445+
npm run solo-test -- node stop -i node1,node2 -n solo-e2e
446+
447+
# download state file to default location at ~/.solo/logs/<namespace>
448+
npm run solo-test -- node states -i node1,node2 -n solo-e2e
449+
```
450+
451+
By default the state files are saved under `~/solo` directory
452+
453+
```bash
454+
└── logs
455+
├── solo-e2e
456+
│   ├── network-node1-0-state.zip
457+
│   └── network-node2-0-state.zip
458+
└── solo.log
459+
```
460+
461+
Later, user can use the following command to upload the state files to the network and restart hedera nodes.
462+
```bash
463+
npm run solo-test -- node start -i node1,node2 -n solo-e2e --state-file network-node1-0-state.zip
464+
```
405465

406466
## Support
407467

resources/templates/settings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ crypto.enableNewKeyStoreModel, true
1212

1313
# TODO: remove this? only defaults to true when going from 0.52 to 0.53
1414
event.migrateEventHashing, false
15+
state.saveStatePeriod, 60

src/commands/flags.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ export const deployJsonRpcRelay: CommandFlag = {
182182
}
183183
}
184184

185+
export const stateFile: CommandFlag = {
186+
constName: 'stateFile',
187+
name: 'state-file',
188+
definition: {
189+
describe: 'A zipped state file to be used for the network',
190+
defaultValue: '',
191+
type: 'string'
192+
}
193+
}
194+
185195
export const releaseTag: CommandFlag = {
186196
constName: 'releaseTag',
187197
name: 'release-tag',
@@ -882,6 +892,7 @@ export const allFlags: CommandFlag[] = [
882892
relayReleaseTag,
883893
releaseTag,
884894
replicaCount,
895+
stateFile,
885896
setAlias,
886897
settingTxt,
887898
stakeAmounts,

src/commands/mirror_node.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,9 @@ export class MirrorNodeCommand extends BaseCommand {
338338

339339
try {
340340
await tasks.run()
341-
self.logger.debug('node start has completed')
341+
self.logger.debug('mirror node depolyment has completed')
342342
} catch (e: Error | any) {
343-
throw new SoloError(`Error starting node: ${e.message}`, e)
343+
throw new SoloError(`Error deploying node: ${e.message}`, e)
344344
} finally {
345345
await lease.release()
346346
await self.accountManager.close()
@@ -429,9 +429,9 @@ export class MirrorNodeCommand extends BaseCommand {
429429

430430
try {
431431
await tasks.run()
432-
self.logger.debug('node start has completed')
432+
self.logger.debug('mirror node destruction has completed')
433433
} catch (e: Error | any) {
434-
throw new SoloError(`Error starting node: ${e.message}`, e)
434+
throw new SoloError(`Error destrong mirror node: ${e.message}`, e)
435435
} finally {
436436
await lease.release()
437437
await self.accountManager.close()

src/commands/node/configs.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,17 @@ export const logsConfigBuilder = function (argv, ctx, task) {
246246
return config
247247
}
248248

249+
export const statesConfigBuilder = function (argv, ctx, task) {
250+
/** @type {{namespace: string, nodeAliases: NodeAliases, nodeAliasesUnparsed:string}} */
251+
const config = {
252+
namespace: this.configManager.getFlag(flags.namespace),
253+
nodeAliases: helpers.parseNodeAliases(this.configManager.getFlag(flags.nodeAliasesUnparsed)),
254+
nodeAliasesUnparsed: this.configManager.getFlag(flags.nodeAliasesUnparsed)
255+
}
256+
ctx.config = config
257+
return config
258+
}
259+
249260
export const refreshConfigBuilder = async function (argv, ctx, task) {
250261
ctx.config = this.getConfig(REFRESH_CONFIGS_NAME, argv.flags,
251262
[

src/commands/node/flags.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ export const LOGS_FLAGS = {
184184
optionalFlags: []
185185
}
186186

187+
export const STATES_FLAGS = {
188+
requiredFlags: [flags.namespace, flags.nodeAliasesUnparsed],
189+
requiredFlagsWithDisabledPrompt: [],
190+
optionalFlags: []
191+
}
192+
187193
export const REFRESH_FLAGS = {
188194
requiredFlags: [
189195
flags.cacheDir,
@@ -239,6 +245,7 @@ export const START_FLAGS = {
239245
flags.quiet,
240246
flags.nodeAliasesUnparsed,
241247
flags.debugNodeAlias,
248+
flags.stateFile,
242249
flags.stakeAmounts,
243250
]
244251
}

src/commands/node/handlers.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@
1818
import * as helpers from '../../core/helpers.js'
1919
import * as NodeFlags from './flags.js'
2020
import {
21-
addConfigBuilder, deleteConfigBuilder, downloadGeneratedFilesConfigBuilder, keysConfigBuilder, logsConfigBuilder,
22-
prepareUpgradeConfigBuilder, refreshConfigBuilder, setupConfigBuilder, startConfigBuilder, stopConfigBuilder,
21+
addConfigBuilder,
22+
deleteConfigBuilder,
23+
downloadGeneratedFilesConfigBuilder,
24+
keysConfigBuilder,
25+
logsConfigBuilder,
26+
prepareUpgradeConfigBuilder,
27+
refreshConfigBuilder,
28+
setupConfigBuilder,
29+
startConfigBuilder,
30+
statesConfigBuilder,
31+
stopConfigBuilder,
2332
updateConfigBuilder
2433
} from './configs.js'
2534
import {
@@ -500,6 +509,21 @@ export class NodeCommandHandlers {
500509
return true
501510
}
502511

512+
async states (argv: any) {
513+
argv = helpers.addFlagsToArgv(argv, NodeFlags.STATES_FLAGS)
514+
515+
const action = helpers.commandActionBuilder([
516+
this.tasks.initialize(argv, statesConfigBuilder.bind(this), null),
517+
this.tasks.getNodeStateFiles()
518+
], {
519+
concurrent: false,
520+
rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION
521+
}, 'Error in downloading states from nodes', null)
522+
523+
await action(argv, this)
524+
return true
525+
}
526+
503527
async refresh (argv: any) {
504528
argv = helpers.addFlagsToArgv(argv, NodeFlags.REFRESH_FLAGS)
505529

@@ -566,6 +590,9 @@ export class NodeCommandHandlers {
566590
const action = helpers.commandActionBuilder([
567591
this.tasks.initialize(argv, startConfigBuilder.bind(this), lease),
568592
this.tasks.identifyExistingNodes(),
593+
this.tasks.uploadStateFiles(
594+
(ctx: any) => ctx.config.stateFile.length === 0
595+
),
569596
this.tasks.startNodes('nodeAliases'),
570597
this.tasks.enablePortForwarding(),
571598
this.tasks.checkAllNodesAreActive('nodeAliases'),

src/commands/node/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ export class NodeCommand extends BaseCommand {
137137
handler: 'logs'
138138
}, NodeFlags.LOGS_FLAGS))
139139

140+
.command(new YargsCommand({
141+
command: 'states',
142+
description: 'Download hedera states from the network nodes and stores them in <SOLO_LOGS_DIR>/<namespace>/<podName>/ directory',
143+
commandDef: nodeCmd,
144+
handler: 'states'
145+
}, NodeFlags.STATES_FLAGS))
146+
140147
.command(new YargsCommand({
141148
command: 'add',
142149
description: 'Adds a node with a specific version of Hedera platform',

src/commands/node/tasks.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import crypto from 'crypto'
5555
import {
5656
addDebugOptions,
5757
getNodeAccountMap,
58-
getNodeLogs,
58+
getNodeLogs, getNodeStatesFromPod,
5959
prepareEndpoints,
6060
renameAndCopyFile,
6161
sleep,
@@ -691,6 +691,26 @@ export class NodeCommandTasks {
691691
})
692692
}
693693

694+
uploadStateFiles (skip: Function | boolean) {
695+
const self = this
696+
return new Task('Upload state files network nodes', async (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
697+
const config = ctx.config
698+
699+
const zipFile = config.stateFile
700+
self.logger.debug(`zip file: ${zipFile}`)
701+
for (const nodeAlias of ctx.config.nodeAliases) {
702+
const podName = ctx.config.podNames[nodeAlias]
703+
self.logger.debug(`Uploading state files to pod ${podName}`)
704+
await self.k8.copyTo(podName, constants.ROOT_CONTAINER, zipFile, `${constants.HEDERA_HAPI_PATH}/data`)
705+
706+
self.logger.info(`Deleting the previous state files in pod ${podName} directory ${constants.HEDERA_HAPI_PATH}/data/saved`)
707+
await self.k8.execContainer(podName, constants.ROOT_CONTAINER, ['rm', '-rf', `${constants.HEDERA_HAPI_PATH}/data/saved/*`])
708+
await self.k8.execContainer(podName, constants.ROOT_CONTAINER,
709+
['tar', '-xvf', `${constants.HEDERA_HAPI_PATH}/data/${path.basename(zipFile)}`, '-C', `${constants.HEDERA_HAPI_PATH}/data/saved`])
710+
}
711+
}, skip)
712+
}
713+
694714
identifyNetworkPods () {
695715
const self = this
696716
return new Task('Identify network pods', (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
@@ -965,6 +985,15 @@ export class NodeCommandTasks {
965985
})
966986
}
967987

988+
getNodeStateFiles () {
989+
const self = this
990+
return new Task('Get node states', async (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
991+
for (const nodeAlias of ctx.config.nodeAliases) {
992+
await getNodeStatesFromPod(self.k8, ctx.config.namespace, nodeAlias)
993+
}
994+
})
995+
}
996+
968997
checkPVCsEnabled () {
969998
return new Task('Check that PVCs are enabled', (ctx: any, task: ListrTaskWrapper<any, any, any>) => {
970999
if (!this.configManager.getFlag(flags.persistentVolumeClaims)) {

0 commit comments

Comments
 (0)