Skip to content

Commit

Permalink
machines: Detach disk
Browse files Browse the repository at this point in the history
New feature is introduced only for LibvirtDBus provider
Detaching the disk is always done is permanent way and if the
domain is running the live state is affected as well.

Closes #9812
  • Loading branch information
KKoukiou authored and martinpitt committed Sep 27, 2018
1 parent d0c2bcf commit 442264d
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 2 deletions.
5 changes: 5 additions & 0 deletions pkg/machines/actions/provider-actions.es6
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
CREATE_AND_ATTACH_VOLUME,
CREATE_VM,
DELETE_VM,
DETACH_DISK,
ENABLE_LIBVIRT,
FORCEOFF_VM,
FORCEREBOOT_VM,
Expand Down Expand Up @@ -79,6 +80,10 @@ export function deleteVm(vm, options) {
return virt(DELETE_VM, { name: vm.name, id: vm.id, connectionName: vm.connectionName, options: options });
}

export function detachDisk({ connectionName, target, name, id, live = false }) {
return virt(DETACH_DISK, { connectionName, target, name, id, live });
}

export function enableLibvirt(enable, serviceName) {
return virt(ENABLE_LIBVIRT, { enable, serviceName });
}
Expand Down
35 changes: 35 additions & 0 deletions pkg/machines/components/diskRemove.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2018 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react';

import { detachDisk } from '../actions/provider-actions.es6';

const onDetachDisk = (dispatch, vm, target) => {
return () => {
dispatch(detachDisk({ connectionName:vm.connectionName, id:vm.id, name:vm.name, target, live: vm.state == 'running' }));
};
};

const RemoveDiskAction = ({ dispatch, vm, target, idPrefixRow }) => {
return (
<button id={`${idPrefixRow}-detach`} className="btn btn-default btn-control-ct fa fa-minus" onClick={onDetachDisk(dispatch, vm, target)} />
);
};

export default RemoveDiskAction;
15 changes: 14 additions & 1 deletion pkg/machines/components/vmDisksTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import cockpit from 'cockpit';
import { Listing, ListingRow } from 'cockpit-components-listing.jsx';
import { Info } from './notification/inlineNotification.jsx';
import { convertToUnit, toReadableNumber, units } from "../helpers.es6";
import RemoveDiskAction from './diskRemove.jsx';

const _ = cockpit.gettext;

Expand Down Expand Up @@ -54,7 +55,7 @@ const VmDiskCell = ({ value, id }) => {
);
};

const VmDisksTab = ({ idPrefix, disks, actions, renderCapacity, notificationText }) => {
const VmDisksTab = ({ idPrefix, vm, disks, actions, renderCapacity, notificationText, dispatch, provider }) => {
let notification = null;
const columnTitles = [_("Device"), _("Target")];
let renderCapacityUsed, renderReadOnly;
Expand Down Expand Up @@ -106,6 +107,17 @@ const VmDisksTab = ({ idPrefix, disks, actions, renderCapacity, notificationText
}

columns.push(disk.diskSourceCell);

if (provider === 'LibvirtDBus') {
const removeDiskAction = RemoveDiskAction({
dispatch,
vm,
target: disk.target,
idPrefixRow,
});
columns.push(<div>{removeDiskAction}</div>);
}

return (<ListingRow key={idPrefixRow} columns={columns} navigateToItem={disk.onNavigate} />);
})}
</Listing>
Expand All @@ -119,6 +131,7 @@ VmDisksTab.propTypes = {
disks: PropTypes.array.isRequired,
renderCapacity: PropTypes.bool,
notificationText: PropTypes.string,
provider: PropTypes.string,
};

export default VmDisksTab;
5 changes: 4 additions & 1 deletion pkg/machines/components/vmDisksTabLibvirt.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ class VmDisksTabLibvirt extends React.Component {
return (
<VmDisksTab idPrefix={idPrefix}
actions={actions}
vm={vm}
disks={disks}
renderCapacity={areDiskStatsSupported}
notificationText={this.getNotification(vm, areDiskStatsSupported)} />
notificationText={this.getNotification(vm, areDiskStatsSupported)}
dispatch={dispatch}
provider={config.provider.name} />
);
}
}
Expand Down
1 change: 1 addition & 0 deletions pkg/machines/constants/provider-action-types.es6
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const CONSOLE_VM = "CONSOLE_VM";
export const CREATE_AND_ATTACH_VOLUME = "CREATE_AND_ATTACH_VOLUME";
export const CREATE_VM = "CREATE_VM";
export const DELETE_VM = "DELETE_VM";
export const DETACH_DISK = "DETACH_DISK";
export const ENABLE_LIBVIRT = "ENABLE_LIBVIRT";
export const FORCEOFF_VM = "FORCEOFF_VM";
export const FORCEREBOOT_VM = "FORCEREBOOT_VM";
Expand Down
23 changes: 23 additions & 0 deletions pkg/machines/libvirt-common.es6
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@ function getBootableDeviceType(device) {
return type;
}

export function getDiskElemByTarget(domxml, targetOriginal) {
const domainElem = getDomainElem(domxml);

if (!domainElem) {
console.warn(`Can't parse dumpxml, input: "${domainElem}"`);
return;
}

const devicesElem = domainElem.getElementsByTagName('devices')[0];
const diskElems = devicesElem.getElementsByTagName('disk');

if (diskElems) {
for (let i = 0; i < diskElems.length; i++) {
const diskElem = diskElems[i];
const targetElem = diskElem.getElementsByTagName('target')[0];
const target = targetElem.getAttribute('dev'); // identifier of the disk, i.e. sda, hdc
if (target === targetOriginal) {
return new XMLSerializer().serializeToString(diskElem);
}
}
}
}

export function getDomainElem(domXml) {
let parser = new DOMParser();
const xmlDoc = parser.parseFromString(domXml, "application/xml");
Expand Down
36 changes: 36 additions & 0 deletions pkg/machines/libvirt-dbus.es6
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ import {
} from './helpers.es6';

import {
buildFailHandler,
canConsole,
canDelete,
canInstall,
canReset,
canRun,
canSendNMI,
canShutdown,
getDiskElemByTarget,
getSingleOptionalElem,
isRunning,
parseDumpxml,
Expand Down Expand Up @@ -99,6 +101,7 @@ const Enum = {
VIR_DOMAIN_STATS_BALLOON: 4,
VIR_DOMAIN_STATS_VCPU: 8,
VIR_DOMAIN_STATS_BLOCK: 32,
VIR_DOMAIN_XML_INACTIVE: 2,
VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE: 2,
VIR_CONNECT_LIST_STORAGE_POOLS_DIR: 64
};
Expand Down Expand Up @@ -315,6 +318,39 @@ LIBVIRT_DBUS_PROVIDER = {
};
},

DETACH_DISK({
name,
connectionName,
id: vmPath,
target,
live
}) {
let detachFlags = Enum.VIR_DOMAIN_AFFECT_CURRENT;
if (live)
detachFlags |= Enum.VIR_DOMAIN_AFFECT_LIVE;

return dispatch => {
clientLibvirt[connectionName].call(vmPath, 'org.libvirt.Domain', 'GetXMLDesc', [0], TIMEOUT)
.done(domXml => {
let diskXML = getDiskElemByTarget(domXml[0], target);
let getXMLFlags = Enum.VIR_DOMAIN_XML_INACTIVE;

clientLibvirt[connectionName].call(vmPath, 'org.libvirt.Domain', 'GetXMLDesc', [getXMLFlags], TIMEOUT)
.done(domInactiveXml => {
let diskInactiveXML = getDiskElemByTarget(domInactiveXml[0], target);
if (diskInactiveXML)
detachFlags |= Enum.VIR_DOMAIN_AFFECT_CONFIG;

clientLibvirt[connectionName].call(vmPath, 'org.libvirt.Domain', 'DetachDevice', [diskXML, detachFlags], TIMEOUT)
.done(() => { dispatch(getVm({connectionName, id:vmPath})) })
.fail(buildFailHandler({ dispatch, name, connectionName, message: _("VM DETACH action failed") }));
})
.fail(buildFailHandler({ dispatch, name, connectionName, message: _("VM DETACH action failed") }));
})
.fail(buildFailHandler({ dispatch, name, connectionName, message: _("VM DETACH action failed") }));
};
},

FORCEOFF_VM({
name,
connectionName,
Expand Down
51 changes: 51 additions & 0 deletions test/verify/check-machines-dbus
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,56 @@ class TestMachinesDBus(machineslib.TestMachines):
self.provider = "libvirt-dbus"


def testDetachDisk(self):
b = self.browser
m = self.machine

# prepare libvirt storage pools
m.execute("mkdir /mnt/vm_one; chmod a+rwx /mnt/vm_one")
m.execute("virsh pool-create-as myPoolOne --type dir --target /mnt/vm_one")
m.execute("virsh vol-create-as myPoolOne mydiskofpoolone_1 --capacity 1G --format qcow2")
m.execute("virsh vol-create-as myPoolOne mydiskofpoolone_2 --capacity 1G --format qcow2")
wait(lambda: "mydiskofpoolone_1" in m.execute("virsh vol-list myPoolOne"))
wait(lambda: "mydiskofpoolone_2" in m.execute("virsh vol-list myPoolOne"))

self.startVm("subVmTest1")

m.execute("virsh attach-disk --domain subVmTest1 --source /mnt/vm_one/mydiskofpoolone_1 --target vdb --targetbus virtio")
m.execute("virsh attach-disk --domain subVmTest1 --source /mnt/vm_one/mydiskofpoolone_2 --target vdc --targetbus virtio --persistent")

self.login_and_go("/machines")
b.wait_in_text("body", "Virtual Machines")
b.wait_present("tbody tr th")
b.wait_in_text("tbody tr th", "subVmTest1")

# Test detaching non permanent disk of a running domain
b.click("tbody tr th") # click on the row header
b.wait_present("#vm-subVmTest1-state")
b.wait_in_text("#vm-subVmTest1-state", "running")

b.wait_present("#vm-subVmTest1-disks") # wait for the tab
b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

b.wait_present("#vm-subVmTest1-disks-vdb-detach") # button
b.click("#vm-subVmTest1-disks-vdb-detach")
b.wait_not_present("vm-subVmTest1-disks-vdb-target")

# Test that detaching disk of a running domain will affect the
# inactive configuration as well
b.click("#vm-subVmTest1-off-caret")
b.wait_visible("#vm-subVmTest1-forceOff")
b.click("#vm-subVmTest1-forceOff")
b.wait_in_text("#vm-subVmTest1-state", "shut off")
b.wait_not_present("vm-subVmTest1-disks-vdb-target")

# Test detaching permanent disk of a stopped domain
b.wait_present("#vm-subVmTest1-disks") # wait for the tab
b.click("#vm-subVmTest1-disks") # open the "Disks" subtab

b.wait_present("#vm-subVmTest1-disks-vdc-detach") # button
b.click("#vm-subVmTest1-disks-vdc-detach")
b.wait_not_present("vm-subVmTest1-disks-vdc-target")


if __name__ == '__main__':
test_main()

0 comments on commit 442264d

Please sign in to comment.