diff --git a/dev/vagrant/Vagrantfile b/dev/vagrant/Vagrantfile index aa1d2b3428..a7e46533e6 100644 --- a/dev/vagrant/Vagrantfile +++ b/dev/vagrant/Vagrantfile @@ -141,7 +141,7 @@ Vagrant.configure(2) do |config| case PROVIDER when "libvirt" if vm.name - `virsh -c qemu:///system net-dhcp-leases vagrant-private-dhcp | awk -F'[ /]+' '/#{vm.name} / {print $6}'`.chop + `python newest_dhcp_lease.py #{vm.name}`.chop end when "virtualbox_ubuntu" when "virtualbox_centos" diff --git a/dev/vagrant/newest_dhcp_lease.py b/dev/vagrant/newest_dhcp_lease.py new file mode 100644 index 0000000000..1ade75bc4b --- /dev/null +++ b/dev/vagrant/newest_dhcp_lease.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Command-line utility to get the IP address from the newest DHCP lease. + +It's written for using with vagrant-hostmanager and vagrant-libvirt plugins. +Vagrant-hostmanager by default fetches only IP addresses from eth0 interfaces +on VM-s. Therefore, the first purpose of this utility is to be able to fetch +the address also from the other interfaces. + +Libvirt/virsh only lists all DHCP leases for the given network with timestamps. +DHCP leases have their expiration time, but are not cleaned up after destroying +VM. If someone destroys and sets up the VM with the same hostname, we have +many DHCP leases for the same hostname and we have to look up for timestamp. +That's the second purpose of this script. +""" + +import argparse +import csv +import functools +import operator +import xml.etree.ElementTree as etree + +import libvirt + + +class NoBridgeInterfaceException(Exception): + pass + + +class NoDHCPLeaseException(Exception): + pass + + +def libvirt_conn(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + conn = libvirt.openReadOnly('qemu:///system') + return f(conn, *args, **kwargs) + return wrapper + + +@libvirt_conn +def get_vir_network_dhcp_lease(conn, vm_name): + """Libvirt since 1.2.6 version provides DHCPLeases method in virNetwork. + + That's the current official way for getting DHCP leases and this + information isn't stored anywhere else anymore. + """ + network = conn.networkLookupByName('vagrant-private-dhcp') + dhcp_leases = libvirt.virNetwork.DHCPLeases(network) + + vm_dhcp_leases = filter(lambda lease: lease['hostname'] == vm_name, + dhcp_leases) + + newest_vm_dhcp_lease = sorted(vm_dhcp_leases, + key=operator.itemgetter('expirytime'), + reverse=True)[0]['ipaddr'] + return newest_vm_dhcp_lease + + +def get_mac_address(conn, domain_name): + """Get MAC address from domain XML.""" + domain = conn.lookupByName(domain_name) + domain_xml = domain.XMLDesc() + domain_tree = etree.fromstring(domain_xml) + devices = domain_tree.find('devices') + interfaces = devices.iterfind('interface') + + for interface in interfaces: + interface_type = interface.get('type') + if interface_type != 'bridge': + continue + mac_element = interface.find('mac') + mac_address = mac_element.get('address') + return mac_address + + raise NoBridgeInterfaceException() + + +@libvirt_conn +def get_dnsmasq_dhcp_lease(conn, vm_name): + """In libvirt under 1.2.6 DHCP leases are stored in file. + + There is no API for DHCP leases yet. + """ + domain_name = 'vagrant_' + vm_name + mac_address = get_mac_address(conn, domain_name) + + with open( + '/var/lib/libvirt/dnsmasq/vagrant-private-dhcp.leases' + ) as leases_file: + reader = csv.reader(leases_file, delimiter=' ') + for row in reader: + lease_mac, lease_ip, lease_vm_name = row[1:4] + if not (lease_mac == mac_address and lease_vm_name == vm_name): + continue + return lease_ip + + raise NoDHCPLeaseException() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('vm_name', help='Name of the virtual machine') + + args = parser.parse_args() + vm_name = args.vm_name + + if libvirt.getVersion() >= 1002006: + newest_dhcp_lease = get_vir_network_dhcp_lease(vm_name) + else: + newest_dhcp_lease = get_dnsmasq_dhcp_lease(vm_name) + + print(newest_dhcp_lease) + + +if __name__ == '__main__': + main()