Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(e2): rhidp-5439 - [e2e] container "backstage-backend" in pod ... is waiting to start: PodInitializing #2197

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e-tests/playwright/e2e/audit-log/catalog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UIhelper } from "../../utils/ui-helper";
import { LogUtils } from "./log-utils";
import { CatalogImport } from "../../support/pages/catalog-import";

test.describe.skip("Audit Log check for Catalog Plugin", () => {
test.describe("Audit Log check for Catalog Plugin", () => {
let uiHelper: UIhelper;
let common: Common;
let catalogImport: CatalogImport;
Expand Down
215 changes: 152 additions & 63 deletions e2e-tests/playwright/e2e/audit-log/log-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@ import { execFile } from "child_process";
import { Log } from "./logs";

export class LogUtils {
/**
* Executes a command and returns the output as a promise.
*
* @param command The command to execute
* @param args An array of arguments for the command
* @returns A promise that resolves with the command output
*/
static executeCommand(command: string, args: string[] = []): Promise<string> {
return new Promise((resolve, reject) => {
execFile(command, args, { encoding: "utf8" }, (error, stdout, stderr) => {
if (error) {
console.error("Error executing command:", error);
reject(`Error: ${error.message}`);
return;
}
if (stderr) {
console.warn("stderr warning:", stderr);
}
resolve(stdout);
});
});
}

/**
* Validates if the actual log matches the expected log values.
* It compares both primitive and nested object properties.
Expand All @@ -11,7 +34,6 @@ export class LogUtils {
* @param expected The expected log values to validate against
*/
public static validateLog(actual: Log, expected: Partial<Log>) {
// Loop through each key in the expected log object
Object.keys(expected).forEach((key) => {
const expectedValue = expected[key as keyof Log];
const actualValue = actual[key as keyof Log];
Expand Down Expand Up @@ -42,71 +64,116 @@ export class LogUtils {
}

/**
* Executes a command and returns the output as a promise.
* Lists all pods in the specified namespace and returns their details.
*
* @param command The command to execute
* @param args An array of arguments for the command
* @returns A promise that resolves with the command output
* @param namespace The namespace to list pods from
* @returns A promise that resolves with the pod details
*/
static executeCommand(command: string, args: string[] = []): Promise<string> {
return new Promise((resolve, reject) => {
execFile(command, args, { encoding: "utf8" }, (error, stdout, stderr) => {
if (error) {
console.error("Error executing command:", error);
reject(`Error: ${error.message}`);
return;
}
if (stderr) {
console.warn("stderr warning:", stderr);
}
resolve(stdout);
});
});
static async listPods(namespace: string): Promise<string> {
const args = ["get", "pods", "-n", namespace, "-o", "wide"];
try {
console.log("Fetching pod list with command:", "oc", args.join(" "));
return await LogUtils.executeCommand("oc", args);
} catch (error) {
console.error("Error listing pods:", error);
throw new Error(
`Failed to list pods in namespace "${namespace}": ${error}`,
);
}
}

/**
* Fetches the logs from pods that match the fixed pod selector and applies a grep filter.
* The pod selector is:
* - app.kubernetes.io/component=backstage
* - app.kubernetes.io/instance=redhat-developer-hub
* - app.kubernetes.io/name=developer-hub
* Fetches detailed information about a specific pod.
*
* @param podName The name of the pod to fetch details for
* @param namespace The namespace where the pod is located
* @returns A promise that resolves with the pod details in JSON format
*/
static async getPodDetails(
podName: string,
namespace: string,
): Promise<string> {
const args = ["get", "pod", podName, "-n", namespace, "-o", "json"];
try {
const output = await LogUtils.executeCommand("oc", args);
console.log(`Details for pod ${podName}:`, output);
return output;
} catch (error) {
console.error(`Error fetching details for pod ${podName}:`, error);
throw new Error(`Failed to fetch pod details: ${error}`);
}
}

/**
* Fetches logs with retry logic in case the log is not immediately available.
*
* @param filter The string to filter the logs
* @returns A promise that resolves with the filtered logs
* @param maxRetries Maximum number of retry attempts
* @param retryDelay Delay (in milliseconds) between retries
* @returns The log line matching the filter, or throws an error if not found
*/
static async getPodLogsWithGrep(filter: string): Promise<string> {
static async getPodLogsWithRetry(
filter: string,
maxRetries: number = 3,
retryDelay: number = 5000,
): Promise<string> {
const podSelector =
"app.kubernetes.io/component=backstage,app.kubernetes.io/instance=rhdh,app.kubernetes.io/name=backstage";
const tailNumber = 30;
const namespace = process.env.NAME_SPACE || "default";
const namespace = process.env.NAME_SPACE || "showcase-ci-nightly";

const args = [
"logs",
"-l",
podSelector,
`--tail=${tailNumber}`,
"-c",
"backstage-backend",
"-n",
namespace,
];
let attempt = 0;
while (attempt <= maxRetries) {
try {
console.log(
`Attempt ${attempt + 1}/${maxRetries + 1}: Fetching logs...`,
);
const args = [
"logs",
"-l",
podSelector,
`--tail=${tailNumber}`,
"-c",
"backstage-backend",
"-n",
namespace,
];

console.log("Executing command:", "oc", args.join(" "));
console.log("Executing command:", "oc", args.join(" "));
const output = await LogUtils.executeCommand("oc", args);

try {
const output = await LogUtils.executeCommand("oc", args);
console.log("Raw log output:", output);

const logLines = output.split("\n");
const logLines = output.split("\n");
const filteredLines = logLines.filter((line) => line.includes(filter));

const filteredLines = logLines.filter((line) => line.includes(filter));
if (filteredLines.length > 0) {
console.log("Matching log line found:", filteredLines[0]);
return filteredLines[0]; // Return the first matching log
}

const firstMatch = filteredLines[0] || "";
console.warn(
`No matching logs found for filter "${filter}" on attempt ${
attempt + 1
}. Retrying...`,
);
} catch (error) {
console.error(
`Error fetching logs on attempt ${attempt + 1}:`,
error.message,
);
}

return firstMatch;
} catch (error) {
console.error("Error fetching logs:", error);
throw new Error(`Failed to fetch logs: ${error}`);
attempt++;
if (attempt <= maxRetries) {
console.log(`Waiting ${retryDelay / 1000} seconds before retrying...`);
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}

throw new Error(
`Failed to fetch logs for filter "${filter}" after ${maxRetries + 1} attempts.`,
);
}

/**
Expand All @@ -115,8 +182,8 @@ export class LogUtils {
* @returns A promise that resolves when the login is successful
*/
static async loginToOpenShift(): Promise<void> {
const token = process.env.K8S_CLUSTER_TOKEN;
const server = process.env.K8S_CLUSTER_URL;
const token = process.env.K8S_CLUSTER_TOKEN || "";
const server = process.env.K8S_CLUSTER_URL || "";

if (!token || !server) {
throw new Error(
Expand Down Expand Up @@ -155,19 +222,41 @@ export class LogUtils {
baseURL: string,
plugin: string,
) {
const actualLog = await LogUtils.getPodLogsWithGrep(eventName);
const expectedLog: Partial<Log> = {
actor: {
hostname: new URL(baseURL).hostname,
},
message,
plugin,
request: {
method,
url,
},
};
console.log(actualLog);
LogUtils.validateLog(JSON.parse(actualLog), expectedLog);
try {
const actualLog = await LogUtils.getPodLogsWithRetry(eventName);
console.log("Raw log output before filtering:", actualLog);

let parsedLog: Log;
try {
parsedLog = JSON.parse(actualLog);
} catch (parseError) {
console.error("Failed to parse log JSON. Log content:", actualLog);
throw new Error(`Invalid JSON received for log: ${parseError}`);
}

const expectedLog: Partial<Log> = {
actor: {
hostname: new URL(baseURL).hostname,
},
message,
plugin,
request: {
method,
url,
},
};

console.log("Validating log with expected values:", expectedLog);
LogUtils.validateLog(parsedLog, expectedLog);
} catch (error) {
console.error("Error validating log event:", error);
console.error("Event name:", eventName);
console.error("Expected message:", message);
console.error("Expected method:", method);
console.error("Expected URL:", url);
console.error("Base URL:", baseURL);
console.error("Plugin:", plugin);
throw error;
}
}
}
Loading