Skip to content

Commit

Permalink
refactor: polish code, add IPv6 tests and remove unsupported ports
Browse files Browse the repository at this point in the history
  • Loading branch information
fedealconada committed Nov 13, 2023
1 parent f7c241c commit 683f667
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 139 deletions.
77 changes: 46 additions & 31 deletions Probe/Tests/Utils/PingMonitor.test.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,79 @@
import Hostname from 'Common/Types/API/Hostname';
import IPv4 from 'Common/Types/IP/IPv4';
import IPv6 from 'Common/Types/IP/IPv6';
import PositiveNumber from 'Common/Types/PositiveNumber';
import Ping, {
PingResponse,
} from '../../Utils/Monitors/MonitorTypes/PingMonitor';

describe('Ping', () => {
jest.setTimeout(10000);
test('Ping.ping should return appropriate object if the valid hostname is given', async () => {
let result: PingResponse | null = await Ping.ping(
new Hostname('google.com', 80)
);

expect(result).not.toBeNull();
expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0);
expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000);
expect(result!.isOnline).toBe(true);
result = await Ping.ping(new Hostname('www.google.com', 80), {
timeout: new PositiveNumber(5000),
});
test('should succeed with a valid and reachable hostname', async () => {
const result: PingResponse | null = await Ping.ping(
new Hostname('google.com'),
{
timeout: new PositiveNumber(5000),
}
);

expect(result).not.toBeNull();
expect(result!.isOnline).toBe(true);
expect(result!.responseTimeInMS).toBeDefined();
expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0);
expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000);
});

result = await Ping.ping(new Hostname('www.google.com', 65000), {
timeout: new PositiveNumber(5000),
});
expect(result).not.toBeNull();
expect(result!.isOnline).toBe(false);
expect(result!.responseTimeInMS).toBeUndefined();
test('should fail with an invalid hostname', async () => {
const result: PingResponse | null = await Ping.ping(
new Hostname('invalid.hostname'),
{
timeout: new PositiveNumber(5000),
}
);

result = await Ping.ping(new Hostname('www.a.com', 65000), {
timeout: new PositiveNumber(5000),
});
expect(result).not.toBeNull();
expect(result!.isOnline).toBe(false);
expect(result!.isOnline).toBe(false);
expect(result!.responseTimeInMS).toBeUndefined();
});
test('Ping.ping should return appropriate object if the valid IPV4 or IPV6 is given', async () => {
let result: PingResponse | null = null;

result = await Ping.ping(new IPv4('172.217.170.206'), {
timeout: new PositiveNumber(5000),
}); // One of the google ip
test('should succeed with a valid IPV4 address', async () => {
const result: PingResponse | null = await Ping.ping(
new IPv4('8.8.8.8'),
{
timeout: new PositiveNumber(5000),
}
);

expect(result).not.toBeNull();
expect(result!.isOnline).toBe(true);
expect(result!.responseTimeInMS).toBeDefined();
expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0);
expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000);
});

test('should succeed with a valid and reachable IPv6 address', async () => {
const ipv6Address: IPv6 = new IPv6('2001:4860:4860::8888');
const result: PingResponse | null = await Ping.ping(ipv6Address);

result = await Ping.ping(new IPv4('192.0.2.200')); //
expect(result).not.toBeNull();
expect(result!.isOnline).toBe(false);
expect(result!.responseTimeInMS).toBeUndefined();
expect(result!.isOnline).toBe(true);
expect(result!.responseTimeInMS).toBeDefined();
expect(result!.responseTimeInMS?.toNumber()).toBeGreaterThan(0);
expect(result!.responseTimeInMS?.toNumber()).toBeLessThanOrEqual(5000);
});

test('should fail with an unreachable IPv6 address', async () => {
const ipv6Address: IPv6 = new IPv6(
'abcd:ef01:2345:6789:abcd:ef01:2345:6789'
);
// since ping does not support timeouts on IPv6, we set the deadline to 1 second to avoid exceeding jest's timeout
const result: PingResponse | null = await Ping.ping(ipv6Address, {
deadline: 1,
});

result = await Ping.ping(new IPv4('0.42.52.42')); // ip can't start 0
expect(result).not.toBeNull();
expect(result!.responseTimeInMS).toBeUndefined();
expect(result!.isOnline).toBe(false);
expect(result!.responseTimeInMS).toBeUndefined();
});
});
175 changes: 68 additions & 107 deletions Probe/Utils/Monitors/MonitorTypes/PingMonitor.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import ping from 'ping';
import logger from 'CommonServer/Utils/Logger';
import Sleep from 'Common/Types/Sleep';
import UnableToReachServer from 'Common/Types/Exception/UnableToReachServer';

import Hostname from 'Common/Types/API/Hostname';
import URL from 'Common/Types/API/URL';
import IPv4 from 'Common/Types/IP/IPv4';
import IPv6 from 'Common/Types/IP/IPv6';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import logger from 'CommonServer/Utils/Logger';
import ping from 'ping';
import UnableToReachServer from 'Common/Types/Exception/UnableToReachServer';
import Sleep from 'Common/Types/Sleep';

// TODO - make sure it work for the IPV6
export interface PingResponse {
isOnline: boolean;
responseTimeInMS?: PositiveNumber | undefined;
Expand All @@ -18,155 +18,116 @@ export interface PingResponse {

export interface PingOptions {
timeout?: PositiveNumber;
retry?: number | undefined;
currentRetryCount?: number | undefined;
monitorId?: ObjectID | undefined;
isOnlineCheckRequest?: boolean | undefined;
retry?: number;
currentRetryCount?: number;
monitorId?: ObjectID;
isOnlineCheckRequest?: boolean;
deadline?: number;
}

export default class PingMonitor {
// burn domain names into the code to see if this probe is online.
private static checkHosts = [
'google.com',
'facebook.com',
'microsoft.com',
'youtube.com',
'apple.com',
];

public static async isProbeOnline(): Promise<boolean> {
if (
(
await PingMonitor.ping(new Hostname('google.com'), {
isOnlineCheckRequest: true,
})
)?.isOnline
) {
return true;
} else if (
(
await PingMonitor.ping(new Hostname('facebook.com'), {
isOnlineCheckRequest: true,
})
)?.isOnline
) {
return true;
} else if (
(
await PingMonitor.ping(new Hostname('microsoft.com'), {
isOnlineCheckRequest: true,
})
)?.isOnline
) {
return true;
} else if (
(
await PingMonitor.ping(new Hostname('youtube.com'), {
isOnlineCheckRequest: true,
})
)?.isOnline
) {
return true;
} else if (
(
await PingMonitor.ping(new Hostname('apple.com'), {
for (const host of PingMonitor.checkHosts) {
const response: PingResponse | null = await PingMonitor.ping(
new Hostname(host),
{
isOnlineCheckRequest: true,
})
)?.isOnline
) {
return true;
}
);
if (response?.isOnline) {
return true;
}
}

return false;
}

public static async ping(
host: Hostname | IPv4 | IPv6 | URL,
pingOptions?: PingOptions
pingOptions: PingOptions = {}
): Promise<PingResponse | null> {
let hostAddress: string = '';
if (host instanceof Hostname) {
hostAddress = host.hostname;
} else if (host instanceof URL) {
hostAddress = host.hostname.hostname;
} else {
hostAddress = host.toString();
}
const hostAddress: string =
host instanceof URL ? host.hostname.hostname : host.toString();
const isIPv6: boolean = host instanceof IPv6;
const {
monitorId,
currentRetryCount = 0,
timeout = isIPv6
? false
: (new PositiveNumber(5000) as false | PositiveNumber),
retry = 5,
} = pingOptions;

logger.info(
`Pinging host: ${pingOptions?.monitorId?.toString()} ${hostAddress} - Retry: ${
pingOptions?.currentRetryCount
}`
`Pinging host ${monitorId?.toString()} at ${hostAddress}, Retry: ${currentRetryCount}`
);

try {
const res: ping.PingResponse = await ping.promise.probe(
hostAddress,
{
timeout: Math.ceil(
(pingOptions?.timeout?.toNumber() || 5000) / 1000
),
v6: isIPv6,
// @ts-ignore
timeout: isIPv6
? false
: // @ts-ignore
Math.ceil(timeout.toNumber() / 1000),
deadline: pingOptions.deadline,
}
);

logger.info(
`Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress} success: `
);
logger.info(res);

if (!res.alive) {
throw new UnableToReachServer(
`Unable to reach host ${hostAddress}. Monitor ID: ${pingOptions?.monitorId?.toString()}`
`Unable to reach host ${hostAddress}. Monitor ID: ${monitorId?.toString()}`
);
}

return {
isOnline: res.alive,
isOnline: true,
responseTimeInMS: res.time
? new PositiveNumber(Math.ceil(res.time as any))
: undefined,
failureCause: '',
};
} catch (err: unknown) {
logger.info(
`Pinging host ${pingOptions?.monitorId?.toString()} ${hostAddress} error: `
} catch (err) {
logger.error(
`Error pinging host ${monitorId?.toString()} at ${hostAddress}:`,
err
);
logger.info(err);

if (!pingOptions) {
pingOptions = {};
}

if (!pingOptions.currentRetryCount) {
pingOptions.currentRetryCount = 0;
}

if (pingOptions.currentRetryCount < (pingOptions.retry || 5)) {
pingOptions.currentRetryCount++;
if (currentRetryCount < retry) {
await Sleep.sleep(1000);
return await this.ping(host, pingOptions);
return this.ping(host, {
...pingOptions,
currentRetryCount: currentRetryCount + 1,
});
}

// check if timeout exceeded and if yes, return null
if (
(err as any).toString().includes('timeout') &&
(err as any).toString().includes('exceeded')
typeof err === 'string' &&
err.includes('timeout') &&
err.includes('exceeded')
) {
logger.info(
`Ping Monitor - Timeout exceeded ${pingOptions.monitorId?.toString()} ${host.toString()} - ERROR: ${err}`
);

return {
isOnline: false,
failureCause: 'Timeout exceeded',
};
return { isOnline: false, failureCause: 'Timeout exceeded' };
}

// check if the probe is online.
if (!pingOptions.isOnlineCheckRequest) {
if (!(await PingMonitor.isProbeOnline())) {
logger.error(
`PingMonitor Monitor - Probe is not online. Cannot ping ${pingOptions?.monitorId?.toString()} ${host.toString()} - ERROR: ${err}`
);
return null;
}
if (
!pingOptions.isOnlineCheckRequest &&
!(await PingMonitor.isProbeOnline())
) {
return null;
}

return {
isOnline: false,
failureCause: (err as any).toString(),
failureCause: err instanceof Error ? err.message : String(err),
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion Probe/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"testEnvironment": "node",
"collectCoverage": true,
"coverageReporters": ["text"],
"coverageReporters": ["text", "lcov"],
"testRegex": "./Tests/(.*).test.ts",
"collectCoverageFrom": ["./**/*.(tsx||ts)"],
"coverageThreshold": {
Expand Down

0 comments on commit 683f667

Please sign in to comment.