diff mbox

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

Message ID 1284503143-5993-2-git-send-email-lmr@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lucas Meneghel Rodrigues Sept. 14, 2010, 10:25 p.m. UTC
None
diff mbox

Patch

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