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. 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@xxxxxxxxxx> Signed-off-by: Feng Yang <fyang@xxxxxxxxxx> Signed-off-by: Amos Kong <akong@xxxxxxxxxx> --- 0 files changed, 0 insertions(+), 0 deletions(-) diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py index fb2d1c2..b019fc5 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,79 @@ def get_sub_dict_names(dict, keyword): # Functions related to MAC/IP addresses +def generate_mac_address(root_dir, instance_vm, nic_index, prefix='00:11:22:33:'): + """ + 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: Return mac address. + """ + 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 addr %s " % 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("Key : %s" % key) + logging.debug("pool is %s" % mac_pool) + else: + logging.debug("Freeing mac addr %s" % 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 bdc9aab..6812c98 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,15 @@ class VM: if not glob.glob("/tmp/*%s" % self.instance): break + if self.mac_prefix is None: + s, o = commands.getstatusoutput("ifconfig eth0") + if s == 0: + mac = re.findall("HWaddr (\S*)", o)[0] + self.mac_prefix = '00' + mac[8:] + ':' + - 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 +146,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 +156,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_macaddr(self.get_macaddr(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): @@ -386,6 +408,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_macaddr(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 @@ -749,11 +778,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...") @@ -879,10 +912,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_macaddr(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) @@ -895,6 +931,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)) @@ -923,6 +960,42 @@ class VM: "redirected" % port) return self.redirs.get(port) + def get_macaddr(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_macaddr(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 already be used!") + mac_pool.close() + fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN) + lock_file.close() def get_pid(self): """ diff --git a/client/tests/kvm/tests_base.cfg.sample b/client/tests/kvm/tests_base.cfg.sample index cb35f5e..26760f6 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 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html