Skip to content

Commit dc2e9b4

Browse files
committed
Squashed commit of the following:
commit 0ede4ee Author: Jeromy Cannon <[email protected]> Date: Wed Mar 20 21:54:30 2024 +0000 timeout fetch plus log messages Signed-off-by: Jeromy Cannon <[email protected]> commit abdabc2 Author: Jeromy Cannon <[email protected]> Date: Wed Mar 20 21:32:36 2024 +0000 fix check failure Signed-off-by: Jeromy Cannon <[email protected]> commit 6369895 Author: Jeromy Cannon <[email protected]> Date: Wed Mar 20 20:55:24 2024 +0000 working version Signed-off-by: Jeromy Cannon <[email protected]> Signed-off-by: Jeromy Cannon <[email protected]>
1 parent 54efab3 commit dc2e9b4

File tree

6 files changed

+124
-65
lines changed

6 files changed

+124
-65
lines changed

src/commands/node.mjs

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ export class NodeCommand extends BaseCommand {
4242
this.plaformInstaller = opts.platformInstaller
4343
this.keyManager = opts.keyManager
4444
this.accountManager = opts.accountManager
45+
this._portForwards = []
46+
}
47+
48+
/**
49+
* stops and closes the port forwards
50+
* @returns {Promise<void>}
51+
*/
52+
async close () {
53+
if (this._portForwards) {
54+
for (const srv of this._portForwards) {
55+
await this.k8.stopPortForward(srv)
56+
}
57+
}
58+
59+
this._portForwards = []
4560
}
4661

4762
async checkNetworkNodePod (namespace, nodeId) {
@@ -495,10 +510,11 @@ export class NodeCommand extends BaseCommand {
495510
title: 'Check node proxies are ACTIVE',
496511
task: async (ctx, parentTask) => {
497512
const subTasks = []
513+
let localPort = constants.LOCAL_NODE_PROXY_START_PORT
498514
for (const nodeId of ctx.config.nodeIds) {
499515
subTasks.push({
500516
title: `Check proxy for node: ${chalk.yellow(nodeId)}`,
501-
task: async () => await self.checkNetworkNodeProxyUp(ctx.config.namespace, nodeId)
517+
task: async () => await self.checkNetworkNodeProxyUp(ctx.config.namespace, nodeId, localPort++)
502518
})
503519
}
504520

@@ -523,45 +539,48 @@ export class NodeCommand extends BaseCommand {
523539
throw new FullstackTestingError(`Error starting node: ${e.message}`, e)
524540
} finally {
525541
await self.accountManager.close()
542+
await self.close()
526543
}
527544

528545
return true
529546
}
530547

531-
async checkNetworkNodeProxyUp (namespace, nodeId, maxAttempts = 10, delay = 5000) {
532-
const podArray = await this.k8.getPodsByLabel([`app=haproxy-${nodeId}`, 'fullstack.hedera.com/type=haproxy'])
548+
/**
549+
* Check if the network node proxy is up, requires close() to be called after
550+
* @param namespace the namespace
551+
* @param nodeId the node id
552+
* @param localPort the local port to forward to
553+
* @param maxAttempts the maximum number of attempts
554+
* @param delay the delay between attempts
555+
* @returns {Promise<boolean>} true if the proxy is up
556+
*/
557+
async checkNetworkNodeProxyUp (namespace, nodeId, localPort, maxAttempts = 10, delay = 5000) {
558+
const podArray = await this.k8.getPodsByLabel(namespace, [`app=haproxy-${nodeId}`, 'fullstack.hedera.com/type=haproxy'])
533559

534560
let attempts = 0
535561
if (podArray.length > 0) {
536562
const podName = podArray[0].metadata.name
563+
this._portForwards.push(await this.k8.portForward(podName, localPort, 5555, namespace))
564+
try {
565+
await this.k8.testConnection('localhost', localPort)
566+
} catch (e) {
567+
throw new FullstackTestingError(`failed to create port forward for '${nodeId}' proxy on port ${localPort}`, e)
568+
}
537569

538570
while (attempts < maxAttempts) {
539-
const logResponse = await this.k8.kubeClient.readNamespacedPodLog(
540-
podName,
541-
namespace,
542-
'haproxy',
543-
undefined,
544-
undefined,
545-
1024,
546-
undefined,
547-
undefined,
548-
undefined,
549-
4
550-
)
551-
552-
if (logResponse.response.statusCode !== 200) {
553-
throw new FullstackTestingError(`Expected pod ${podName} log query to execute successful, but instead got a status of ${logResponse.response.statusCode}`)
554-
}
571+
try {
572+
const status = await this.getNodeProxyStatus(`http://localhost:${localPort}/v2/services/haproxy/stats/native?type=backend`)
573+
if (status === 'UP') {
574+
this.logger.debug(`Proxy ${podName} is UP. [attempt: ${attempts}/${maxAttempts}]`)
575+
return true
576+
}
555577

556-
this.logger.debug(`Received HAProxy log from ${podName}`, { output: logResponse.body })
557-
if (logResponse.body.includes('Server be_servers/server1 is UP')) {
558-
this.logger.debug(`Proxy ${podName} is UP [attempt: ${attempts}/${maxAttempts}]`)
559-
return true
578+
attempts++
579+
this.logger.debug(`Proxy ${podName} is not UP. Checking again in ${delay}ms ... [attempt: ${attempts}/${maxAttempts}]`)
580+
await sleep(delay)
581+
} catch (e) {
582+
throw new FullstackTestingError(`failed to create port forward for '${nodeId}' proxy on port ${localPort}`, e)
560583
}
561-
562-
attempts++
563-
this.logger.debug(`Proxy ${podName} is not UP. Checking again in ${delay}ms ... [attempt: ${attempts}/${maxAttempts}]`)
564-
await sleep(delay)
565584
}
566585
}
567586

@@ -862,4 +881,32 @@ export class NodeCommand extends BaseCommand {
862881
}
863882
}
864883
}
884+
885+
async getNodeProxyStatus (url) {
886+
try {
887+
this.logger.debug(`Fetching proxy status from: ${url}`)
888+
const res = await fetch(url, {
889+
method: 'GET',
890+
signal: AbortSignal.timeout(5000),
891+
headers: {
892+
Authorization: `Basic ${Buffer.from(
893+
`${constants.NODE_PROXY_USER_ID}:${constants.NODE_PROXY_PASSWORD}`).toString(
894+
'base64')}`
895+
}
896+
})
897+
const response = await res.json()
898+
899+
if (res.status === 200) {
900+
const status = response[0]?.stats?.filter(
901+
(stat) => stat.name === 'http_backend')[0]?.stats?.status
902+
this.logger.debug(`Proxy status: ${status}`)
903+
return status
904+
} else {
905+
this.logger.debug(`Proxy request status code: ${res.status}`)
906+
return null
907+
}
908+
} catch (e) {
909+
this.logger.error(`Error in fetching proxy status: ${e.message}`, e)
910+
}
911+
}
865912
}

src/core/account_manager.mjs

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
TransferTransaction
3232
} from '@hashgraph/sdk'
3333
import { FullstackTestingError, MissingArgumentError } from './errors.mjs'
34-
import net from 'net'
3534
import { Templates } from './templates.mjs'
3635

3736
const REASON_FAILED_TO_GET_KEYS = 'failed to get keys for accountId'
@@ -58,8 +57,6 @@ const REJECTED = 'rejected'
5857
*
5958
*/
6059
export class AccountManager {
61-
static _openPortForwardConnections = 0
62-
6360
/**
6461
* creates a new AccountManager instance
6562
* @param logger the logger to use
@@ -202,12 +199,10 @@ export class AccountManager {
202199

203200
if (usePortForward) {
204201
this._portForwards.push(await this.k8.portForward(serviceObject.podName, localPort, port))
205-
AccountManager._openPortForwardConnections++
206202
}
207203

208204
nodes[`${host}:${targetPort}`] = AccountId.fromString(serviceObject.accountId)
209-
await this.testConnection(serviceObject.podName, host, targetPort)
210-
205+
await this.k8.testConnection(host, targetPort)
211206
localPort++
212207
}
213208

@@ -464,31 +459,6 @@ export class AccountManager {
464459
return receipt.status === Status.Success
465460
}
466461

467-
/**
468-
* to test the connection to the node within the network
469-
* @param podName the podName is only used for logging messages and errors
470-
* @param host the host of the target connection
471-
* @param port the port of the target connection
472-
* @returns {Promise<boolean>}
473-
*/
474-
async testConnection (podName, host, port) {
475-
const self = this
476-
477-
return new Promise((resolve, reject) => {
478-
const s = new net.Socket()
479-
s.on('error', (e) => {
480-
s.destroy()
481-
reject(new FullstackTestingError(`failed to connect to '${host}:${port}': ${e.message}`, e))
482-
})
483-
484-
s.connect(port, host, () => {
485-
self.logger.debug(`Connection test successful: ${host}:${port}`)
486-
s.destroy()
487-
resolve(true)
488-
})
489-
})
490-
}
491-
492462
/**
493463
* creates a new Hedera account
494464
* @param namespace the namespace to store the Kubernetes key secret into

src/core/constants.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ export const GENESIS_KEY = process.env.GENESIS_KEY || '302e020100300506032b65700
8282
export const SYSTEM_ACCOUNTS = [[3, 100], [200, 349], [400, 750], [900, 1000]] // do account 0.0.2 last and outside the loop
8383
export const TREASURY_ACCOUNT = 2
8484
export const LOCAL_NODE_START_PORT = process.env.LOCAL_NODE_START_PORT || 30212
85+
export const LOCAL_NODE_PROXY_START_PORT = process.env.LOCAL_NODE_PROXY_START_PORT || 30313
8586
export const ACCOUNT_CREATE_BATCH_SIZE = process.env.ACCOUNT_CREATE_BATCH_SIZE || 50
87+
export const NODE_PROXY_USER_ID = process.env.NODE_PROXY_USER_ID || 'admin'
88+
export const NODE_PROXY_PASSWORD = process.env.NODE_PROXY_PASSWORD || 'adminpwd'
8689

8790
export const POD_STATUS_RUNNING = 'Running'
8891
export const POD_STATUS_READY = 'Ready'

src/core/k8.mjs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,14 @@ export class K8 {
197197

198198
/**
199199
* Get pods by labels
200+
* @param namespace the namespace of the pods
200201
* @param labels list of labels
201202
* @return {Promise<Array<V1Pod>>}
202203
*/
203-
async getPodsByLabel (labels = []) {
204-
const ns = this._getNamespace()
204+
async getPodsByLabel (namespace, labels = []) {
205205
const labelSelector = labels.join(',')
206206
const result = await this.kubeClient.listNamespacedPod(
207-
ns,
207+
namespace,
208208
undefined,
209209
undefined,
210210
undefined,
@@ -661,19 +661,45 @@ export class K8 {
661661
* @param podName pod name
662662
* @param localPort local port
663663
* @param podPort port of the pod
664+
* @param namespace namespace of the pod (optional)
664665
*/
665-
async portForward (podName, localPort, podPort) {
666-
const ns = this._getNamespace()
666+
async portForward (podName, localPort, podPort, namespace = null) {
667+
const ns = namespace || this._getNamespace()
667668
const forwarder = new k8s.PortForward(this.kubeConfig, false)
668669
const server = await net.createServer((socket) => {
669670
forwarder.portForward(ns, podName, [podPort], socket, null, socket, 3)
670671
})
671672

672673
// add info for logging
673674
server.info = `${podName}:${podPort} -> ${constants.LOCAL_HOST}:${localPort}`
675+
this.logger.debug(`Starting port-forwarder [${server.info}]`)
674676
return server.listen(localPort, constants.LOCAL_HOST)
675677
}
676678

679+
/**
680+
* to test the connection to a pod within the network
681+
* @param host the host of the target connection
682+
* @param port the port of the target connection
683+
* @returns {Promise<boolean>}
684+
*/
685+
async testConnection (host, port) {
686+
const self = this
687+
688+
return new Promise((resolve, reject) => {
689+
const s = new net.Socket()
690+
s.on('error', (e) => {
691+
s.destroy()
692+
reject(new FullstackTestingError(`failed to connect to '${host}:${port}': ${e.message}`, e))
693+
})
694+
695+
s.connect(port, host, () => {
696+
self.logger.debug(`Connection test successful: ${host}:${port}`)
697+
s.destroy()
698+
resolve(true)
699+
})
700+
})
701+
}
702+
677703
/**
678704
* Stop the port forwarder server
679705
*

test/e2e/commands/01_node.test.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,18 @@ describe.each([
9898
expect(e).toBeNull()
9999
}
100100
}, 20000)
101+
102+
it('Node Proxy should be UP', async () => {
103+
expect.assertions(1)
104+
105+
try {
106+
await expect(nodeCmd.checkNetworkNodeProxyUp(namespace, 'node0', 30313)).resolves.toBeTruthy()
107+
} catch (e) {
108+
nodeCmd.logger.showUserError(e)
109+
expect(e).toBeNull()
110+
} finally {
111+
await nodeCmd.close()
112+
}
113+
}, 20000)
101114
})
102115
})

test/e2e/core/account_manager.test.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ describe('AccountManager', () => {
4040

4141
// ports should be opened
4242
accountManager._portForwards.push(await k8.portForward(podName, localPort, podPort))
43-
const status = await accountManager.testConnection(podName, localHost, localPort)
43+
const status = await k8.testConnection(localHost, localPort)
4444
expect(status).toBeTruthy()
4545

4646
// ports should be closed
4747
await accountManager.close()
4848
try {
49-
await accountManager.testConnection(podName, localHost, localPort)
49+
await k8.testConnection(localHost, localPort)
5050
} catch (e) {
5151
expect(e.message.includes(`failed to connect to '${localHost}:${localPort}'`)).toBeTruthy()
5252
}

0 commit comments

Comments
 (0)