Skip to content

Commit

Permalink
Introduce virt-xml into NIC update API
Browse files Browse the repository at this point in the history
  • Loading branch information
skobyda committed Oct 15, 2021
1 parent e8a78dc commit 79830af
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 143 deletions.
3 changes: 1 addition & 2 deletions src/components/vm/nics/nicEdit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ export class EditNICModal extends React.Component {
const { vm, network } = this.props;

domainChangeInterfaceSettings({
name: vm.name,
id: vm.id,
vmName: vm.name,
connectionName: vm.connectionName,
hotplug: vm.state === 'running',
persistent: vm.persistent,
Expand Down
4 changes: 2 additions & 2 deletions src/components/vm/nics/vmNicsCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import AddNIC from './nicAdd.jsx';
import { EditNICModal } from './nicEdit.jsx';
import WarningInactive from '../../common/warningInactive.jsx';
import './nic.css';
import { domainChangeInterfaceState, domainDetachIface, domainInterfaceAddresses, domainGet } from '../../../libvirtApi/domain.js';
import { domainChangeInterfaceSettings, domainDetachIface, domainInterfaceAddresses, domainGet } from '../../../libvirtApi/domain.js';
import { ListingTable } from "cockpit-components-table.jsx";
import { DeleteResourceButton, DeleteResourceModal } from '../../common/deleteResource.jsx';

Expand Down Expand Up @@ -210,7 +210,7 @@ export class VmNetworkTab extends React.Component {
return (e) => {
e.stopPropagation();
if (network.mac) {
domainChangeInterfaceState({ name: vm.name, id: vm.id, connectionName: vm.connectionName, networkMac: network.mac, state: network.state === 'up' ? 'down' : 'up' })
domainChangeInterfaceSettings({ vmName: vm.name, connectionName: vm.connectionName, networkMac: network.mac, state: network.state === 'up' ? 'down' : 'up' })
.then(() => domainGet({ connectionName: vm.connectionName, id:vm.id, name: vm.name }))
.catch(ex => {
onAddErrorNotification({
Expand Down
83 changes: 1 addition & 82 deletions src/libvirt-xml-update.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getDoc, getSingleOptionalElem } from './libvirt-xml-parse.js';
import { getNextAvailableTarget, logDebug } from './helpers.js';
import { getNextAvailableTarget } from './helpers.js';

export function updateDisk({ domXml, diskTarget, readonly, shareable, busType, existingTargets, cache }) {
const s = new XMLSerializer();
Expand Down Expand Up @@ -245,87 +245,6 @@ export function updateBootOrder(domXml, devices) {
return s.serializeToString(doc);
}

/**
* Returns updated XML description of the network interface specified by mac address.
* @param {String} domXml Domain XML description.
* @param {String} macAddress MAC Address of the network interface we will update.
* @param {String} state Desired state; one of up/down.
* @return {String} Updated XML description of the device we will update or null on error.
*/
export function updateNetworkIface({ domXml, macAddress, newMacAddress, networkState, networkModelType, networkType, networkSource }) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(domXml, "application/xml");

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

const domainElem = xmlDoc.getElementsByTagName("domain")[0];
const devicesElem = domainElem.getElementsByTagName("devices")[0];
const interfaceElems = devicesElem.getElementsByTagName('interface');

if (interfaceElems) {
for (let i = 0; i < interfaceElems.length; i++) {
const interfaceElem = interfaceElems[i];
const macElem = getSingleOptionalElem(interfaceElem, 'mac');
if (macElem === undefined)
return null;
const mac = macElem.getAttribute('address');

if (mac !== macAddress)
continue;

if (networkState) {
let linkElem = getSingleOptionalElem(interfaceElem, 'link');
if (linkElem === undefined) {
linkElem = xmlDoc.createElement('link');
interfaceElem.appendChild(linkElem);
}
linkElem.setAttribute('state', networkState);
}

const typeChanged = networkType !== interfaceElem.getAttribute('type', networkType);
if (networkType) {
interfaceElem.setAttribute('type', networkType);
}

if (networkSource && networkType) {
let sourceElem = getSingleOptionalElem(interfaceElem, 'source');
// Source elements of different iface types might contain differently named attributes,
// so we delete whole element and create a new one
if (typeChanged && sourceElem) {
sourceElem.remove();
sourceElem = undefined;
}
if (!sourceElem) {
sourceElem = xmlDoc.createElement("source");
interfaceElem.appendChild(sourceElem);
}
if (networkType === 'network')
sourceElem.setAttribute('network', networkSource);
else if (networkType === 'direct')
sourceElem.setAttribute('dev', networkSource);
else if (networkType === 'bridge')
sourceElem.setAttribute('bridge', networkSource);
}

if (networkModelType) {
const modelElem = getSingleOptionalElem(interfaceElem, 'model');
modelElem.setAttribute('type', networkModelType);
}

const returnXML = (new XMLSerializer()).serializeToString(interfaceElem);

logDebug(`updateNetworkIface: Updated XML: "${returnXML}"`);

return returnXML;
}
}
console.warn("Can't update network interface element in domXml");
return null;
}

/*
* This function is used to define only offline attribute of memory.
*/
Expand Down
81 changes: 24 additions & 57 deletions src/libvirtApi/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,13 @@ import {
import {
getDiskElemByTarget,
getDoc,
getElem,
getSingleOptionalElem,
parseDumpxml,
} from '../libvirt-xml-parse.js';
import {
updateBootOrder,
updateDisk,
updateMaxMemory,
updateNetworkIface,
} from '../libvirt-xml-update.js';
import { storagePoolRefresh } from './storagePool.js';
import { snapshotGetAll } from './snapshot.js';
Expand Down Expand Up @@ -165,8 +163,7 @@ export function domainAttachIface({ connectionName, vmName, mac, permanent, hotp
}

export function domainChangeInterfaceSettings({
name,
id: objPath,
vmName,
connectionName,
hotplug,
persistent,
Expand All @@ -175,46 +172,31 @@ export function domainChangeInterfaceSettings({
networkType,
networkSource,
networkModel,
state,
}) {
/*
* 0 -> VIR_DOMAIN_AFFECT_CURRENT
* 1 -> VIR_DOMAIN_AFFECT_LIVE
* 2 -> VIR_DOMAIN_AFFECT_CONFIG
*/
let flags = Enum.VIR_DOMAIN_AFFECT_CURRENT;
flags |= Enum.VIR_DOMAIN_AFFECT_CONFIG;

if (newMacAddress && newMacAddress !== macAddress) {
return domainAttachIface({
connectionName,
hotplug,
vmName: name,
mac: newMacAddress,
permanent: persistent,
sourceType: networkType,
source: networkSource,
model: networkModel
})
.then(() => domainDetachIface({ connectionName, mac: macAddress, vmName: name, live: hotplug, persistent }));
} else {
// Error handling inside the modal dialog this function is called
return call(connectionName, objPath, 'org.libvirt.Domain', 'GetXMLDesc', [Enum.VIR_DOMAIN_XML_INACTIVE], { timeout, type: 'u' })
.then(domXml => {
const updatedXml = updateNetworkIface({
domXml: domXml[0],
macAddress,
newMacAddress,
networkType,
networkSource,
networkModelType: networkModel
});
if (!updatedXml) {
return Promise.reject(new Error("VM CHANGE_NETWORK_SETTINGS action failed: updated device XML couldn't not be generated"));
} else {
return call(connectionName, objPath, 'org.libvirt.Domain', 'UpdateDevice', [updatedXml, flags], { timeout, type: 'su' });
}
});
const options = { err: "message" };
if (connectionName === "system")
options.superuser = "try";

let networkParams;
if (newMacAddress && networkType && networkSource && networkModel)
networkParams = `mac=${newMacAddress},type=${networkType},source=${networkSource},model=${networkModel}`;
else if (state)
networkParams = `link.state=${state}`;

const args = [
"virt-xml", "-c", `qemu:///${connectionName}`,
vmName, "--edit", `mac=${macAddress}`, "--network",
networkParams
];

if (hotplug) {
args.push("--update");
if (!persistent)
args.push("--no-define");
}

return cockpit.spawn(args, options);
}

export function domainChangeAutostart ({
Expand Down Expand Up @@ -242,21 +224,6 @@ export function domainChangeBootOrder({
});
}

export function domainChangeInterfaceState({
connectionName,
id: objPath,
name,
networkMac,
state,
}) {
return call(connectionName, objPath, 'org.libvirt.Domain', 'GetXMLDesc', [0], { timeout, type: 'u' })
.then(domXml => {
const updatedXml = updateNetworkIface({ domXml: domXml[0], macAddress: networkMac, networkState: state });
// updateNetworkIface can fail but we 'll catch the exception from the API call itself that will error on null argument
return call(connectionName, objPath, 'org.libvirt.Domain', 'UpdateDevice', [updatedXml, Enum.VIR_DOMAIN_AFFECT_CURRENT], { timeout, type: 'su' });
});
}

export function domainCreate({
connectionName,
memorySize,
Expand Down

0 comments on commit 79830af

Please sign in to comment.