Skip to content

Commit

Permalink
kdump: add automation script support
Browse files Browse the repository at this point in the history
Extend the kdump page to allow administrators to configure kdump via
Cockpit and export an Ansible playbook using the linux-system-roles
kdump role.
  • Loading branch information
jelly committed Nov 24, 2023
1 parent 75143b3 commit 8f7b958
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 24 deletions.
2 changes: 1 addition & 1 deletion pkg/kdump/config-client-suse.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export class ConfigFileSUSE extends ConfigFile {

/* generate the config file from raw text and settings
*/
_generateConfig(settings) {
generateConfig(settings) {
settings = this._persistSettings(settings);

const lines = this._lines.slice(0);
Expand Down
6 changes: 4 additions & 2 deletions pkg/kdump/config-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ export class ConfigFile {
this.settings._internal.core_collector.value &&
(this.settings._internal.core_collector.value.split(" ").indexOf("-c") != -1)
);

this.settings.core_collector = this.settings._internal?.core_collector?.value ?? defaultCoreCollector;
}

/* update single _internal setting to given value
Expand Down Expand Up @@ -302,7 +304,7 @@ export class ConfigFile {

/* generate the config file from raw text and settings
*/
_generateConfig(settings) {
generateConfig(settings) {
settings = this._persistSettings(settings);

const lines = this._lines.slice(0);
Expand Down Expand Up @@ -350,7 +352,7 @@ export class ConfigFile {
write(settings) {
return this._fileHandle.modify((oldContent) => {
this._parseText(oldContent, true);
return this._generateConfig(settings);
return this.generateConfig(settings);
});
}
}
4 changes: 4 additions & 0 deletions pkg/kdump/kdump-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ export class KdumpClient {
});
}

exportConfig(settings) {
return this.configClient.generateConfig(settings).trim();
}

targetFromSettings(settings) {
const target = {
type: "unknown",
Expand Down
108 changes: 107 additions & 1 deletion pkg/kdump/kdump-view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.
import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox/index.js";
import { Card, CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText/index.js";
import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Form, FormGroup, FormSection } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect/index.js";
import { Page, PageSection, PageSectionVariants } from "@patternfly/react-core/dist/esm/components/Page/index.js";
Expand All @@ -41,15 +41,78 @@ import { Tooltip, TooltipPosition } from "@patternfly/react-core/dist/esm/compon
import { OutlinedQuestionCircleIcon } from "@patternfly/react-icons";

import { useDialogs, DialogsContext } from "dialogs.jsx";
import { read_os_release } from "os-release.js";
import { fmt_to_fragments } from 'utils.jsx';
import { show_modal_dialog } from "cockpit-components-dialog.jsx";
import { FormHelper } from "cockpit-components-form-helper";
import { ModalError } from 'cockpit-components-inline-notification.jsx';
import { PrivilegedButton } from "cockpit-components-privileged.jsx";
import { ModificationsExportDialog } from "cockpit-components-modifications.jsx";

const _ = cockpit.gettext;
const DEFAULT_KDUMP_PATH = "/var/crash";

const exportAnsibleTask = (settings, os_release) => {
const target = Object.keys(settings.targets)[0];
const targetSettings = settings.targets[target];
const kdump_core_collector = settings.core_collector;

let role_name = "linux-system-roles";
if (os_release.NAME === "RHEL" || os_release.ID_LIKE?.includes('rhel')) {
role_name = "rhel-system-roles";
}

let ansible = `
---
# Also available via https://galaxy.ansible.com/ui/standalone/roles/linux-system-roles/kdump/
- name: install ${role_name}
package:
name: ${role_name}
state: present
delegate_to: 127.0.0.1
become: true
- name: run kdump system role
include_role:
name: ${role_name}.kdump
vars:
kdump_path: ${targetSettings.path || DEFAULT_KDUMP_PATH}
kdump_core_collector: ${kdump_core_collector}`;

if (target === "ssh") {
// HACK: we should not have to specify kdump_ssh_user and kdump_ssh_user as it is in kdump_target.location
// https://github.com/linux-system-roles/kdump/issues/184
let ssh_user;
let ssh_server;
const parts = targetSettings.server.split('@');
if (parts.length === 1) {
ssh_user = "root";
ssh_server = parts[0];
} else if (parts.length === 2) {
ssh_user = parts[0];
ssh_server = parts[1];
} else {
throw new Error("ssh server contains two @ symbols");
}
ansible += `
kdump_target:
type: ssh
kdump_sshkey: ${targetSettings.sshkey}
kdump_ssh_server: ${ssh_server}
kdump_ssh_user: ${ssh_user}`;
} else if (target === "nfs") {
ansible += `
kdump_target:
type: nfs
location: ${targetSettings.server}:${targetSettings.export}
`;
} else if (target !== "local") {
// target is unsupported
throw new Error("Unsupported kdump target"); // not-covered: assertion
}

return ansible;
};

const KdumpSettingsModal = ({ settings, initialTarget, handleSave }) => {
const Dialogs = useDialogs();
const compressionAllowed = settings.compression?.allowed;
Expand Down Expand Up @@ -262,8 +325,12 @@ export class KdumpPage extends React.Component {

constructor(props) {
super(props);
this.state = { os_release: null };

this.handleTestSettingsClick = this.handleTestSettingsClick.bind(this);
this.handleSettingsClick = this.handleSettingsClick.bind(this);
this.handleAutomationClick = this.handleAutomationClick.bind(this);
read_os_release().then(os_release => this.setState({ os_release }));
}

handleTestSettingsClick() {
Expand Down Expand Up @@ -325,6 +392,33 @@ export class KdumpPage extends React.Component {
handleSave={this.props.onSaveSettings} />);
}

handleAutomationClick() {
const Dialogs = this.context;
let enableCrashKernel = '';
let kdumpconf = this.props.exportConfig(this.props.kdumpStatus.config);
kdumpconf = kdumpconf.replaceAll('$', '\\$');
if (this.state.os_release.NAME?.includes('Fedora')) {
enableCrashKernel = `
# A reboot will be required if crashkernel was not set before
kdumpctl reset-crashkernel`;
}
const shell = `
cat > /etc/kdump.conf << EOF
${kdumpconf}
EOF
systemctl enable --now kdump.service
${enableCrashKernel}
`;

Dialogs.show(
<ModificationsExportDialog
ansible={exportAnsibleTask(this.props.kdumpStatus.config, this.state.os_release)}
shell={shell}
show
onClose={Dialogs.close}
/>);
}

render() {
let kdumpLocation = (
<div className="dialog-wait-ct">
Expand Down Expand Up @@ -461,6 +555,17 @@ export class KdumpPage extends React.Component {
}
const tooltip_info = _("This will test the kdump configuration by crashing the kernel.");

let automationButton = null;
if (this.props.kdumpStatus && this.props.kdumpStatus.config !== null && this.state.os_release !== null && targetCanChange) {
automationButton = (
<FlexItem align={{ md: 'alignRight' }}>
<Button id="kdump-automation-script" variant="secondary" onClick={this.handleAutomationClick}>
{_("View automation script")}
</Button>
</FlexItem>
);
}

let kdumpSwitch = (<Switch isChecked={!!serviceRunning}
onChange={this.props.onSetServiceState}
aria-label={_("kdump status")}
Expand All @@ -482,6 +587,7 @@ export class KdumpPage extends React.Component {
<HelperText>
<HelperTextItem variant="indeterminate">{serviceRunning ? _("Enabled") : _("Disabled")}</HelperTextItem>
</HelperText>
{automationButton}
</Flex>
</PageSection>
<PageSection>
Expand Down
4 changes: 4 additions & 0 deletions pkg/kdump/kdump.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const initStore = function(rootElement) {
dataStore.kdumpClient.validateSettings(settings)
.then(() => dataStore.kdumpClient.writeSettings(settings));

dataStore.exportConfig = settings =>
dataStore.kdumpClient.exportConfig(settings);

// whether we're actively trying to change the state
dataStore.stateChanging = false;
function setServiceState(_event, desiredState) {
Expand All @@ -71,6 +74,7 @@ const initStore = function(rootElement) {
kdumpCmdlineEnabled: dataStore.crashkernel || false,
onSaveSettings: dataStore.saveSettings,
onCrashKernel: dataStore.kdumpClient.crashKernel,
exportConfig: dataStore.exportConfig,
})}</WithDialogs>);
};
dataStore.render = render;
Expand Down
Loading

0 comments on commit 8f7b958

Please sign in to comment.