Skip to content

Commit

Permalink
test: add test for containers
Browse files Browse the repository at this point in the history
  • Loading branch information
Siumauricio committed Jan 1, 2025
1 parent 7f891b1 commit fe3dde4
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 54 deletions.
178 changes: 178 additions & 0 deletions apps/monitoring/__test__/containers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { describe, it, expect, beforeEach } from "vitest";
import {
shouldMonitorContainer,
getContainerConfig,
} from "../src/containers/config";

describe("Container monitoring", () => {
beforeEach(() => {
// Reset process.env before each test
process.env.CONTAINER_MONITORING_CONFIG = "";
});

describe("shouldMonitorContainer", () => {
it("should match exact container names", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [{ appName: "dokploy-postgres", maxFileSizeMB: 15 }],
excludeServices: [],
});

expect(
shouldMonitorContainer("dokploy-postgres.1.2rfdhwsjhm82wai9hm9dp4sqn"),
).toBe(true);
expect(shouldMonitorContainer("testing-postgres-123")).toBe(false);
});

it("should handle multiple postgres instances correctly", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [{ appName: "postgres", maxFileSizeMB: 15 }],
excludeServices: [],
});

// Este test demuestra el bug actual:
// Al usar 'postgres' como appName, coincide con cualquier contenedor que tenga 'postgres' en su nombre
expect(
shouldMonitorContainer("dokploy-postgres.1.2rfdhwsjhm82wai9hm9dp4sqn"),
).toBe(false); // debería ser false
expect(
shouldMonitorContainer("testing-plausible-9cc9fd-plausible_db-1"),
).toBe(false);
expect(
shouldMonitorContainer(
"testing-fdghdfgh-pukqqe.1.31mn5ve9qs2xmdadahwtl659r",
),
).toBe(false);
});

it("should handle compose project monitoring", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [
{ appName: "testing-plausible-9cc9fd", maxFileSizeMB: 15 },
],
excludeServices: [],
});

// Debería coincidir con todos los contenedores del proyecto compose
expect(
shouldMonitorContainer("testing-plausible-9cc9fd-plausible-1"),
).toBe(true);
expect(
shouldMonitorContainer("testing-plausible-9cc9fd-plausible_db-1"),
).toBe(true);
expect(
shouldMonitorContainer(
"testing-plausible-9cc9fd-plausible_events_db-1",
),
).toBe(true);
expect(
shouldMonitorContainer("dokploy-postgres.1.2rfdhwsjhm82wai9hm9dp4sqn"),
).toBe(false);
});

it("should handle service replicas", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [
{ appName: "testing-testing-1cosrk", maxFileSizeMB: 15 },
],
excludeServices: [],
});

// Ambas réplicas deberían coincidir
expect(
shouldMonitorContainer(
"testing-testing-1cosrk.1.klqzvggx7382en2itijsvd199",
),
).toBe(true);
expect(
shouldMonitorContainer(
"testing-testing-1cosrk.2.piajmzb7v7uclfdgz8lar4wbw",
),
).toBe(true);
});

it("should handle docker swarm services", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [{ appName: "dokploy-traefik", maxFileSizeMB: 15 }],
excludeServices: [],
});

expect(
shouldMonitorContainer("dokploy-traefik.1.u3uplxcs58rjiv4s4ty7gfidz"),
).toBe(true);
expect(shouldMonitorContainer("other-traefik.1.xyz")).toBe(false);
});

it("should handle specific service monitoring", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [
{ appName: "testing-plausible-9cc9fd", maxFileSizeMB: 15 },
],
excludeServices: [],
});

// Debería coincidir con todos los contenedores del servicio plausible
expect(
shouldMonitorContainer("testing-plausible-9cc9fd-plausible-1"),
).toBe(true);
expect(
shouldMonitorContainer("testing-plausible-9cc9fd-plausible_db-1"),
).toBe(true);
expect(
shouldMonitorContainer(
"testing-plausible-9cc9fd-plausible_events_db-1",
),
).toBe(true);
expect(
shouldMonitorContainer("dokploy-postgres.1.2rfdhwsjhm82wai9hm9dp4sqn"),
).toBe(false);
});

it("should handle docker compose service replicas", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [
{ appName: "testing-testing-1cosrk", maxFileSizeMB: 15 },
],
excludeServices: [],
});

// Ambas réplicas deberían coincidir
expect(
shouldMonitorContainer(
"testing-testing-1cosrk.1.klqzvggx7382en2itijsvd199",
),
).toBe(true);
expect(
shouldMonitorContainer(
"testing-testing-1cosrk.2.piajmzb7v7uclfdgz8lar4wbw",
),
).toBe(true);
});
});

describe("getContainerConfig", () => {
it("should return correct config for matched container", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [{ appName: "dokploy-postgres", maxFileSizeMB: 15 }],
excludeServices: [],
});

const config = getContainerConfig(
"dokploy-postgres.1.2rfdhwsjhm82wai9hm9dp4sqn",
);
expect(config).toEqual({
appName: "dokploy-postgres",
maxFileSizeMB: 15,
});
});

it("should return default config for non-matched container", () => {
process.env.CONTAINER_MONITORING_CONFIG = JSON.stringify({
includeServices: [{ appName: "dokploy-postgres", maxFileSizeMB: 15 }],
excludeServices: [],
});

const config = getContainerConfig("some-other-container");
expect(config).toEqual({ appName: "*", maxFileSizeMB: 10 });
});
});
});
12 changes: 10 additions & 2 deletions apps/monitoring/__test__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,16 @@ invalid json line
{"timestamp": "2024-12-28T00:01:00Z", "value": 2}`;

const result = parseLog(logContent);
expect(result).toHaveLength(3);
expect(result[1]).toEqual({ raw: "invalid json line" });
expect(result).toHaveLength(2);

expect(result[0]).toEqual({
timestamp: "2024-12-28T00:00:00Z",
value: 1,
});
expect(result[1]).toEqual({
timestamp: "2024-12-28T00:01:00Z",
value: 2,
});
});

it("should handle empty log content", () => {
Expand Down
67 changes: 26 additions & 41 deletions apps/monitoring/src/containers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ export const loadConfig = (): MonitoringConfig => {
try {
const configJson = process.env.CONTAINER_MONITORING_CONFIG;

console.log(configJson);

if (!configJson) {
return DEFAULT_CONFIG;
}
Expand All @@ -35,54 +33,41 @@ export const loadConfig = (): MonitoringConfig => {
};

export const getServiceName = (containerName: string): string => {
const match = containerName.match(
/^([\w-]+(?:_[\w-]+)*)(?:\.\d+\.[a-z0-9]+)?$/,
);
return match ? match[1] : containerName;
// Para contenedores de Docker Swarm (ej: dokploy-postgres.1.2rfdhwsjhm82wai9hm9dp4sqn)
const swarmMatch = containerName.match(/^([^.]+)(?:\.\d+\.[a-z0-9]+)?$/);
if (swarmMatch) return swarmMatch[1];

return containerName;
};

export const shouldMonitorContainer = (
containerName: string,
config: MonitoringConfig,
): boolean => {
const { includeServices, excludeServices } = config;
export const shouldMonitorContainer = (containerName: string): boolean => {
const config = loadConfig();
const { includeServices } = config;
const serviceName = getServiceName(containerName);

// If specifically included, always monitor
if (includeServices.some((service) => service.appName === serviceName)) {
return true;
}
// Verificar si el nombre del servicio coincide exactamente con alguno configurado
return includeServices.some((service) => {
// Si el appName es exactamente igual al nombre del servicio
if (service.appName === serviceName) return true;

// If there's a wildcard in includeServices and not specifically excluded
if (
includeServices.some((service) => service.appName === "*") &&
!excludeServices.includes(serviceName)
) {
return true;
}
// Si el servicio comienza con el appName
if (serviceName.startsWith(service.appName)) return true;

// In any other case, don't monitor
return false;
return false;
});
};

export const getContainerConfig = (
containerName: string,
config: MonitoringConfig,
): ServiceConfig => {
const serviceName = getServiceName(containerName);
export const getContainerConfig = (containerName: string): ServiceConfig => {
const config = loadConfig();
const { includeServices } = config;
const serviceName = getServiceName(containerName);

// If it has specific configuration, use it
const specificConfig = includeServices.find(
(service) => service.appName === serviceName,
);
if (specificConfig) {
return specificConfig;
}
// Buscar la configuración que coincida con el servicio
const specificConfig = includeServices.find((service) => {
if (service.appName === serviceName) return true;
if (serviceName.startsWith(service.appName)) return true;
return false;
});

// If not, use default configuration (wildcard)
const wildcardConfig = includeServices.find(
(service) => service.appName === "*",
);
return wildcardConfig || { appName: "*", maxFileSizeMB: 10 };
return specificConfig || { appName: "*", maxFileSizeMB: 10 };
};
21 changes: 10 additions & 11 deletions apps/monitoring/src/containers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import console from "node:console";
import { containerLogFile } from "../constants.js";
import type { Container } from "./types.js";
import {
loadConfig,
shouldMonitorContainer,
getContainerConfig,
getServiceName,
Expand All @@ -15,9 +14,6 @@ import { processContainerData } from "./utils.js";

export const execAsync = util.promisify(exec);

const config = loadConfig();

console.log(config);
const REFRESH_RATE_CONTAINER = Number(
process.env.CONTAINER_REFRESH_RATE || 10000,
);
Expand All @@ -43,23 +39,26 @@ export const logContainerMetrics = () => {
return;
}

const seenServices = new Set<string>();
const filteredContainer = containers.filter((container) => {
const shouldMonitor = shouldMonitorContainer(container.Name, config);
console.log(
`Service ${getServiceName(container.Name)} (${container.Name}): ${shouldMonitor ? "monitored" : "filtered out"}`,
);
return shouldMonitor;
if (!shouldMonitorContainer(container.Name)) return false;

const serviceName = getServiceName(container.Name);
if (seenServices.has(serviceName)) return false;

seenServices.add(serviceName);
return true;
});

console.log(`Monitoring ${filteredContainer.length} containers`);
console.log(`Writing metrics for ${filteredContainer.length} containers`);

for (const container of filteredContainer) {
try {
const serviceName = getServiceName(container.Name);
const containerPath = join(containerLogFile, `${serviceName}.log`);
const processedData = processContainerData(container);
const logLine = `${JSON.stringify(processedData)}\n`;
const containerConfig = getContainerConfig(container.Name, config);
const containerConfig = getContainerConfig(container.Name);

const { maxFileSizeMB = 10 } = containerConfig;

Expand Down

0 comments on commit fe3dde4

Please sign in to comment.