Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 .github/workflows/fb-e2e-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ jobs:
pmm_client_version: ${{ inputs.pmm_client_version || 'latest-tarball' }}
launchable_confidence: ${{ inputs.launchable_confidence || '100%' }}
pmm_qa_branch: ${{ github.event_name == 'pull_request' && github.head_ref || (inputs.pmm_qa_branch || 'main') }}
setup_services: '--database psmdb'
setup_services: '--database psmdb --database ps=8.4'
pmm_test_flag: '@rta'
workers: 1

51 changes: 51 additions & 0 deletions cli/tests/perconaMySqlServer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,56 @@ test.describe('PMM Client CLI tests for Percona Server Database', { tag: '@perco
`Expected pmm-admin to honor --connection-timeout=5s, got ${output.durationMs.toFixed(0)} ms`,
).toBeGreaterThan(5_000);
});

test('Verify adding, changing and removing MySQL RTA Agent in pmm-admin CLI', async ({ }) => {
test.skip(adminVersion < 9, 'MySQL RTA is available from pmm-client version 3.9.0');
Comment thread
theTibi marked this conversation as resolved.

const serviceName = 'rta_mysql_service';

await test.step('add MySQL service for the RTA agent', async () => {
const output = await cli.exec(`docker exec ${containerName} pmm-admin add mysql --query-source=perfschema --username=${MYSQL_USER} --password=${MYSQL_PASSWORD} ${serviceName} ${ipPort}`);
await output.assertSuccess();
await output.outContains('MySQL Service added.');
});

const serviceId = (await cli.exec(`docker exec ${containerName} pmm-admin list | grep "${serviceName}" | awk -F" " '{print $4}'`)).getStdOutLines()[0];
const pmmAgentId = (await cli.exec(`docker exec ${containerName} pmm-admin list | grep pmm_agent | awk -F" " '{print $3}'`)).getStdOutLines()[0];

await test.step('add MySQL RTA agent and verify it reaches Running', async () => {
const output = await cli.exec(`docker exec ${containerName} pmm-admin inventory add agent rta-mysql-agent ${pmmAgentId} ${serviceId} ${MYSQL_USER} --password=${MYSQL_PASSWORD}`);
await output.assertSuccess();
await output.outContains('Real-Time Analytics MySQL agent added.');
Comment thread
coderabbitai[bot] marked this conversation as resolved.

await expect(async () => {
const pmmAdminListOutput = await cli.exec(`docker exec ${containerName} pmm-admin list`);

await pmmAdminListOutput.outContains('rta_mysql_agent Running');
}).toPass({ intervals: [1_000], timeout: 60_000 });

await expect(async () => {
const pmmAdminListOutput = await cli.exec(`docker exec ${containerName} pmm-admin inventory list agents --service-id=${serviceId}`);

await pmmAdminListOutput.outContains('rta_mysql_agent Running');
}).toPass({ intervals: [1_000], timeout: 60_000 });
});

const rtaAgentId = (await cli.exec(`docker exec ${containerName} pmm-admin list | grep rta_mysql_agent | awk -F" " '{print $3}'`)).getStdOutLines()[0];

await test.step('change MySQL RTA agent log level and collect interval', async () => {
const output = await cli.exec(`docker exec ${containerName} pmm-admin inventory change agent rta-mysql-agent ${rtaAgentId} --log-level=debug --collect-interval=5s`);
await output.assertSuccess();
await output.outContains('Real-Time Analytics MySQL agent configuration updated.');
await output.outContains('changed log level to debug');
await output.outContains('changed collect interval to 5s');
});

await test.step('remove MySQL RTA agent and service', async () => {
const output = await cli.exec(`docker exec ${containerName} pmm-admin inventory remove agent ${rtaAgentId}`);
await output.assertSuccess();
await output.outContains('Agent removed.');

await removeMySQLService(containerName, serviceName);
});
});
});

16 changes: 16 additions & 0 deletions e2e_tests/fixtures/pmmTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import QueryAnalytics from '@pages/qan/queryAnalytics.page';
import RealTimeAnalyticsPage from '@pages/qan/rta/realTimeAnalytics.page';
import NodesPage from '@pages/inventory/nodes.page';
import MongoDBHelper from '@helpers/mongodb.helper';
import MySQLHelper from '@helpers/mysql.helper';
import VacuumDashboard from '@pages/dashboards/postgresql/vacuumDashboard';
import apiEndpoints from '@helpers/apiEndpoints';
import SettingsPage from '@pages/ha/settings.page';
Expand All @@ -31,6 +32,7 @@ const pmmTest = base.extend<{
dashboard: Dashboard;
grafanaHelper: GrafanaHelper;
mongoDbHelper: MongoDBHelper;
mySqlDbHelper: MySQLHelper;
api: Api;
qanStoredMetrics: QanStoredMetrics;
urlHelper: UrlHelper;
Expand Down Expand Up @@ -122,6 +124,20 @@ const pmmTest = base.extend<{

await use(mongoDbHelper);
},
mySqlDbHelper: async ({}, use) => {
// Defaults match the QA framework ps setup (host port 3317 -> sandbox 3307,
// dbdeployer msandbox credentials); override via env for local environments.
const mySqlDbHelper = new MySQLHelper({
database: process.env.MYSQL_DATABASE || 'test',
host: process.env.MYSQL_HOST || '127.0.0.1',
password: process.env.MYSQL_PASSWORD || 'msandbox',
port: Number(process.env.MYSQL_PORT) || 3_317,
username: process.env.MYSQL_USER || 'msandbox',
});

await use(mySqlDbHelper);
await mySqlDbHelper.close();
},
nodesPage: async ({ page }, use) => await use(new NodesPage(page)),
portalRemoval: async ({ page }, use) => {
const portalRemoval = new PortalRemoval(page);
Expand Down
84 changes: 84 additions & 0 deletions e2e_tests/helpers/mysql.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import mysql, { Connection, Pool } from 'mysql2/promise';
import { Timeouts } from './timeouts';

interface MySqlConfig {
host?: string;
port: number;
username: string;
password: string;
database?: string;
}

export default class MySQLHelper {
private closed = false;
private config: mysql.PoolOptions;
// Connections running intentionally long queries; destroyed on close() so
// fixture teardown does not wait for SLEEP() statements to finish.
private longRunningConnections: Connection[] = [];
private pool: Pool;

constructor(config: MySqlConfig) {
this.config = {
connectTimeout: Timeouts.THIRTY_SECONDS,
database: config.database,
host: config.host || '127.0.0.1',
password: config.password,
port: config.port,
user: config.username,
};
this.pool = mysql.createPool(this.config);
}

close = async () => {
this.closed = true;

// destroy() drops the socket immediately instead of draining, so teardown
// is not blocked by long-running SLEEP() queries.
for (const connection of this.longRunningConnections) {
connection.destroy();
}

this.longRunningConnections = [];

await this.pool.end();
};

runQuery = async (sql: string) => await this.pool.query(sql);

/**
* Simulates a long-running query visible in sys.x$processlist (and therefore in RTA).
* SLEEP() keeps the statement in the processlist for the whole delay while consuming
* nothing; the label is embedded as a string literal so the RTA query text can be
* filtered by it (comments could be stripped by intermediaries, literals cannot).
*
* Runs on a dedicated connection that close() destroys, so tests do not have to
* wait for the full delay. Errors caused by that teardown are suppressed; anything
* else is rethrown so a query that could not run still fails the test.
*
* @param options.queryLabel - string selected by the query; use it to find this query in RTA
* @returns Resolves when the statement finishes or the helper is closed
*/
simulateLongRunningQuery = async (
options: {
delayMs?: number;
queryLabel?: string;
} = {},
) => {
const { delayMs = Timeouts.TEN_SECONDS, queryLabel = 'rta-simulated-query' } = options;
const escapedLabel = queryLabel.replace(/\\/g, '\\\\').replace(/'/g, "''");
const delaySeconds = Math.max(1, Math.ceil(delayMs / 1_000));
const connection = await mysql.createConnection(this.config);

this.longRunningConnections.push(connection);

try {
return await connection.query(`SELECT '${escapedLabel}', SLEEP(${delaySeconds})`);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} catch (error) {
if (this.closed) {
return null;
}

throw error;
}
};
}
Loading
Loading