Setting Static IP Using Libvirt
As part of the work on my pet project, catapult, I was looking for a way to set a static IP address for a VM managed by libvirt. This VM serves me as the testing host which I use for development. Having the same IP always provides a couple of benefits:
- Using the same API parameters whenever I test
- I can easily script the deployment of the test host for other to use
- Will be super useful when I finally start writing the system tests
Googling around I came across the solution of simply adding the following element to the network definition:
<host mac='52:54:00:8a:7b:9d' name='fc_host' ip='192.168.122.45'/>
However, this requires recreating the network. Since I wanted the starting of my test VM to be scripted, editing the network configuration using virsh
seemed quite tedious. So instead, I resorted to using the Python SDK, and came up with the following code (my Python skill is quite lacking, so it is not very good, but it does the job):
#!/bin/python
import libvirt
import sys
from xml.etree import ElementTree as ET
def should_update(section, mac, ip):
for node in section.findall(".//host"):
if node.attrib["mac"] == mac and node.attrib["ip"] == ip:
return False
return True
def find_dhcp(root):
dhcp_section = root.find("./ip/dhcp")
return dhcp_section
def update_network(dom_name, root, mac, ip, dhcp_section):
host = ET.Element("host", mac=mac, name=dom_name, ip=ip)
dhcp_section.append(host)
return ET.tostring(root).decode()
conn = libvirt.open("qemu:///system")
dom_name = sys.argv[1]
try:
dom = conn.lookupByName(dom_name)
except:
sys.exit(-1)
ip_address = sys.argv[3]
mac_address = sys.argv[4]
network = conn.networkLookupByName(sys.argv[2])
network_xml = network.XMLDesc(0)
xml_root = ET.fromstring(network_xml)
dhcp_section = find_dhcp(xml_root)
if not should_update(dhcp_section, mac_address, ip_address):
print("IP address is already configured")
sys.exit(0)
updated_network = update_network(
dom_name,
xml_root,
mac_address,
ip_address,
dhcp_section)
if network.isActive():
network.destroy()
network = conn.networkDefineXML(updated_network)
network.setAutostart(True)
network.create()
So this code doesn't do much other than check the DHCP configuration of the network, then injects the XML element to the network definition
<host mac='52:54:00:8a:7b:9d' name='fc_host' ip='192.168.122.45'/>
Then it destroys and starts the network if needed.
This is not to pleasant, but it mostly works fine. However, I revisited the issue again, for reasons I cannot recall. And found an old reply in the libvirt mailing list, where the net-update command was pointed out:
virsh net-update default add-last ip-dhcp-host \
"<host mac='52:54:00:8a:7b:9d' name='fc_host' ip='192.168.122.45'/>" \
--live --config
Which I find very convenient, since I can simply do what I wanted without restarting the network and I can throw away the Python script, and my test VM definition script will now look like this:
#!/bin/bash
echo "Defining VM..."
VM_NAME=${1:-"fchost"}
LIBVIRT_NETWORK=${2:-"default"}
VM_IP=${3:-"192.168.122.45"}
if virsh list --all | grep -q "${VM_NAME}"; then
echo "${VM_NAME} is already installed... "
else
dom=$(virt-install --import --name "${VM_NAME}" \
--memory 1024 --vcpus 1 --cpu host \
--disk os.img,bus=virtio \
--os-type=linux \
--graphics spice \
--noautoconsole \
--network=default,model=virtio \
--connect qemu:///system \
--print-xml)
echo $dom | virsh define /dev/stdin
fi
fc_host_status=$(virsh list | grep fc_host | tr -s \"[:blank:]\" | cut -d ' ' -f4)
if [ "${fc_hoststatus}" == 'running' ]; then
echo "${VM_NAME} is already running"
exit 0
fi
mac_address=$(virsh dumpxml "${VM_NAME}" | grep "mac address" | awk -F\' '{ print $2}')
echo "Setting IP address to ${VM_IP} for MAC address ${MAC_ADDRESS}"
xml_entry="<host mac=\"${mac_address}\" name=\"${VM_NAME}\" ip=\"${VM_IP}\"/>"
# TODO: check both IP and MAC, since this wouldn't work if we use a new VM after previously defining one
if virsh net-dumpxml "${LIBVIRT_NETWORK}" | grep -q "${VM_NAME}"; then
echo "IP address is already configured"
else
virsh net-update ${LIBVIRT_NETWORK} add-last ip-dhcp-host "${xml_entry}" --live --config
fi
echo "starting ${VM_NAME}..."
virsh start "${VM_NAME}"
If I still wanted to use python to do this, it would be much simpler:
import libvirt
import sys
conn = libvirt.open('qemu:///system')
dom_name = sys.argv[1]
try:
dom = conn.lookupByName(dom_name)
except:
sys.exit(-1)
ip_address = sys.argv[3]
mac_address = sys.argv[4]
network = conn.networkLookupByName(sys.argv[2])
flags = (libvirt.VIR_NETWORK_UPDATE_AFFECT_LIVE |
libvirt.VIR_NETWORK_UPDATE_AFFECT_CONFIG)
entry = "<host mac='{}' name='{}' ip='{}'/>".format(
mac_address, dom_name, ip_address)
network.update(
libvirt.VIR_NETWORK_UPDATE_COMMAND_ADD_LAST,
libvirt.VIR_NETWORK_SECTION_IP_DHCP_HOST,
0,
entry,
flags)
_