Skip to content

Commit

Permalink
fix: removing only networks started by hedera instead of global prune (
Browse files Browse the repository at this point in the history
…#686)

Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas authored Jul 17, 2024
1 parent 4035713 commit c2a0a10
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ npm install && npm install -g

> **_NOTE:_** If you want to use any of the CLI options listed below, you'd need to pass `--` after `npm run start` (for example) and then specify the wanted option. For example, if you want to start in detached mode, you can use `npm run start -- -d`
> **_WARNING:_** While stopping the networks, we will first list all Docker networks with the `hedera-` prefix in their names. This operation may affect not only the networks initiated by the `npm run start` command from this repository but also any other networks you have created with this prefix. Network termination can be triggered both by a direct `npm run stop` call and by the `npm run start` script if the initial startup process fails and failover recovery is activated. One of the recovery steps includes attempting to close all previously started networks with the `hedera-` prefix.
## Using hedera-local

Expand Down
6 changes: 3 additions & 3 deletions docker-compose-state-migration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ services:

networks:
network-node-bridge:
name: network-node-bridge
name: hedera-network-node-bridge
driver: bridge
ipam:
driver: default
Expand All @@ -365,10 +365,10 @@ networks:
ip_range: 172.27.0.0/24
gateway: 172.27.0.254
mirror-node:
name: mirror-node
name: hedera-mirror-node
driver: bridge
cloud-storage:
name: cloud-storage
name: hedera-cloud-storage
driver: bridge

volumes:
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.evm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ services:
"echo 'Deleting old recordStream files.';find /records/ -type f -delete;echo 'This container is intentionally disabled by the EVM profile.';"
]
command: []

account-balances-uploader:
restart: "no"
entrypoint:
Expand All @@ -17,6 +18,7 @@ services:
"echo 'Deleting old accountBalances files.';find /balances/ -type f -delete;echo 'This container is intentionally disabled by the EVM profile.';"
]
command: []

record-sidecar-uploader:
restart: "no"
entrypoint:
Expand All @@ -26,6 +28,7 @@ services:
"echo 'Deleting old sidecars files.';find /sidecar-files/ -type f -delete;echo 'This container is intentionally disabled by the EVM profile.';"
]
command: []

minio:
restart: "no"
entrypoint:
Expand All @@ -34,6 +37,7 @@ services:
"This container is intentionally disabled by the EVM profile."
]
command: []

importer:
user: root
volumes:
Expand Down
8 changes: 5 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ services:
environment:
DOCKER_LOCAL_MIRROR_NODE_MENU_NAME: ${DOCKER_LOCAL_MIRROR_NODE_MENU_NAME}
DOCKER_LOCAL_MIRROR_NODE_URL: ${DOCKER_LOCAL_MIRROR_NODE_URL}
networks:
- mirror-node

web3:
image: "${MIRROR_IMAGE_PREFIX}hedera-mirror-web3:${MIRROR_WEB3_IMAGE_TAG:-${MIRROR_IMAGE_TAG}}"
Expand Down Expand Up @@ -501,7 +503,7 @@ services:

networks:
network-node-bridge:
name: network-node-bridge
name: hedera-network-node
driver: bridge
ipam:
driver: default
Expand All @@ -510,10 +512,10 @@ networks:
ip_range: 172.27.0.0/24
gateway: 172.27.0.254
mirror-node:
name: mirror-node
name: hedera-mirror-node
driver: bridge
cloud-storage:
name: cloud-storage
name: hedera-cloud-storage
driver: bridge

volumes:
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const CONTAINERS = [
},
];

export const NETWORK_PREFIX = 'hedera-';

export const CONSENSUS_NODE_LABEL = "network-node";
export const MIRROR_NODE_LABEL = "mirror-node-rest";
export const RELAY_LABEL = "json-rpc-relay";
Expand Down
5 changes: 3 additions & 2 deletions src/services/DockerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import detectPort from 'detect-port';
import * as dotenv from 'dotenv';
import { CLIOptions } from '../types/CLIOptions';
import path from 'path';
import { SafeDockerNetworkRemover } from '../utils/SafeDockerNetworkRemover';

dotenv.config();

Expand Down Expand Up @@ -90,7 +91,7 @@ export class DockerService implements IService{

/**
* Returns the null output path depending on the operating system.
*
*
* @public
* @returns {string} - The null output path.
*/
Expand Down Expand Up @@ -391,7 +392,7 @@ export class DockerService implements IService{
shell.exec(`docker compose down -v --remove-orphans 2>${nullOutput}`);
this.logger.trace('Cleaning the volumes and temp files...', stateName);
shell.exec(`rm -rf network-logs/* >${nullOutput} 2>&1`);
shell.exec(`docker network prune -f 2>${nullOutput}`);
SafeDockerNetworkRemover.removeAll();
this.logger.info(`${LOADING} Trying to startup again...`, stateName);
}
}
3 changes: 2 additions & 1 deletion src/state/StopState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from '../constants';
import { CLIOptions } from '../types/CLIOptions';
import { CLIService } from '../services/CLIService';
import { SafeDockerNetworkRemover } from '../utils/SafeDockerNetworkRemover';

export class StopState implements IState {
/**
Expand Down Expand Up @@ -101,7 +102,7 @@ export class StopState implements IState {
shell.exec(`rm -rf network-logs/* >${nullOutput} 2>&1`);
this.logger.trace(`Working dir is ${this.cliOptions.workDir}`, this.stateName);
shell.exec(`rm -rf "${join(this.cliOptions.workDir, 'network-logs')}" >${nullOutput} 2>&1`);
shell.exec(`docker network prune -f 2>${nullOutput}`);
SafeDockerNetworkRemover.removeAll();
shell.cd(rootPath);
this.logger.info(STOP_STATE_STOPPED_MESSAGE, this.stateName);
this.observer?.update(EventType.Finish);
Expand Down
57 changes: 57 additions & 0 deletions src/utils/SafeDockerNetworkRemover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*-
*
* Hedera Local Node
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import shell from 'shelljs';
import { IS_WINDOWS, NETWORK_PREFIX } from '../constants';

/**
* Checks if the given string is a valid Docker network ID.
* A valid Docker network ID is a 12-character hexadecimal string.
*
* @param {string} id - The string to be validated as a Docker network ID.
* @returns {boolean} - Returns true if the string is a valid Docker network ID, false otherwise.
*
* @example
* const id = "89ded1eca1d5";
* console.log(isCorrectDockerId(id)); // Output: true
*
* @example
* const invalidId = "invalidID123";
* console.log(isCorrectDockerId(invalidId)); // Output: false
*/
const isCorrectDockerId = (id: string) => id.trim() !== '' && /^[a-f0-9]{12}$/.test(id);

/**
* Provides utility methods for safe networks removal.
*/
export class SafeDockerNetworkRemover {
/**
* Removes all the networks started by docker compose. Only networks with the "hedera-" prefix will be affected.
*/
public static removeAll() {
const result = shell.exec(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`);
if (!result || result.stderr !== '') {
return;
}
result.stdout.split('\n').filter(isCorrectDockerId).forEach((id) => {
shell.exec(`docker network rm ${id} -f 2>${IS_WINDOWS ? 'null' : '/dev/null'}`);
});
}
}
6 changes: 6 additions & 0 deletions test/unit/states/InitState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { expect } from 'chai';
import fs from 'fs';
import yaml from 'js-yaml';
import { after } from "mocha";
import { join } from 'path';
import { SinonSandbox, SinonSpy, SinonStub, SinonStubbedInstance } from 'sinon';
import {
Expand Down Expand Up @@ -249,6 +250,11 @@ describe('InitState tests', () => {
onStartStub.restore()
})

after(() => {
fsReadFileSync.restore()
ymlLoad.restore()
})

it('should execute "prepareWorkDirectory" as expected', async () => {
prepareWorkDirectoryStub.restore()

Expand Down
8 changes: 7 additions & 1 deletion test/unit/states/StopState.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { StopState } from '../../../src/state/StopState';
import {
DOCKER_CLEANING_VALUMES_MESSAGE,
DOCKER_STOPPING_CONTAINERS_MESSAGE,
NETWORK_PREFIX,
STOP_STATE_INIT_MESSAGE,
STOP_STATE_ON_START_MESSAGE,
STOP_STATE_STOPPED_MESSAGE,
Expand Down Expand Up @@ -79,6 +80,11 @@ describe('StopState tests', () => {

it('should execute onStart properly', async () => {
const { shellCDStub, shellExecStub }= shellTest;
const network = { name: 'hedera-cloud-storage', id: '89ded1eca1d5' };
shellExecStub
.withArgs(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`)
.returns({ stderr: '', stdout: network.id });

await stopState.onStart();

// loggin messages
Expand All @@ -100,7 +106,7 @@ describe('StopState tests', () => {
testSandbox.assert.calledWith(shellExecStub, 'docker compose down -v --remove-orphans 2>/dev/null');
testSandbox.assert.calledWith(shellExecStub, 'rm -rf network-logs/* >/dev/null 2>&1');
testSandbox.assert.calledWith(shellExecStub, 'rm -rf "testDir/network-logs" >/dev/null 2>&1');
testSandbox.assert.calledWith(shellExecStub, 'docker network prune -f 2>/dev/null');
testSandbox.assert.calledWith(shellExecStub, `docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`);

expect(processTest.processCWDStub.calledOnce).to.be.true;
})
Expand Down
109 changes: 109 additions & 0 deletions test/unit/utils/SafeDockerNetworkRemover.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*-
*
* Hedera Local Node
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import { expect } from 'chai';
import sinon from 'sinon';
import { SafeDockerNetworkRemover } from '../../../src/utils/SafeDockerNetworkRemover';
import { IS_WINDOWS, NETWORK_PREFIX } from '../../../src/constants';
import { getTestBed } from '../testBed';
import { load } from 'js-yaml';
import { readdirSync, readFileSync } from 'fs';
import { join } from "path";

describe('SafeDockerNetworkRemover', () => {
let shellExecStub: sinon.SinonStub;

before(() => {
let {
shellStubs
} = getTestBed({
workDir: 'testDir',
});

shellExecStub = shellStubs.shellExecStub;
});

after(() => {
shellExecStub.restore();
});

describe('removeByName', () => {
it('should not proceed if docker network ls command fails', () => {
shellExecStub.withArgs(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`).returns({ stderr: 'error' });

SafeDockerNetworkRemover.removeAll();
expect(shellExecStub.calledOnce).to.be.true;
expect(shellExecStub.calledWith(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`)).to.be.true;
});

it('should not proceed if docker network ls command returns no result', () => {
shellExecStub.withArgs(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`).returns({ stdout: '', stderr: '' });

SafeDockerNetworkRemover.removeAll();
expect(shellExecStub.calledWith(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`)).to.be.true;
});

it('should remove valid Docker network IDs', () => {
shellExecStub.withArgs(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`).returns({ stdout: '89ded1eca1d5\ninvalidID123\n', stderr: '' });
shellExecStub.withArgs(`docker network rm 89ded1eca1d5 -f 2>${IS_WINDOWS ? 'null' : '/dev/null'}`).returns({});

SafeDockerNetworkRemover.removeAll();
expect(shellExecStub.calledWith(`docker network rm 89ded1eca1d5 -f 2>${IS_WINDOWS ? 'null' : '/dev/null'}`)).to.be.true;
});

it('should not remove invalid Docker network IDs', () => {
shellExecStub.withArgs(`docker network ls --filter name=${NETWORK_PREFIX} --format "{{.ID}}"`).returns({ stdout: 'invalidID123\nanotherInvalid\n', stderr: '' });

SafeDockerNetworkRemover.removeAll();
expect(shellExecStub.calledWith(`docker network rm invalidID123 -f 2>${IS_WINDOWS ? 'null' : '/dev/null'}`)).to.be.false;
expect(shellExecStub.calledWith(`docker network rm anotherInvalid -f 2>${IS_WINDOWS ? 'null' : '/dev/null'}`)).to.be.false;
});
});

describe('config check', () => {
it('check if all of the networks from composer yaml files are listed in the NETWORK_NAMES const', () => {
const relativePath = '../../..';
const files = readdirSync(join(__dirname, relativePath)).filter(name => /^docker-compose.*\.yml$/.test(name));
for (const file of files) {
const data = readFileSync(join(__dirname, `${relativePath}/${file}`));
const config = load(data.toString());
for (const network of Object.values(config.networks || {}).map((network: any) => network.name.trim())) {
expect(network.startsWith(NETWORK_PREFIX), `Network '${network}' does not start with the prefix '${NETWORK_PREFIX}'. It won't be removed by 'npm run stop'`).to.be.true;
}
}
});
it('check if all services have a network set and all network names start with "hedera-"', () => {
const relativePath = '../../..';
const files = readdirSync(join(__dirname, relativePath)).filter(name => /^docker-compose(?!.*(evm|multinode)\.yml$).*\.yml$/.test(name));
for (const file of files) {
const data = readFileSync(join(__dirname, `${relativePath}/${file}`));
const config = load(data.toString());
const services = config.services || {};
for (const [serviceName, serviceConfig] of Object.entries(services)) {
if (serviceConfig.extends || (serviceConfig.network_mode || '' === 'none')) {
continue; // The child service might have inherited the network. There is no network in non-network mode.
}
const networks = serviceConfig.networks;
expect(networks, `Service '${serviceName}' does not have a network set.`).to.exist;
}
}
});
});
});

0 comments on commit c2a0a10

Please sign in to comment.