Skip to content

Commit 1fa1d78

Browse files
authored
fix: allow expired leases to be overwritten (#828)
Signed-off-by: Nathan Klick <[email protected]>
1 parent 356f341 commit 1fa1d78

File tree

8 files changed

+70
-35
lines changed

8 files changed

+70
-35
lines changed

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,6 @@ jobs:
8686
verbosity: 3
8787
wait: 120s
8888

89-
- name: Install NVM & Dependencies
90-
id: npm-deps
91-
run: |
92-
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
93-
export NVM_DIR="$HOME/.nvm"
94-
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
95-
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
96-
nvm install lts/hydrogen
97-
nvm use lts/hydrogen
98-
npm ci
99-
10089
- name: Install gettext-base
10190
id: gettext-base
10291
run: |

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/83a423a3a1c942459127b3aec62ab0b5)](https://app.codacy.com/gh/hashgraph/solo/dashboard?utm_source=gh\&utm_medium=referral\&utm_content=\&utm_campaign=Badge_grade)
88
[![codecov](https://codecov.io/gh/hashgraph/solo/graph/badge.svg?token=hBkQdB1XO5)](https://codecov.io/gh/hashgraph/solo)
99

10-
> [!WARNING]
10+
> \[!WARNING]
1111
> SPECIAL NOTICE: Introducing v1.0.0 comes with BREAKING CHANGES. We have removed caching of the flags in the solo config file. All commands will need required flags or user will need to answer the prompts. See more details in our release notes: [release/tag/v1.0.0](https://github.com/hashgraph/solo/releases/tag/v1.0.0)
1212
13-
1413
An opinionated CLI tool to deploy and manage standalone test networks.
1514

1615
## Table of Contents

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"test": "cross-env MOCHA_SUITE_NAME=\"Unit Tests\" c8 --report-dir='coverage/unit' mocha 'test/unit/**/*.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit.xml",
1515
"test-e2e-all": "cross-env MOCHA_SUITE_NAME=\"Mocha E2E All Tests\" c8 --report-dir='coverage/e2e-all' mocha 'test/e2e/**/*.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit-e2e-all.xml",
1616
"test-e2e-integration": "cross-env MOCHA_SUITE_NAME=\"Mocha E2E Integration Tests\" c8 --report-dir='coverage/e2e-integration' mocha 'test/e2e/integration/**/*.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit-e2e-integration.xml",
17+
"test-e2e-leases": "cross-env MOCHA_SUITE_NAME=\"Mocha E2E Lease Tests\" c8 --report-dir='coverage/e2e-leases' mocha 'test/e2e/integration/core/lease_manager.test.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit-e2e-integration.xml",
1718
"test-e2e-standard": "cross-env MOCHA_SUITE_NAME=\"Mocha E2E Standard Tests\" c8 --report-dir='coverage/e2e-standard' mocha 'test/e2e/**/*.ts' --ignore 'test/unit/**/*.ts' --ignore 'test/e2e/integration/**/*.ts' --ignore 'test/e2e/commands/mirror_node*.ts' --ignore 'test/e2e/commands/node*.ts' --ignore 'test/e2e/commands/separate_node*.ts' --ignore 'test/e2e/commands/relay*.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit-e2e-standard.xml --timeout 30000",
1819
"test-e2e-mirror-node": "cross-env MOCHA_SUITE_NAME=\"Mocha E2E Mirror Node Tests\" c8 --report-dir='coverage/e2e-mirror-node' mocha 'test/e2e/commands/mirror_node.test.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit-e2e-mirror-node.xml",
1920
"test-e2e-node-pem-stop": "cross-env MOCHA_SUITE_NAME=\"Mocha E2E Node PEM Stop Tests\" c8 --report-dir='coverage/e2e-node-pem-stop' mocha 'test/e2e/commands/node_pem_stop.test.ts' --reporter-options configFile=mocha-multi-reporter.json,cmrOutput=mocha-junit-reporter+mochaFile+junit-e2e-node-pem-stop.xml",

src/commands/mirror_node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class MirrorNodeCommand extends BaseCommand {
6363
async prepareHederaExplorerValuesArg (config: any) {
6464
let valuesArg = ''
6565

66-
const profileName = <string>this.configManager.getFlag<string>(flags.profileName)
66+
const profileName = this.configManager.getFlag<string>(flags.profileName) as string
6767
const profileValuesFile = await this.profileManager.prepareValuesHederaExplorerChart(profileName)
6868
if (profileValuesFile) {
6969
valuesArg += this.prepareValuesFiles(profileValuesFile)

src/core/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export const MINUTES = 60 * SECONDS
164164

165165
export const LEASE_ACQUIRE_RETRY_TIMEOUT = +process.env.LEASE_ACQUIRE_RETRY_TIMEOUT || 20 * SECONDS
166166
export const MAX_LEASE_ACQUIRE_ATTEMPTS = +process.env.MAX_LEASE_ACQUIRE_ATTEMPTS || 10
167-
export const LEASE_RENEW_TIMEOUT = +process.env.LEASE_RENEW_TIMEOUT || 10 * SECONDS
167+
export const DEFAULT_LEASE_RENEW_TIMEOUT = 10 * SECONDS
168168

169169
export const PODS_RUNNING_MAX_ATTEMPTS = +process.env.PODS_RUNNING_MAX_ATTEMPTS || 60 * 15
170170
export const PODS_RUNNING_DELAY = +process.env.PODS_RUNNING_DELAY || 1000

src/core/lease_manager.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ import { flags } from '../commands/index.ts'
1919
import type { ConfigManager } from './config_manager.ts'
2020
import type { K8 } from './k8.ts'
2121
import type { SoloLogger } from './logging.ts'
22-
import { LEASE_RENEW_TIMEOUT, LEASE_ACQUIRE_RETRY_TIMEOUT, MAX_LEASE_ACQUIRE_ATTEMPTS, OS_USERNAME } from './constants.ts'
22+
import { DEFAULT_LEASE_RENEW_TIMEOUT, LEASE_ACQUIRE_RETRY_TIMEOUT, MAX_LEASE_ACQUIRE_ATTEMPTS, OS_USERNAME } from './constants.ts'
2323
import type { ListrTaskWrapper } from 'listr2'
2424
import chalk from 'chalk'
2525
import { sleep } from './helpers.ts'
2626
import { LeaseWrapper } from './lease_wrapper.ts'
27+
import type { V1Lease } from '@kubernetes/client-node'
2728

2829
export class LeaseManager {
2930
constructor (
@@ -76,7 +77,8 @@ export class LeaseManager {
7677
}
7778

7879
//? Renew lease with the callback
79-
const intervalId = setInterval(renewLeaseCallback, LEASE_RENEW_TIMEOUT)
80+
const renewalTimeout = +process.env.LEASE_RENEW_TIMEOUT || DEFAULT_LEASE_RENEW_TIMEOUT
81+
const intervalId = setInterval(renewLeaseCallback, renewalTimeout)
8082

8183
const releaseLeaseCallback = async () => {
8284
//? Stop renewing the lease once release callback is called
@@ -94,6 +96,21 @@ export class LeaseManager {
9496
return { releaseLease: releaseLeaseCallback }
9597
}
9698

99+
private async tryAcquireLease (username: string,
100+
leaseName: string,
101+
namespace: string,
102+
task: ListrTaskWrapper<any, any, any>,
103+
title: string): Promise<V1Lease> {
104+
try {
105+
return await this.k8.readNamespacedLease(leaseName, namespace)
106+
} catch (error) {
107+
if (error.meta.statusCode !== 404) {
108+
task.title = `${title} - ${chalk.red(`failed to acquire lease, unexpected server response ${error.meta.statusCode}!`)}`
109+
}
110+
return Promise.resolve(null)
111+
}
112+
}
113+
97114
private async acquireLeaseOrRetry (
98115
username: string,
99116
leaseName: string,
@@ -105,23 +122,10 @@ export class LeaseManager {
105122
): Promise<void> {
106123
if (!attempt) attempt = 1
107124

108-
let exists = false
109-
110-
try {
111-
const lease = await this.k8.readNamespacedLease(leaseName, namespace)
112-
113-
exists = !!lease
114-
} catch (error) {
115-
if (error.meta.statusCode !== 404) {
116-
task.title = `${title} - ${chalk.red(`failed to acquire lease, unexpected server response ${error.meta.statusCode}!`)}` +
117-
`, attempt: ${chalk.cyan(attempt.toString())}/${chalk.cyan(maxAttempts.toString())}`
118-
119-
throw new SoloError(`Failed to acquire lease: ${error.message}`)
120-
}
121-
}
125+
let lease = await this.tryAcquireLease(username, leaseName, namespace, task, title)
122126

123127
//? In case the lease is already acquired retry after cooldown
124-
if (exists) {
128+
while (!!lease && !this.isLeaseExpired(lease)) {
125129
attempt++
126130

127131
if (attempt === maxAttempts) {
@@ -137,8 +141,11 @@ export class LeaseManager {
137141
`, attempt: ${chalk.cyan(attempt.toString())}/${chalk.cyan(maxAttempts.toString())}`
138142

139143
await sleep(LEASE_ACQUIRE_RETRY_TIMEOUT)
144+
lease = await this.tryAcquireLease(username, leaseName, namespace, task, title)
145+
}
140146

141-
return this.acquireLeaseOrRetry(username, leaseName, namespace, task, title, attempt)
147+
if (lease) {
148+
await this.k8.deleteNamespacedLease(leaseName, namespace)
142149
}
143150

144151
await this.k8.createNamespacedLease(namespace, leaseName, username)
@@ -154,4 +161,18 @@ export class LeaseManager {
154161
if (!await this.k8.hasNamespace(namespace)) return null
155162
return namespace
156163
}
164+
165+
private isLeaseExpired (lease: V1Lease) : boolean {
166+
const now = Date.now()
167+
const duration = lease.spec?.leaseDurationSeconds ? lease.spec?.leaseDurationSeconds : 20
168+
let acquired = lease.spec?.acquireTime
169+
170+
if (lease.spec.renewTime) {
171+
acquired = lease.spec?.renewTime
172+
}
173+
174+
const deltaSec = (now - new Date(acquired).valueOf()) / 1000
175+
176+
return deltaSec > duration
177+
}
157178
}

src/core/templates.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { DataValidationError, SoloError, IllegalArgumentError, MissingArgumentEr
2121
import { constants } from './index.ts'
2222
import { type AccountId } from '@hashgraph/sdk'
2323
import type { NodeAlias, PodName } from '../types/aliases.ts'
24-
import { GrpcProxyTlsEnums} from './enumerations.ts'
24+
import { GrpcProxyTlsEnums } from './enumerations.ts'
2525

2626
export class Templates {
2727
public static renderNetworkPodName (nodeAlias: NodeAlias): PodName {

test/e2e/integration/core/lease_manager.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import { expect } from 'chai'
2020
import { flags } from '../../../../src/commands/index.ts'
2121
import { e2eTestSuite, getDefaultArgv, TEST_CLUSTER } from '../../../test_util.ts'
2222
import * as version from '../../../../version.ts'
23-
import { LEASE_ACQUIRE_RETRY_TIMEOUT, MAX_LEASE_ACQUIRE_ATTEMPTS, MINUTES } from '../../../../src/core/constants.ts'
23+
import {
24+
DEFAULT_LEASE_RENEW_TIMEOUT,
25+
LEASE_ACQUIRE_RETRY_TIMEOUT,
26+
MAX_LEASE_ACQUIRE_ATTEMPTS,
27+
MINUTES
28+
} from '../../../../src/core/constants.ts'
2429
import { sleep } from '../../../../src/core/helpers.js'
2530

2631
const namespace = 'lease-mngr-e2e'
@@ -75,5 +80,25 @@ e2eTestSuite(namespace, argv, undefined, undefined, undefined, undefined, undefi
7580

7681
await initialLease.release()
7782
}).timeout(3 * MINUTES)
83+
84+
it('expired leases should be overwritten', async () => {
85+
process.env.LEASE_RENEW_TIMEOUT = (DEFAULT_LEASE_RENEW_TIMEOUT * 4).toString()
86+
const initialLease = leaseManager.instantiateLease()
87+
const title = 'Initial Lease'
88+
// @ts-ignore to access private property
89+
await initialLease.acquireTask({ title }, title)
90+
91+
// Ensure lease expires
92+
await sleep(LEASE_ACQUIRE_RETRY_TIMEOUT)
93+
94+
process.env.LEASE_RENEW_TIMEOUT = DEFAULT_LEASE_RENEW_TIMEOUT.toString()
95+
const newLease = leaseManager.instantiateLease()
96+
const newTitle = 'New Lease'
97+
// @ts-ignore to access private property
98+
await newLease.acquireTask({ newTitle }, newTitle, 8)
99+
100+
await initialLease.release()
101+
await newLease.release()
102+
}).timeout(3 * MINUTES)
78103
})
79104
})

0 commit comments

Comments
 (0)