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 authored and KKoukiou committed Nov 10, 2021
1 parent ff12f8c commit 09624ef
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 142 deletions.
4 changes: 1 addition & 3 deletions src/components/vm/nics/nicEdit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,8 @@ 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,
macAddress: network.mac,
newMacAddress: this.state.networkMac,
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, macAddress: network.mac, state: network.state === 'up' ? 'down' : 'up', hotplug: vm.state === "running" })
.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
86 changes: 31 additions & 55 deletions src/libvirtApi/domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ import {
updateBootOrder,
updateDisk,
updateMaxMemory,
updateNetworkIface,
} from '../libvirt-xml-update.js';
import { storagePoolRefresh } from './storagePool.js';
import { snapshotGetAll } from './snapshot.js';
Expand Down Expand Up @@ -166,8 +165,7 @@ export function domainAttachIface({ connectionName, vmName, mac, permanent, hotp
}

export function domainChangeInterfaceSettings({
name,
id: objPath,
vmName,
connectionName,
hotplug,
persistent,
Expand All @@ -176,46 +174,39 @@ 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 }));
const options = { err: "message" };
if (connectionName === "system")
options.superuser = "try";

let networkParams = "";
if (state) {
networkParams = `link.state=${state}`;
} 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' });
}
});
if (newMacAddress)
networkParams += `mac=${newMacAddress},`;
if (networkType)
networkParams += `type=${networkType},`;
if (networkSource)
networkParams += `source=${networkSource},`;
if (networkModel)
networkParams += `model=${networkModel},`;
}

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 @@ -243,21 +234,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 09624ef

Please sign in to comment.