Skip to content

Commit d38918c

Browse files
feat: add node refresh subcommand (#211)
Signed-off-by: Jeromy Cannon <[email protected]>
1 parent 90616cc commit d38918c

25 files changed

+630
-180
lines changed

.github/workflows/flow-update-readme.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ jobs:
9393
export SOLO_NAMESPACE=solo
9494
export SOLO_CLUSTER_SETUP_NAMESPACE=solo-cluster
9595
96-
echo "Perform the followng kind and solo commands and save output to environment variables"
96+
echo "Perform the following kind and solo commands and save output to environment variables"
9797
9898
export KIND_CREATE_CLUSTER_OUTPUT=$( kind create cluster -n "${SOLO_CLUSTER_NAME}" 2>&1 | tee test.log )
9999

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,6 @@ Contributions are welcome. Please see the [contributing guide](https://github.co
579579
This project is governed by the [Contributor Covenant Code of Conduct](https://github.com/hashgraph/.github/blob/main/CODE_OF_CONDUCT.md). By participating, you are
580580
expected to uphold this code of conduct.
581581

582-
## License
582+
## License
583583

584584
[Apache License 2.0](LICENSE)

jest.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const config = {
2121
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(mjs?)$',
2222
moduleFileExtensions: ['js', 'mjs'],
2323
verbose: true,
24-
reporters: ['default', 'jest-junit'],
24+
reporters: [['default', { summaryThreshold: 1 }], 'jest-junit'],
2525
testSequencer: './test/testSequencer.mjs'
2626
}
2727

src/commands/node.mjs

Lines changed: 312 additions & 63 deletions
Large diffs are not rendered by default.

src/core/helpers.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,10 @@ export function isNumeric (str) {
170170
return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
171171
!isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
172172
}
173+
174+
export function validatePath (input) {
175+
if (input.indexOf('\0') !== -1) {
176+
throw new FullstackTestingError(`access denied for path: ${input}`)
177+
}
178+
return input
179+
}

src/core/k8.mjs

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -397,27 +397,34 @@ export class K8 {
397397
const parentDir = path.dirname(destPath)
398398
const fileName = path.basename(destPath)
399399
const filterMap = new Map(Object.entries(filters))
400-
const entries = await this.listDir(podName, containerName, parentDir)
401-
402-
for (const item of entries) {
403-
if (item.name === fileName && !item.directory) {
404-
let found = true
405-
406-
for (const entry of filterMap.entries()) {
407-
const field = entry[0]
408-
const value = entry[1]
409-
this.logger.debug(`Checking file ${podName}:${containerName} ${destPath}; ${field} expected ${value}, found ${item[field]}`, { filters })
410-
if (`${value}` !== `${item[field]}`) {
411-
found = false
412-
break
400+
401+
try {
402+
const entries = await this.listDir(podName, containerName, parentDir)
403+
404+
for (const item of entries) {
405+
if (item.name === fileName && !item.directory) {
406+
let found = true
407+
408+
for (const entry of filterMap.entries()) {
409+
const field = entry[0]
410+
const value = entry[1]
411+
this.logger.debug(`Checking file ${podName}:${containerName} ${destPath}; ${field} expected ${value}, found ${item[field]}`, { filters })
412+
if (`${value}` !== `${item[field]}`) {
413+
found = false
414+
break
415+
}
413416
}
414-
}
415417

416-
if (found) {
417-
this.logger.debug(`File check succeeded ${podName}:${containerName} ${destPath}`, { filters })
418-
return true
418+
if (found) {
419+
this.logger.debug(`File check succeeded ${podName}:${containerName} ${destPath}`, { filters })
420+
return true
421+
}
419422
}
420423
}
424+
} catch (e) {
425+
const error = new FullstackTestingError(`unable to check file in '${podName}':${containerName}' - ${destPath}: ${e.message}`, e)
426+
this.logger.error(error.message, error)
427+
throw error
421428
}
422429

423430
return false
@@ -674,6 +681,7 @@ export class K8 {
674681

675682
// add info for logging
676683
server.info = `${podName}:${podPort} -> ${constants.LOCAL_HOST}:${localPort}`
684+
server.localPort = localPort
677685
this.logger.debug(`Starting port-forwarder [${server.info}]`)
678686
return server.listen(localPort, constants.LOCAL_HOST)
679687
}
@@ -706,21 +714,74 @@ export class K8 {
706714
* Stop the port forwarder server
707715
*
708716
* @param server an instance of server returned by portForward method
717+
* @param maxAttempts the maximum number of attempts to check if the server is stopped
718+
* @param timeout the delay between checks in milliseconds
709719
* @return {Promise<void>}
710720
*/
711-
stopPortForward (server) {
721+
async stopPortForward (server, maxAttempts = 20, timeout = 500) {
722+
if (!server) {
723+
return
724+
}
725+
712726
this.logger.debug(`Stopping port-forwarder [${server.info}]`)
713-
return new Promise((resolve, reject) => {
727+
728+
// try to close the websocket server
729+
await new Promise((resolve, reject) => {
714730
server.close((e) => {
715731
if (e) {
716-
this.logger.debug(`Failed to stop port-forwarder [${server.info}]: ${e.message}`)
717-
reject(e)
732+
if (e.message.contains('Server is not running')) {
733+
this.logger.debug(`Server not running, port-forwarder [${server.info}]`)
734+
resolve()
735+
} else {
736+
this.logger.debug(`Failed to stop port-forwarder [${server.info}]: ${e.message}`)
737+
reject(e)
738+
}
718739
} else {
719740
this.logger.debug(`Stopped port-forwarder [${server.info}]`)
720741
resolve()
721742
}
722743
})
723744
})
745+
746+
// test to see if the port has been closed or if it is still open
747+
let attempts = 0
748+
while (attempts < maxAttempts) {
749+
let hasError = 0
750+
attempts++
751+
752+
try {
753+
const isPortOpen = await new Promise((resolve, reject) => {
754+
const testServer = net.createServer()
755+
.once('error', err => {
756+
if (err) {
757+
resolve(false)
758+
}
759+
})
760+
.once('listening', () => {
761+
testServer
762+
.once('close', () => {
763+
hasError++
764+
if (hasError > 1) {
765+
resolve(false)
766+
} else {
767+
resolve(true)
768+
}
769+
})
770+
.close()
771+
})
772+
.listen(server.localPort, '0.0.0.0')
773+
})
774+
if (isPortOpen) {
775+
return
776+
}
777+
} catch (e) {
778+
return
779+
}
780+
await sleep(timeout)
781+
}
782+
if (attempts >= maxAttempts) {
783+
throw new FullstackTestingError(`failed to stop port-forwarder [${server.info}]`)
784+
}
724785
}
725786

726787
async recyclePodByLabels (podLabels, maxAttempts = 50) {

src/core/platform_installer.mjs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,25 @@ import * as semver from 'semver'
2222
import { FullstackTestingError, IllegalArgumentError, MissingArgumentError } from './errors.mjs'
2323
import { constants } from './index.mjs'
2424
import { Templates } from './templates.mjs'
25+
import { flags } from '../commands/index.mjs'
2526

2627
/**
2728
* PlatformInstaller install platform code in the root-container of a network pod
2829
*/
2930
export class PlatformInstaller {
30-
constructor (logger, k8) {
31+
constructor (logger, k8, configManager) {
3132
if (!logger) throw new MissingArgumentError('an instance of core/Logger is required')
3233
if (!k8) throw new MissingArgumentError('an instance of core/K8 is required')
3334

3435
this.logger = logger
3536
this.k8 = k8
37+
this.configManager = configManager
38+
}
39+
40+
_getNamespace () {
41+
const ns = this.configManager.getFlag(flags.namespace)
42+
if (!ns) throw new MissingArgumentError('namespace is not set')
43+
return ns
3644
}
3745

3846
async validatePlatformReleaseDir (releaseDir) {
@@ -262,8 +270,6 @@ export class PlatformInstaller {
262270
* @returns {Promise<unknown>}
263271
*/
264272
async prepareConfigTxt (nodeIDs, destPath, releaseTag, chainId = constants.HEDERA_CHAIN_ID, template = `${constants.RESOURCES_DIR}/templates/config.template`) {
265-
const self = this
266-
267273
if (!nodeIDs || nodeIDs.length === 0) throw new MissingArgumentError('list of node IDs is required')
268274
if (!destPath) throw new MissingArgumentError('destPath is required')
269275
if (!template) throw new MissingArgumentError('config templatePath is required')
@@ -290,14 +296,11 @@ export class PlatformInstaller {
290296
let nodeSeq = 0
291297
let accountIdSeq = parseInt(startAccountId.num.toString(), 10)
292298
for (const nodeId of nodeIDs) {
293-
const podName = Templates.renderNetworkPodName(nodeId)
294-
const svcName = Templates.renderNetworkSvcName(nodeId)
295-
296299
const nodeName = nodeId
297300
const nodeNickName = nodeId
298301

299-
const internalIP = await self.k8.getPodIP(podName)
300-
const externalIP = await self.k8.getClusterIP(svcName)
302+
const internalIP = Templates.renderFullyQualifiedNetworkPodName(this._getNamespace(), nodeId)
303+
const externalIP = Templates.renderFullyQualifiedNetworkSvcName(this._getNamespace(), nodeId)
301304

302305
const account = `${accountIdPrefix}.${accountIdSeq}`
303306
if (releaseVersion.minor >= 40) {

src/core/templates.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,12 @@ export class Templates {
159159
throw new FullstackTestingError(`unknown dep: ${dep}`)
160160
}
161161
}
162+
163+
static renderFullyQualifiedNetworkPodName (namespace, nodeId) {
164+
return `${Templates.renderNetworkPodName(nodeId)}.${Templates.renderNetworkSvcName(nodeId)}.${namespace}.svc.cluster.local`
165+
}
166+
167+
static renderFullyQualifiedNetworkSvcName (namespace, nodeId) {
168+
return `${Templates.renderNetworkSvcName(nodeId)}.${namespace}.svc.cluster.local`
169+
}
162170
}

src/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function main (argv) {
5151
const chartManager = new ChartManager(helm, logger)
5252
const configManager = new ConfigManager(logger)
5353
const k8 = new K8(configManager, logger)
54-
const platformInstaller = new PlatformInstaller(logger, k8)
54+
const platformInstaller = new PlatformInstaller(logger, k8, configManager)
5555
const keyManager = new KeyManager(logger)
5656
const accountManager = new AccountManager(logger, k8)
5757
const profileManager = new ProfileManager(logger, configManager)

test/e2e/commands/account.test.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe('AccountCommand', () => {
5858
afterAll(async () => {
5959
await k8.deleteNamespace(namespace)
6060
await accountManager.close()
61+
await nodeCmd.close()
6162
})
6263

6364
describe('node proxies should be UP', () => {
@@ -73,7 +74,7 @@ describe('AccountCommand', () => {
7374
it('should succeed with init command', async () => {
7475
const status = await accountCmd.init(argv)
7576
expect(status).toBeTruthy()
76-
}, 120000)
77+
}, 180000)
7778

7879
describe('special accounts should have new keys', () => {
7980
const genesisKey = PrivateKey.fromStringED25519(constants.GENESIS_KEY)
@@ -121,7 +122,7 @@ describe('AccountCommand', () => {
121122
testLogger.showUserError(e)
122123
expect(e).toBeNull()
123124
}
124-
}, defaultTimeout)
125+
}, 40000)
125126

126127
it('should create account with private key and hbar amount options', async () => {
127128
try {

test/e2e/commands/network.test.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('NetworkCommand', () => {
8181
networkCmd.logger.showUserError(e)
8282
expect(e).toBeNull()
8383
}
84-
}, 60000)
84+
}, 120000)
8585

8686
it('network destroy should success', async () => {
8787
argv[flags.deletePvcs.name] = true

0 commit comments

Comments
 (0)