Skip to content

Commit

Permalink
metrics: install python3-pcp instead of cockpit-pcp
Browse files Browse the repository at this point in the history
The python PCP bridge implementation is now the default in Cockpit and
also supports the beiboot scenario as we can install python3-pcp (as it
does not pull in any cockpit* package).
  • Loading branch information
jelly committed Sep 30, 2024
1 parent 46dfd5b commit e6d0ca9
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 46 deletions.
3 changes: 3 additions & 0 deletions pkg/metrics/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"redis_package": {
"fedora": "valkey",
"platform:el10": "valkey"
},
"python_pcp": {
"arch": "pcp"
}
}
}
33 changes: 22 additions & 11 deletions pkg/metrics/metrics.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,11 @@ function make_rows(rows, rowProps, columnLabels) {
);
}

async function get_pcp_packagelist() {
const os_release = await read_os_release();
return get_manifest_config_matchlist("metrics", "python_pcp", "python3-pcp", [os_release.PLATFORM_ID, os_release.ID]);
}

class CurrentMetrics extends React.Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -1381,8 +1386,11 @@ const PCPConfigDialog = ({
const handleInstall = async () => {
// when enabling services, install missing packages on demand
const missing = [];
if (dialogLoggerValue && !s_pmlogger.exists)
missing.push("cockpit-pcp");
let pcp_missing = false;
if (dialogLoggerValue && !s_pmlogger.exists) {
missing.push(await get_pcp_packagelist());
pcp_missing = true;
}
const redisExists = () => s_redis.exists || s_redis_server.exists || s_valkey.exists;
if (dialogProxyValue && !redisExists()) {
const os_release = await read_os_release();
Expand All @@ -1395,7 +1403,7 @@ const PCPConfigDialog = ({
Dialogs.close();
await install_dialog(missing);
debug("PCPConfig: package installation successful");
if (missing.indexOf("cockpit-pcp") >= 0)
if (pcp_missing)
setNeedsLogout(true);
await wait_cond(() => (s_pmlogger.exists &&
(!dialogProxyValue || (s_pmproxy.exists && redisExists()))),
Expand Down Expand Up @@ -1595,6 +1603,7 @@ class MetricsHistory extends React.Component {
selectedDate: null,
packagekitExists: false,
isBeibootBridge: false,
isPythonPCPInstalled: false,
selectedVisibility: this.columns.reduce((a, v) => ({ ...a, [v[0]]: true }), {})
};

Expand Down Expand Up @@ -1686,8 +1695,8 @@ class MetricsHistory extends React.Component {
}, () => this.load_data(sel, sel === this.today_midnight ? undefined : 24 * SAMPLES_PER_H, true));
}

handleInstall() {
install_dialog("cockpit-pcp")
async handleInstall() {
install_dialog(await get_pcp_packagelist())
.then(() => this.props.setNeedsLogout(true))
.catch(() => null); // ignore cancel
}
Expand Down Expand Up @@ -1785,10 +1794,15 @@ class MetricsHistory extends React.Component {

metrics.addEventListener("close", (event, message) => {
if (message.problem) {
let isPythonPCPInstalled = true;
if (message.message == "python3-pcp not installed") {
isPythonPCPInstalled = false;
}
debug("could not load metrics:", message.problem);
this.setState({
loading: false,
metricsAvailable: false,
isPythonPCPInstalled,
});
} else {
debug("loaded metrics for timestamp", timeformat.dateTime(load_timestamp), "new hours", JSON.stringify(Array.from(new_hours)));
Expand Down Expand Up @@ -1824,14 +1838,11 @@ class MetricsHistory extends React.Component {

// on a single machine, cockpit-pcp depends on pcp; but this may not be the case in the beiboot scenario,
// so additionally check if pcp is available on the logged in target machine
if ((cockpit.manifests && !cockpit.manifests.pcp) || this.pmlogger_service.exists === false)
if (!this.state.isPythonPCPInstalled || this.pmlogger_service.exists === false)
return <EmptyStatePanel
icon={ExclamationCircleIcon}
title={_("Package cockpit-pcp is missing for metrics history")}
action={this.state.isBeibootBridge === true
// See https://github.com/cockpit-project/cockpit/issues/19143
? <Text>{ _("Installation not supported without installed cockpit package") }</Text>
: this.state.packagekitExists && <Button onClick={this.handleInstall}>{_("Install cockpit-pcp")}</Button>}
title={_("Python PCP module is missing for metrics history")}
action={this.state.packagekitExists && <Button onClick={this.handleInstall}>{_("Install Python PCP module")}</Button>}
/>;

if (!this.state.metricsAvailable) {
Expand Down
5 changes: 0 additions & 5 deletions pkg/pcp/manifest.json

This file was deleted.

37 changes: 19 additions & 18 deletions test/verify/check-client
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ class TestClient(testlib.MachineCase):
self.m_target.execute("hostnamectl set-hostname target")
# validate on-demand install: this does not work on arch, non-split package
if self.m_target.image.startswith("debian") or self.m_target.image.startswith("ubuntu"):
self.m_target.execute("dpkg --purge cockpit-pcp-dbgsym || true; dpkg --purge cockpit-pcp pcp")
self.m_target.execute("""
if dpkg -l python3-pcp; then
dpkg --purge pcp python3-pcp
else
dpkg --purge pcp
fi
""")
elif self.m_target.image != 'arch':
# TODO: remove conditional when all images have python3-pcp and a Python PCP bridge
self.m_target.execute("""
if rpm -q python3-pcp; then
rpm --erase --verbose cockpit-pcp pcp python3-pcp
rpm --erase --verbose pcp python3-pcp
else
rpm --erase --verbose cockpit-pcp pcp
rpm --erase --verbose pcp
fi
systemctl daemon-reload
""")
Expand Down Expand Up @@ -101,12 +107,12 @@ exec "$@"
def testBeibootNoBridge(self):
# set up target machine: no cockpit
self.m_target.execute("rm /usr/bin/cockpit-bridge; rm -r /usr/share/cockpit")
self.checkLoginScenarios(local_bridge=False)
self.checkLoginScenarios()

def testBeibootWithBridge(self):
self.checkLoginScenarios(local_bridge=True)
self.checkLoginScenarios()

def checkLoginScenarios(self, *, local_bridge=True):
def checkLoginScenarios(self):
self.m_client.spawn(f"runuser -u admin -- {self.libexecdir}/cockpit-ws --no-tls", "ws.log")
self.m_client.wait_for_cockpit_running()

Expand Down Expand Up @@ -143,21 +149,16 @@ exec "$@"
self.check_login("admin@target")
b.become_superuser()

# cockpit-pcp is installed on client, but not on target; recognize that
# python3-pcp is installed on client, but not on target; recognize that
if self.m_target.image != 'arch':
b.go("/metrics")
b.enter_page("/metrics")
b.wait_in_text(".pf-v5-c-empty-state", "cockpit-pcp is missing")
if local_bridge:
# on-demand install is allowed
b.wait_in_text(".pf-v5-c-empty-state button.pf-m-primary", "Install cockpit-pcp")
b.click(".pf-v5-c-empty-state button.pf-m-primary")
b.wait_in_text(".pf-v5-c-modal-box", "cockpit-pcp will be installed")
b.click(".pf-v5-c-modal-box button.cancel")
else:
# not currently supported in beiboot scenario
b.wait_in_text(".pf-v5-c-empty-state", "Installation not supported without installed cockpit package")
b.wait_not_present(".pf-v5-c-empty-state button")
b.wait_in_text(".pf-v5-c-empty-state", "Python PCP module is missing")
# on-demand install is allowed
b.wait_in_text(".pf-v5-c-empty-state button.pf-m-primary", "Install Python PCP module")
b.click(".pf-v5-c-empty-state button.pf-m-primary")
b.wait_in_text(".pf-v5-c-modal-box", "python3-pcp will be installed")
b.click(".pf-v5-c-modal-box button.cancel")

b.drop_superuser()
self.logout()
Expand Down
29 changes: 17 additions & 12 deletions test/verify/check-metrics
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,11 @@ class TestMetricsPackages(packagelib.PackageCase):
b = self.browser
m = self.machine

# Packaging python in the correct directory generates some noisy *pyc*
# files on at least Fedora, for testing purposes extend PYTHONPATH and
# install to /usr/local/lib in the fake python3-pcp package.
self.write_file("/etc/environment", "PYTHONPATH=/usr/local/lib")

redis_service = redisService(m.image)
redis_package = "valkey" if redis_service == "valkey" else "redis"
extra_packages = ""
Expand All @@ -1270,7 +1275,7 @@ class TestMetricsPackages(packagelib.PackageCase):

if m.ostree_image:
self.login_and_go("/metrics")
b.wait_in_text(".pf-v5-c-empty-state", "cockpit-pcp is missing")
b.wait_in_text(".pf-v5-c-empty-state", "Python PCP module is missing")
b.wait_not_present(".pf-v5-c-empty-state button.pf-m-primary")

b.click("#metrics-header-section button.pf-m-secondary")
Expand All @@ -1285,9 +1290,9 @@ class TestMetricsPackages(packagelib.PackageCase):
# TODO: remove conditional when all images have python3-pcp and a Python PCP bridge
m.execute("""
if dpkg -l python3-pcp; then
dpkg --purge cockpit-pcp-dbgsym || true; dpkg --purge python3-pcp cockpit-pcp pcp redis redis-server
dpkg --purge python3-pcp pcp redis redis-server
else
dpkg --purge cockpit-pcp-dbgsym || true; dpkg --purge cockpit-pcp pcp redis redis-server
dpkg --purge pcp redis redis-server
fi
""")
# HACK: pcp does not clean up correctly on Debian https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=986074
Expand All @@ -1296,17 +1301,18 @@ class TestMetricsPackages(packagelib.PackageCase):
# TODO: remove conditional when all images have python3-pcp and a Python PCP bridge
m.execute(f"""
if rpm -q python3-pcp; then
rpm --erase --verbose cockpit-pcp pcp python3-pcp {redis_package} {extra_packages}
rpm --erase --verbose pcp python3-pcp {redis_package} {extra_packages}
else
rpm --erase --verbose cockpit-pcp pcp {redis_package} {extra_packages}
rpm --erase --verbose pcp {redis_package} {extra_packages}
fi
""")

dummy_service = "[Service]\nExecStart=/bin/sleep infinity\n[Install]\nWantedBy=multi-user.target\n"

# Fake it enough for cockpit.channels.pcp to import succesfully
cpcp_content = {
"/usr/share/cockpit/pcp/manifest.json": '{"requires": {"cockpit": "135"}, "bridges": [{"match": { "payload": "metrics1"},"spawn": [ "/usr/libexec/cockpit-pcp" ]}]}',
"/usr/libexec/cockpit-pcp": "true",
"/usr/local/lib/pcp.py": "pmapi = True",
"/usr/local/lib/cpmapi.py": "",
}
pcp_content = {
"/lib/systemd/system/pmlogger.service": dummy_service,
Expand All @@ -1316,16 +1322,15 @@ class TestMetricsPackages(packagelib.PackageCase):
f"/lib/systemd/system/{redis_service}.service": dummy_service,
}

self.createPackage("cockpit-pcp", "999", "1", content=cpcp_content, depends="pcp",
postinst="chmod +x /usr/libexec/cockpit-pcp")
self.createPackage("python3-pcp", "999", "1", content=cpcp_content, depends="pcp")
self.createPackage("pcp", "999", "1", content=pcp_content, postinst="systemctl daemon-reload")
self.createPackage(redis_package, "999", "1", content=redis_content, postinst="systemctl daemon-reload")
self.enableRepo()
m.execute("pkcon refresh")

# install c-pcp from the empty state
self.login_and_go("/metrics")
b.wait_in_text(".pf-v5-c-empty-state", "cockpit-pcp is missing")
b.wait_in_text(".pf-v5-c-empty-state", "Python PCP module is missing")
b.click(".pf-v5-c-empty-state button.pf-m-primary")
b.click("#dialog button:contains('Install')")
b.wait_not_present("#dialog")
Expand All @@ -1340,7 +1345,7 @@ class TestMetricsPackages(packagelib.PackageCase):
b.logout()

# install c-pcp from the Metrics Settings dialog
m.execute("pkcon remove -y cockpit-pcp pcp")
m.execute("pkcon remove -y python3-pcp pcp")
self.login_and_go("/metrics")
b.click("#metrics-header-section button.pf-m-secondary")
b.wait_visible(self.pcp_dialog_selector)
Expand All @@ -1363,7 +1368,7 @@ class TestMetricsPackages(packagelib.PackageCase):
b.set_val("#login-password-input", "foobar")
b.click('#login-button')
b.enter_page("/metrics")
# this is just a fake cockpit-pcp package
# this is just a fake python3-pcp package
b.wait_in_text(".pf-v5-c-empty-state", "Metrics history could not be loaded")
b.wait_in_text(".pf-v5-c-empty-state", "pmlogger.service is failing to collect data")

Expand Down

0 comments on commit e6d0ca9

Please sign in to comment.