[PATCH 01/18] KVM test: Add a new macaddress pool algorithm

Lucas Meneghel Rodrigues
2010-09-14T22:25:26Z

Old method uses the addresses in the config files which could lead serious
problem when multiple tests running in different hosts.
This patch adds a new macaddress pool algorithm, it generates the mac prefix
based on mac address of the host, and fix it to correspond to IEEE802.
When user have set the mac_prefix in the config file, we should use it instead
of the dynamic generated mac prefix.
Add a parameter like 'preserve_mac', to preserve the original mac address, for
things like migration.

MAC addresses are recorded into a dictionary 'address_pool' in following
format: {{'20100310-165222-Wt7l:0' : 'AE:9D:94:6A:9b:f9'},...}
20100310-165222-Wt7l : instance attribute of VM
0 : index of NIC
AE:9D:94:6A:9b:f9 : mac address
Use 'vm instance' + 'nic index' as the key, macaddress is the value.

Changes from v2:
- Instead of basing ourselves in a physical interface address to get
an address prefix, just generate one with the prefix 0x9a (convention)
randomly and add it to the address pool. If there's already one
prefix, keep it there and just return it.
- Made messages more consistent and informative
- Made function names consistent all across the board
- Fixed some single line spacing between functions

Changs from v1:
- Use 'vm instance' + 'nic index' as the key of address_pool, address is value.
- Put 'mac_lock' and 'address_pool' to '/tmp', for sharing them to other
autotest instances running on the same host.
- Change function names for less confusion.
- Do not copy 'vm.instance' in vm.clone()
- Split 'adding get_ifname function' to another patch

Signed-off-by: Jason Wang <jasowang@redhat.com>
Signed-off-by: Feng Yang <fyang@redhat.com>
Signed-off-by: Amos Kong <akong@redhat.com>
---
client/tests/kvm/kvm_utils.py | 114 ++++++++++++++++++++++++++++++++
client/tests/kvm/kvm_vm.py | 83 ++++++++++++++++++++++--
client/tests/kvm/tests_base.cfg.sample | 2 +-
3 files changed, 193 insertions(+), 6 deletions(-)

diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
index fb2d1c2..bb5c868 100644
--- a/client/tests/kvm/kvm_utils.py
+++ b/client/tests/kvm/kvm_utils.py
@@ -5,6 +5,7 @@ KVM test utility functions.
"""

import time, string, random, socket, os, signal, re, logging, commands, cPickle
+import fcntl, shelve
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error, logging_config
import kvm_subprocess
@@ -82,6 +83,119 @@ def get_sub_dict_names(dict, keyword):

# Functions related to MAC/IP addresses

+def _generate_mac_address_prefix():
+ """
+ Generate a MAC address prefix. By convention we will set KVM autotest
+ MAC addresses to start with 0x9a.
+ """
+ l = [0x9a, random.randint(0x00, 0x7f), random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff)]
+ prefix = ':'.join(map(lambda x: "%02x" % x, l)) + ":"
+ return prefix
+
+
+def generate_mac_address_prefix():
+ """
+ Generate a random MAC address prefix and add it to the MAC pool dictionary.
+ If there's a MAC prefix there already, do not update the MAC pool and just
+ return what's in there.
+ """
+ lock_file = open("/tmp/mac_lock", 'w')
+ fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
+ mac_pool = shelve.open("/tmp/address_pool", writeback=False)
+
+ if mac_pool.get('prefix'):
+ prefix = mac_pool.get('prefix')
+ logging.debug('Retrieved previously generated MAC prefix for this '
+ 'host: %s', prefix)
+ else:
+ prefix = _generate_mac_address_prefix()
+ mac_pool['prefix'] = prefix
+ logging.debug('Generated MAC address prefix for this host: %s', prefix)
+
+ mac_pool.close()
+ fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN)
+ lock_file.close()
+
+ return prefix
+
+
+def generate_mac_address(root_dir, instance_vm, nic_index, prefix=None):
+ """
+ Random generate a MAC address and add it to the MAC pool.
+
+ Try to generate macaddress based on the mac address prefix, add it to a
+ dictionary 'address_pool'.
+ key = VM instance + nic index, value = mac address
+ {['20100310-165222-Wt7l:0'] : 'AE:9D:94:6A:9b:f9'}
+
+ @param root_dir: Root dir for kvm.
+ @param instance_vm: Here we use instance of vm.
+ @param nic_index: The index of nic.
+ @param prefix: Prefix of MAC address.
+ @return: MAC address string.
+ """
+ if prefix is None:
+ prefix = generate_mac_address_prefix()
+
+ lock_file = open("/tmp/mac_lock", 'w')
+ fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
+ mac_pool = shelve.open("/tmp/address_pool", writeback=False)
+ found = False
+ key = "%s:%s" % (instance_vm, nic_index)
+
+ if mac_pool.get(key):
+ found = True
+ mac = mac_pool.get(key)
+
+ while not found:
+ suffix = "%02x:%02x" % (random.randint(0x00,0xfe),
+ random.randint(0x00,0xfe))
+ mac = prefix + suffix
+ mac_list = mac.split(":")
+ # Clear multicast bit
+ mac_list[0] = int(mac_list[0],16) & 0xfe
+ # Set local assignment bit (IEEE802)
+ mac_list[0] = mac_list[0] | 0x02
+ mac_list[0] = "%02x" % mac_list[0]
+ mac = ":".join(mac_list)
+ if mac in [mac_pool.get(k) for k in mac_pool.keys()]:
+ continue
+ mac_pool[key] = mac
+ found = True
+ logging.debug("Generated MAC address for NIC %s: %s ", key, mac)
+
+ mac_pool.close()
+ fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN)
+ lock_file.close()
+ return mac
+
+
+def free_mac_address(root_dir, instance_vm, nic_index):
+ """
+ Free mac address from address pool
+
+ @param root_dir: Root dir for kvm
+ @param instance_vm: Here we use instance attribute of vm
+ @param nic_index: The index of nic
+ """
+ lock_file = open("/tmp/mac_lock", 'w')
+ fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
+ mac_pool = shelve.open("/tmp/address_pool", writeback=False)
+ key = "%s:%s" % (instance_vm, nic_index)
+ if not mac_pool or (not key in mac_pool.keys()):
+ logging.debug("NIC not present in the MAC pool, not modifying pool")
+ logging.debug("NIC: %s" % key)
+ logging.debug("Contents of MAC pool: %s" % mac_pool)
+ else:
+ logging.debug("Freeing MAC addr for NIC %s: %s", key, mac_pool[key])
+ mac_pool.pop(key)
+
+ mac_pool.close()
+ fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN)
+ lock_file.close()
+
+
def mac_str_to_int(addr):
"""
Convert MAC address string to integer.
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index 135d08e..13eaac1 100755
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -5,7 +5,7 @@ Utility classes and functions to handle Virtual Machine creation using qemu.
@copyright: 2008-2009 Red Hat Inc.
"""

-import time, socket, os, logging, fcntl, re, commands, glob
+import time, socket, os, logging, fcntl, re, commands, shelve, glob
import kvm_utils, kvm_subprocess, kvm_monitor, rss_file_transfer
from autotest_lib.client.common_lib import error
from autotest_lib.client.bin import utils
@@ -117,6 +117,7 @@ class VM:
self.params = params
self.root_dir = root_dir
self.address_cache = address_cache
+ self.mac_prefix = params.get('mac_prefix')
self.netdev_id = []

# Find a unique identifier for this VM
@@ -126,8 +127,12 @@ class VM:
if not glob.glob("/tmp/*%s" % self.instance):
break

+ if self.mac_prefix is None:
+ self.mac_prefix = kvm_utils.generate_mac_address_prefix()

- def clone(self, name=None, params=None, root_dir=None, address_cache=None):
+
+ def clone(self, name=None, params=None, root_dir=None,
+ address_cache=None, preserve_mac=True):
"""
Return a clone of the VM object with optionally modified parameters.
The clone is initially not alive and needs to be started using create().
@@ -138,6 +143,7 @@ class VM:
@param params: Optional new VM creation parameters
@param root_dir: Optional new base directory for relative filenames
@param address_cache: A dict that maps MAC addresses to IP addresses
+ @param preserve_mac: Clone mac address or not.
"""
if name is None:
name = self.name
@@ -147,7 +153,20 @@ class VM:
root_dir = self.root_dir
if address_cache is None:
address_cache = self.address_cache
- return VM(name, params, root_dir, address_cache)
+ vm = VM(name, params, root_dir, address_cache)
+ if preserve_mac:
+ vlan = 0
+ for nic_name in kvm_utils.get_sub_dict_names(params, "nics"):
+ nic_params = kvm_utils.get_sub_dict(params, nic_name)
+ vm.set_mac_address(self.get_mac_address(vlan), vlan, True)
+ vlan += 1
+ return vm
+
+
+ def free_mac_addresses(self):
+ nic_num = len(kvm_utils.get_sub_dict_names(self.params, "nics"))
+ for i in range(nic_num):
+ kvm_utils.free_mac_address(self.root_dir, self.instance, i)

def make_qemu_command(self, name=None, params=None, root_dir=None):
@@ -387,6 +406,13 @@ class VM:
mac = None
if "address_index" in nic_params:
mac = kvm_utils.get_mac_ip_pair_from_dict(nic_params)[0]
+ self.set_mac_address(mac=mac, nic_index=vlan)
+ else:
+ mac = kvm_utils.generate_mac_address(self.root_dir,
+ self.instance,
+ vlan,
+ self.mac_prefix)
+
qemu_cmd += add_nic(help, vlan, nic_params.get("nic_model"), mac,
self.netdev_id[vlan])
# Handle the '-net tap' or '-net user' part
@@ -750,11 +776,15 @@ class VM:
logging.debug("Shutdown command sent; waiting for VM "
"to go down...")
if kvm_utils.wait_for(self.is_dead, 60, 1, 1):
- logging.debug("VM is down")
+ logging.debug("VM is down, freeing mac address.")
+ self.free_mac_addresses()
return
finally:
session.close()

+ # Free mac addresses
+ self.free_mac_addresses()
+
if self.monitor:
# Try to destroy with a monitor command
logging.debug("Trying to kill VM with monitor command...")
@@ -880,10 +910,13 @@ class VM:
nic_name = nics[index]
nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
if nic_params.get("nic_mode") == "tap":
- mac, ip = kvm_utils.get_mac_ip_pair_from_dict(nic_params)
+ mac = self.get_mac_address(index)
if not mac:
logging.debug("MAC address unavailable")
return None
+ mac = mac.lower()
+ ip = None
+
if not ip or nic_params.get("always_use_tcpdump") == "yes":
# Get the IP address from the cache
ip = self.address_cache.get(mac)
@@ -896,6 +929,7 @@ class VM:
for nic in nics]
macs = [kvm_utils.get_mac_ip_pair_from_dict(dict)[0]
for dict in nic_dicts]
+ macs.append(mac)
if not kvm_utils.verify_ip_address_ownership(ip, macs):
logging.debug("Could not verify MAC-IP address mapping: "
"%s ---> %s" % (mac, ip))
@@ -925,6 +959,45 @@ class VM:
return self.redirs.get(port)

+ def get_mac_address(self, nic_index=0):
+ """
+ Return the macaddr of guest nic.
+
+ @param nic_index: Index of the NIC
+ """
+ mac_pool = shelve.open("/tmp/address_pool", writeback=False)
+ key = "%s:%s" % (self.instance, nic_index)
+ if key in mac_pool.keys():
+ return mac_pool[key]
+ else:
+ return None
+
+
+ def set_mac_address(self, mac, nic_index=0, shareable=False):
+ """
+ Set mac address for guest. Note: It just update address pool.
+
+ @param mac: address will set to guest
+ @param nic_index: Index of the NIC
+ @param shareable: Where VM can share mac with other VM or not.
+ """
+ lock_file = open("/tmp/mac_lock", 'w')
+ fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
+ mac_pool = shelve.open("/tmp/address_pool", writeback=False)
+ key = "%s:%s" % (self.instance, nic_index)
+
+ if not mac in [mac_pool[i] for i in mac_pool.keys()]:
+ mac_pool[key] = mac
+ else:
+ if shareable:
+ mac_pool[key] = mac
+ else:
+ logging.error("MAC address %s is already in use!", mac)
+ mac_pool.close()
+ fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN)
+ lock_file.close()
+
+
def get_pid(self):
"""
Return the VM's PID. If the VM is dead return None.
diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample
index 7556693..9739a50 100644
--- a/client/tests/kvm/tests_base.cfg.sample
+++ b/client/tests/kvm/tests_base.cfg.sample
@@ -54,7 +54,7 @@ guest_port_remote_shell = 22
nic_mode = user
#nic_mode = tap
nic_script = scripts/qemu-ifup
-address_index = 0
+#address_index = 0
run_tcpdump = yes

# Misc
--
1.7.2.2