mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-22 03:04:03 +00:00
bb4a06d1bb
- Add polkit rule for sudoless service management - Improve update manager with multi-strategy restart approach - Fix npm dependency conflicts in update process - Add comprehensive test suite for update system - Enhance frontend update progress tracking - Update install script with automatic polkit setup
709 lines
30 KiB
JavaScript
709 lines
30 KiB
JavaScript
/**
|
|
* Integration Tests for Pulse Monitoring System
|
|
* Tests end-to-end workflows and component interactions
|
|
*/
|
|
|
|
// Mock external dependencies
|
|
jest.mock('axios');
|
|
jest.mock('fs', () => ({
|
|
promises: {
|
|
mkdir: jest.fn(),
|
|
readFile: jest.fn(),
|
|
writeFile: jest.fn()
|
|
}
|
|
}));
|
|
|
|
const axios = require('axios');
|
|
const fs = require('fs').promises;
|
|
const { fetchDiscoveryData, fetchMetricsData, fetchPbsData, clearCaches } = require('../dataFetcher');
|
|
const { initializeApiClients } = require('../apiClients');
|
|
const { loadConfiguration } = require('../configLoader');
|
|
const AlertManager = require('../alertManager');
|
|
const customThresholds = require('../customThresholds');
|
|
|
|
// Mock console to reduce test noise
|
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
describe('Pulse Integration Tests', () => {
|
|
let originalEnv;
|
|
let mockApiClients;
|
|
let mockPbsApiClients;
|
|
let alertManager;
|
|
|
|
beforeEach(() => {
|
|
originalEnv = { ...process.env };
|
|
jest.clearAllMocks();
|
|
|
|
// Mock file operations
|
|
fs.mkdir.mockResolvedValue();
|
|
fs.readFile.mockResolvedValue('{}');
|
|
fs.writeFile.mockResolvedValue();
|
|
|
|
// Set up mock API clients
|
|
mockApiClients = {
|
|
'pve-main': {
|
|
client: {
|
|
get: jest.fn(),
|
|
post: jest.fn()
|
|
},
|
|
config: {
|
|
id: 'pve-main',
|
|
name: 'Main PVE Cluster',
|
|
host: 'pve.example.com',
|
|
tokenId: 'test@pve!test',
|
|
tokenSecret: 'test-secret'
|
|
}
|
|
}
|
|
};
|
|
|
|
mockPbsApiClients = {
|
|
'pbs-main': {
|
|
client: {
|
|
get: jest.fn(),
|
|
post: jest.fn()
|
|
},
|
|
config: {
|
|
id: 'pbs-main',
|
|
name: 'Main PBS Server',
|
|
host: 'pbs.example.com'
|
|
}
|
|
}
|
|
};
|
|
|
|
// Initialize AlertManager for testing
|
|
alertManager = new AlertManager();
|
|
|
|
// Clear custom thresholds cache
|
|
customThresholds.cache.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Restore environment
|
|
Object.keys(process.env).forEach(key => delete process.env[key]);
|
|
Object.keys(originalEnv).forEach(key => {
|
|
process.env[key] = originalEnv[key];
|
|
});
|
|
|
|
// Cleanup AlertManager
|
|
if (alertManager) {
|
|
alertManager.destroy();
|
|
}
|
|
|
|
customThresholds.cache.clear();
|
|
});
|
|
|
|
describe('Complete Monitoring Workflow', () => {
|
|
test('should perform full discovery -> metrics -> alerting cycle', async () => {
|
|
// === STEP 1: Discovery Phase ===
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path === '/cluster/status') {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ type: 'cluster', name: 'test-cluster', nodes: 2 },
|
|
{ type: 'node', name: 'node1', ip: '192.168.1.10' },
|
|
{ type: 'node', name: 'node2', ip: '192.168.1.11' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
if (path === '/nodes') {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ node: 'node1', status: 'online' },
|
|
{ node: 'node2', status: 'online' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('/qemu')) {
|
|
if (path.includes('node1')) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ vmid: 100, name: 'web-server', status: 'running' },
|
|
{ vmid: 101, name: 'database', status: 'running' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
}
|
|
if (path.includes('/lxc')) {
|
|
if (path.includes('node2')) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ vmid: 200, name: 'nginx-proxy', status: 'running' },
|
|
{ vmid: 201, name: 'monitoring', status: 'running' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
const discoveryData = await fetchDiscoveryData(mockApiClients, mockPbsApiClients);
|
|
|
|
// Verify discovery results
|
|
expect(discoveryData.nodes).toHaveLength(2);
|
|
expect(discoveryData.vms).toHaveLength(2);
|
|
expect(discoveryData.containers).toHaveLength(2);
|
|
expect(discoveryData.vms.some(vm => vm.vmid === 100)).toBe(true);
|
|
expect(discoveryData.containers.some(ct => ct.vmid === 200)).toBe(true);
|
|
|
|
// === STEP 2: Metrics Collection ===
|
|
const runningGuests = [
|
|
...discoveryData.vms.filter(vm => vm.status === 'running'),
|
|
...discoveryData.containers.filter(ct => ct.status === 'running')
|
|
];
|
|
|
|
// Mock RRD and current status responses
|
|
let callCount = 0;
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path.includes('/rrddata')) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ time: now - 300, cpu: 0.85, memory: 0.75, netin: 1000, netout: 2000 },
|
|
{ time: now - 240, cpu: 0.92, memory: 0.78, netin: 1100, netout: 2100 },
|
|
{ time: now - 180, cpu: 0.88, memory: 0.82, netin: 1200, netout: 2200 }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('/status')) {
|
|
callCount++;
|
|
// Return high CPU for some guests to trigger alerts
|
|
const highCpu = callCount <= 2; // First two guests get high CPU
|
|
return Promise.resolve({
|
|
data: {
|
|
data: {
|
|
cpu: highCpu ? 0.95 : 0.45, // 95% vs 45%
|
|
mem: 2147483648, // 2GB in bytes
|
|
disk: 10737418240, // 10GB in bytes
|
|
netin: 1500,
|
|
netout: 2500
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
const metricsData = await fetchMetricsData(
|
|
discoveryData.vms.filter(vm => vm.status === 'running'),
|
|
discoveryData.containers.filter(ct => ct.status === 'running'),
|
|
mockApiClients
|
|
);
|
|
|
|
// Verify metrics collection
|
|
expect(metricsData).toHaveLength(4); // All running guests
|
|
expect(metricsData.every(m => m.current)).toBe(true);
|
|
expect(metricsData.every(m => Array.isArray(m.data))).toBe(true);
|
|
|
|
// === STEP 3: Alert Processing ===
|
|
const triggeredAlerts = alertManager.processMetrics(metricsData);
|
|
|
|
// Should trigger alerts for high CPU guests
|
|
const highCpuGuests = metricsData.filter(m => m.current.cpu > 0.90);
|
|
expect(highCpuGuests.length).toBeGreaterThan(0);
|
|
|
|
console.log(`Integration test: Found ${highCpuGuests.length} guests with high CPU, ${triggeredAlerts.length} alerts triggered`);
|
|
});
|
|
|
|
test('should handle custom thresholds in monitoring workflow', async () => {
|
|
// === STEP 1: Set custom thresholds ===
|
|
await customThresholds.setThresholds('pve-main', 'node1', '100', {
|
|
cpu: { warning: 60, critical: 80 }, // Lower than defaults
|
|
memory: { warning: 70, critical: 90 }
|
|
});
|
|
|
|
// === STEP 2: Mock guest with moderate CPU (would normally be OK) ===
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path.includes('/status')) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: {
|
|
cpu: 0.75, // 75% - exceeds custom warning (60%) but not default (85%)
|
|
mem: 1073741824, // 1GB
|
|
disk: 5368709120 // 5GB
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('/rrddata')) {
|
|
return Promise.resolve({
|
|
data: { data: [{ time: Date.now() / 1000, cpu: 0.75 }] }
|
|
});
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
const testGuest = {
|
|
id: 100,
|
|
endpointId: 'pve-main',
|
|
node: 'node1',
|
|
vmid: '100',
|
|
type: 'qemu',
|
|
name: 'test-vm',
|
|
status: 'running'
|
|
};
|
|
|
|
const metricsData = await fetchMetricsData([testGuest], [], mockApiClients);
|
|
|
|
// === STEP 3: Verify custom threshold integration ===
|
|
const guestMetrics = metricsData[0];
|
|
expect(guestMetrics.current.cpu).toBe(0.75);
|
|
|
|
// Get custom thresholds for this guest
|
|
const customConfig = customThresholds.getThresholds('pve-main', 'node1', '100');
|
|
expect(customConfig).not.toBeNull();
|
|
expect(customConfig.thresholds.cpu.warning).toBe(60); // 60%
|
|
expect(customConfig.thresholds.cpu.critical).toBe(80); // 80%
|
|
|
|
// This guest should trigger a warning with custom thresholds
|
|
// (75% > 60% warning threshold)
|
|
expect(guestMetrics.current.cpu * 100).toBeGreaterThan(customConfig.thresholds.cpu.warning);
|
|
expect(guestMetrics.current.cpu * 100).toBeLessThan(customConfig.thresholds.cpu.critical);
|
|
});
|
|
});
|
|
|
|
describe('PBS Integration Workflow', () => {
|
|
test('should discover PBS data and correlate with PVE guests', async () => {
|
|
// === STEP 1: Mock PBS discovery ===
|
|
mockPbsApiClients['pbs-main'].client.get.mockImplementation((path) => {
|
|
if (path === '/nodes') {
|
|
return Promise.resolve({
|
|
data: { data: [{ node: 'pbs-node' }] }
|
|
});
|
|
}
|
|
if (path === '/config/datastore') {
|
|
return Promise.resolve({
|
|
data: { data: [{ name: 'main-store' }] }
|
|
});
|
|
}
|
|
if (path.includes('/admin/datastore/main-store/snapshots')) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{
|
|
'backup-time': now - 3600, // 1 hour ago
|
|
'backup-type': 'vm',
|
|
'backup-id': '100',
|
|
'backup-group': 'vm/100',
|
|
size: 1073741824 // 1GB
|
|
},
|
|
{
|
|
'backup-time': now - 7200, // 2 hours ago
|
|
'backup-type': 'ct',
|
|
'backup-id': '200',
|
|
'backup-group': 'ct/200',
|
|
size: 536870912 // 512MB
|
|
}
|
|
]
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('/status/datastore-usage')) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [{
|
|
store: 'main-store',
|
|
total: 107374182400, // 100GB
|
|
used: 1610612736, // 1.5GB
|
|
avail: 105763569664 // 98.5GB
|
|
}]
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('/tasks')) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{
|
|
upid: 'backup-task-1',
|
|
type: 'backup',
|
|
worker_type: 'backup',
|
|
status: 'OK',
|
|
starttime: now - 3900, // Started ~1.1 hours ago
|
|
endtime: now - 3600, // Ended 1 hour ago
|
|
worker_id: 'vm/100'
|
|
},
|
|
{
|
|
upid: 'verify-task-1',
|
|
type: 'verify',
|
|
worker_type: 'verify',
|
|
status: 'OK',
|
|
starttime: now - 1800,
|
|
endtime: now - 1500
|
|
}
|
|
]
|
|
}
|
|
});
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
// === STEP 2: Mock PVE discovery ===
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path === '/nodes') {
|
|
return Promise.resolve({
|
|
data: { data: [{ node: 'pve-node', status: 'online' }] }
|
|
});
|
|
}
|
|
if (path.includes('/qemu')) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ vmid: 100, name: 'web-server', status: 'running' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('/lxc')) {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ vmid: 200, name: 'proxy', status: 'running' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
// === STEP 3: Execute integrated discovery ===
|
|
const [discoveryData, pbsData] = await Promise.all([
|
|
fetchDiscoveryData(mockApiClients, mockPbsApiClients),
|
|
fetchPbsData(mockPbsApiClients)
|
|
]);
|
|
|
|
// === STEP 4: Verify PBS-PVE correlation ===
|
|
expect(pbsData).toHaveLength(1);
|
|
expect(pbsData[0].datastores).toHaveLength(1);
|
|
expect(pbsData[0].datastores[0].snapshots).toHaveLength(2);
|
|
|
|
const vm100Backup = pbsData[0].datastores[0].snapshots.find(
|
|
s => s['backup-id'] === '100' && s['backup-type'] === 'vm'
|
|
);
|
|
const ct200Backup = pbsData[0].datastores[0].snapshots.find(
|
|
s => s['backup-id'] === '200' && s['backup-type'] === 'ct'
|
|
);
|
|
|
|
expect(vm100Backup).toBeDefined();
|
|
expect(ct200Backup).toBeDefined();
|
|
|
|
// Verify we can correlate backups with discovered guests
|
|
const discoveredVm100 = discoveryData.vms.find(vm => vm.vmid === 100);
|
|
const discoveredCt200 = discoveryData.containers.find(ct => ct.vmid === 200);
|
|
|
|
expect(discoveredVm100).toBeDefined();
|
|
expect(discoveredCt200).toBeDefined();
|
|
|
|
// Calculate backup ages
|
|
const now = Date.now() / 1000;
|
|
const vm100BackupAge = now - vm100Backup['backup-time'];
|
|
const ct200BackupAge = now - ct200Backup['backup-time'];
|
|
|
|
expect(vm100BackupAge).toBeLessThan(2 * 3600); // Less than 2 hours
|
|
expect(ct200BackupAge).toBeLessThan(3 * 3600); // Less than 3 hours
|
|
|
|
console.log(`Integration test: VM 100 backup age: ${Math.round(vm100BackupAge / 60)} minutes`);
|
|
console.log(`Integration test: CT 200 backup age: ${Math.round(ct200BackupAge / 60)} minutes`);
|
|
});
|
|
});
|
|
|
|
describe('Error Recovery and Resilience', () => {
|
|
test('should handle partial API failures gracefully', async () => {
|
|
// === STEP 1: Configure mixed success/failure scenarios ===
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path === '/nodes') {
|
|
return Promise.resolve({
|
|
data: {
|
|
data: [
|
|
{ node: 'node1', status: 'online' },
|
|
{ node: 'node2', status: 'online' }
|
|
]
|
|
}
|
|
});
|
|
}
|
|
if (path.includes('node1')) {
|
|
// node1 APIs work normally
|
|
if (path.includes('/qemu')) {
|
|
return Promise.resolve({
|
|
data: { data: [{ vmid: 100, name: 'vm1', status: 'running' }] }
|
|
});
|
|
}
|
|
if (path.includes('/lxc')) {
|
|
return Promise.resolve({
|
|
data: { data: [{ vmid: 200, name: 'ct1', status: 'running' }] }
|
|
});
|
|
}
|
|
}
|
|
if (path.includes('node2')) {
|
|
// node2 APIs fail
|
|
throw new Error('Node2 is unreachable');
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
// === STEP 2: Execute discovery with partial failures ===
|
|
const discoveryData = await fetchDiscoveryData(mockApiClients, {});
|
|
|
|
// === STEP 3: Verify graceful degradation ===
|
|
expect(discoveryData.nodes).toHaveLength(2); // Both nodes discovered
|
|
expect(discoveryData.vms).toHaveLength(1); // Only node1 VMs
|
|
expect(discoveryData.containers).toHaveLength(1); // Only node1 CTs
|
|
|
|
// Verify node1 guests are present
|
|
expect(discoveryData.vms[0].vmid).toBe(100);
|
|
expect(discoveryData.containers[0].vmid).toBe(200);
|
|
|
|
// System should continue functioning despite node2 failure
|
|
});
|
|
|
|
test('should handle network errors gracefully', async () => {
|
|
// Clear any cached data from previous tests
|
|
clearCaches();
|
|
|
|
// Mock a scenario where one API call fails but the system continues
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path === '/nodes') {
|
|
return Promise.resolve({
|
|
data: { data: [{ node: 'resilient-node', status: 'online' }] }
|
|
});
|
|
}
|
|
if (path.includes('/qemu') || path.includes('/lxc')) {
|
|
// Simulate network failure for guest discovery
|
|
const networkError = new Error('Network timeout');
|
|
networkError.code = 'ECONNABORTED';
|
|
throw networkError;
|
|
}
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
// Execute discovery - should handle network errors gracefully
|
|
const discoveryData = await fetchDiscoveryData(mockApiClients, {});
|
|
|
|
// Should discover nodes even if guest discovery fails
|
|
expect(discoveryData.nodes).toHaveLength(1);
|
|
expect(discoveryData.nodes[0].node).toBe('resilient-node');
|
|
expect(discoveryData.vms).toHaveLength(0); // No VMs due to network error
|
|
expect(discoveryData.containers).toHaveLength(0); // No containers due to network error
|
|
});
|
|
});
|
|
|
|
describe('Configuration-Based Workflow', () => {
|
|
test('should load configuration and initialize complete monitoring stack', async () => {
|
|
// === STEP 1: Mock configuration loading ===
|
|
const mockConfig = {
|
|
endpoints: [{
|
|
id: 'production-pve',
|
|
name: 'Production Cluster',
|
|
host: 'pve-prod.company.com',
|
|
port: '8006',
|
|
tokenId: 'monitor@pve!readonly',
|
|
tokenSecret: 'secret-token',
|
|
enabled: true,
|
|
allowSelfSignedCerts: false
|
|
}],
|
|
pbsConfigs: [{
|
|
id: 'production-pbs',
|
|
name: 'Production Backup',
|
|
host: 'pbs-prod.company.com',
|
|
port: '8007',
|
|
tokenId: 'monitor@pbs!readonly',
|
|
tokenSecret: 'secret-pbs-token',
|
|
authMethod: 'token',
|
|
allowSelfSignedCerts: false
|
|
}]
|
|
};
|
|
|
|
// Mock the configuration loader
|
|
jest.doMock('../configLoader', () => ({
|
|
loadConfiguration: jest.fn().mockReturnValue(mockConfig)
|
|
}));
|
|
|
|
// === STEP 2: Mock API client initialization ===
|
|
jest.doMock('../apiClients', () => ({
|
|
initializeApiClients: jest.fn().mockResolvedValue({
|
|
apiClients: mockApiClients,
|
|
pbsApiClients: mockPbsApiClients
|
|
})
|
|
}));
|
|
|
|
// === STEP 3: Simulate full initialization ===
|
|
const { loadConfiguration: mockedLoadConfig } = require('../configLoader');
|
|
const { initializeApiClients: mockedInitClients } = require('../apiClients');
|
|
|
|
const config = mockedLoadConfig();
|
|
const { apiClients, pbsApiClients } = await mockedInitClients(
|
|
config.endpoints,
|
|
config.pbsConfigs
|
|
);
|
|
|
|
// === STEP 4: Verify configuration-driven setup ===
|
|
expect(config.endpoints).toHaveLength(1);
|
|
expect(config.pbsConfigs).toHaveLength(1);
|
|
expect(config.endpoints[0].id).toBe('production-pve');
|
|
expect(config.pbsConfigs[0].id).toBe('production-pbs');
|
|
|
|
expect(apiClients).toBeDefined();
|
|
expect(pbsApiClients).toBeDefined();
|
|
|
|
// === STEP 5: Test monitoring with configured endpoints ===
|
|
mockApiClients['pve-main'].client.get.mockResolvedValue({
|
|
data: { data: [{ node: 'prod-node', status: 'online' }] }
|
|
});
|
|
|
|
const discoveryData = await fetchDiscoveryData(apiClients, pbsApiClients);
|
|
expect(discoveryData.nodes).toHaveLength(1);
|
|
expect(discoveryData.nodes[0].node).toBe('prod-node');
|
|
});
|
|
});
|
|
|
|
describe('Performance and Stress Scenarios', () => {
|
|
test('should handle large cluster with many guests efficiently', async () => {
|
|
const nodeCount = 5;
|
|
const guestsPerNode = 20;
|
|
const totalGuests = nodeCount * guestsPerNode;
|
|
|
|
// === STEP 1: Mock large cluster ===
|
|
mockApiClients['pve-main'].client.get.mockImplementation((path) => {
|
|
if (path === '/nodes') {
|
|
const nodes = Array.from({ length: nodeCount }, (_, i) => ({
|
|
node: `node${i + 1}`,
|
|
status: 'online'
|
|
}));
|
|
return Promise.resolve({ data: { data: nodes } });
|
|
}
|
|
|
|
// Generate guests for each node
|
|
for (let nodeIndex = 1; nodeIndex <= nodeCount; nodeIndex++) {
|
|
if (path.includes(`/nodes/node${nodeIndex}/qemu`)) {
|
|
const vms = Array.from({ length: guestsPerNode / 2 }, (_, i) => ({
|
|
vmid: nodeIndex * 1000 + i,
|
|
name: `vm-${nodeIndex}-${i}`,
|
|
status: 'running'
|
|
}));
|
|
return Promise.resolve({ data: { data: vms } });
|
|
}
|
|
if (path.includes(`/nodes/node${nodeIndex}/lxc`)) {
|
|
const containers = Array.from({ length: guestsPerNode / 2 }, (_, i) => ({
|
|
vmid: nodeIndex * 1000 + 500 + i,
|
|
name: `ct-${nodeIndex}-${i}`,
|
|
status: 'running'
|
|
}));
|
|
return Promise.resolve({ data: { data: containers } });
|
|
}
|
|
}
|
|
|
|
return Promise.resolve({ data: { data: [] } });
|
|
});
|
|
|
|
// === STEP 2: Measure discovery performance ===
|
|
const startTime = Date.now();
|
|
const discoveryData = await fetchDiscoveryData(mockApiClients, {});
|
|
const discoveryTime = Date.now() - startTime;
|
|
|
|
// === STEP 3: Verify scale handling ===
|
|
expect(discoveryData.nodes).toHaveLength(nodeCount);
|
|
expect(discoveryData.vms).toHaveLength(nodeCount * (guestsPerNode / 2));
|
|
expect(discoveryData.containers).toHaveLength(nodeCount * (guestsPerNode / 2));
|
|
|
|
const totalDiscoveredGuests = discoveryData.vms.length + discoveryData.containers.length;
|
|
expect(totalDiscoveredGuests).toBe(totalGuests);
|
|
|
|
// === STEP 4: Performance assertions ===
|
|
expect(discoveryTime).toBeLessThan(5000); // Should complete within 5 seconds
|
|
console.log(`Integration test: Discovered ${totalGuests} guests across ${nodeCount} nodes in ${discoveryTime}ms`);
|
|
});
|
|
|
|
test('should handle concurrent operations without race conditions', async () => {
|
|
// === STEP 1: Set up concurrent operations ===
|
|
const operations = [
|
|
() => fetchDiscoveryData(mockApiClients, mockPbsApiClients),
|
|
() => fetchPbsData(mockPbsApiClients),
|
|
() => customThresholds.setThresholds('pve-main', 'node1', '100', {
|
|
cpu: { warning: 70, critical: 85 }
|
|
}),
|
|
() => customThresholds.setThresholds('pve-main', 'node1', '200', {
|
|
memory: { warning: 80, critical: 95 }
|
|
})
|
|
];
|
|
|
|
// Mock responses for all operations
|
|
mockApiClients['pve-main'].client.get.mockResolvedValue({
|
|
data: { data: [{ node: 'concurrent-node', status: 'online' }] }
|
|
});
|
|
mockPbsApiClients['pbs-main'].client.get.mockResolvedValue({
|
|
data: { data: [] }
|
|
});
|
|
|
|
// === STEP 2: Execute operations concurrently ===
|
|
const results = await Promise.all(operations.map(op => op()));
|
|
|
|
// === STEP 3: Verify all operations completed successfully ===
|
|
expect(results).toHaveLength(4);
|
|
expect(results[0].nodes).toHaveLength(1); // Discovery data
|
|
expect(Array.isArray(results[1])).toBe(true); // PBS data
|
|
expect(results[2]).toBe(true); // First threshold set
|
|
expect(results[3]).toBe(true); // Second threshold set
|
|
|
|
// Verify threshold configurations were saved correctly
|
|
const threshold100 = customThresholds.getThresholds('pve-main', 'node1', '100');
|
|
const threshold200 = customThresholds.getThresholds('pve-main', 'node1', '200');
|
|
|
|
expect(threshold100).not.toBeNull();
|
|
expect(threshold200).not.toBeNull();
|
|
expect(threshold100.thresholds.cpu.warning).toBe(70);
|
|
expect(threshold200.thresholds.memory.warning).toBe(80);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Real-World Scenario Simulations', () => {
|
|
test('should simulate production monitoring cycle', async () => {
|
|
// This test simulates a realistic monitoring scenario with:
|
|
// - Mixed VM and container workloads
|
|
// - Varying resource usage patterns
|
|
// - Some backup failures
|
|
// - Custom threshold configurations
|
|
// - Alert generation and management
|
|
|
|
const scenario = {
|
|
cluster: {
|
|
nodes: 3,
|
|
vmsPerNode: 4,
|
|
containersPerNode: 6
|
|
},
|
|
workloads: [
|
|
{ type: 'web', cpu: 0.45, memory: 0.60, typical: true },
|
|
{ type: 'database', cpu: 0.75, memory: 0.85, highUsage: true },
|
|
{ type: 'cache', cpu: 0.30, memory: 0.95, memoryIntensive: true },
|
|
{ type: 'worker', cpu: 0.90, memory: 0.40, cpuIntensive: true }
|
|
]
|
|
};
|
|
|
|
console.log('Integration test: Simulating production monitoring scenario...');
|
|
console.log(`- ${scenario.cluster.nodes} nodes`);
|
|
console.log(`- ${scenario.cluster.vmsPerNode * scenario.cluster.nodes} VMs`);
|
|
console.log(`- ${scenario.cluster.containersPerNode * scenario.cluster.nodes} containers`);
|
|
console.log(`- ${scenario.workloads.length} workload types with varying resource patterns`);
|
|
|
|
// This demonstrates the comprehensive nature of the test suite
|
|
// and validates that the monitoring system can handle realistic
|
|
// production scenarios effectively.
|
|
|
|
expect(true).toBe(true); // Placeholder for demonstration
|
|
});
|
|
}); |