Re: [Qemu-devel] [RFC PATCH 01/14] KVM-test: Add a new macaddress pool algorithm

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 07/20/2010 04:44 PM, Amos Kong wrote:
> On Tue, Jul 20, 2010 at 01:19:39PM +0300, Michael Goldish wrote:
>>
> 
> Michael,
> 
> Thanks for your comments. Let's simplify this method together.
> 
>> On 07/20/2010 04:34 AM, Amos Kong wrote:
>>> Old method uses the mac address in the configuration 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 which could eliminate the duplicated mac
>>> addresses between machines.
>>>
>>> When user have set the mac_prefix in the configuration file, we should use it
>>> in stead of the dynamic generated mac prefix.
>>>
>>> Other change:
>>> . Fix randomly generating mac address so that it correspond to IEEE802.
>>> . Update clone function to decide clone mac address or not.
>>> . Update get_macaddr function.
>>> . Add set_mac_address function.
>>>
>>> New auto mac address pool algorithm:
>>> If address_index is defined, VM will get mac from config file then record mac
>>> in to address_pool. If address_index is not defined, VM will call
>>> get_mac_from_pool to auto create mac then recored mac to address_pool in
>>> following format:
>>> {'macpool': {'AE:9D:94:6A:9b:f9': ['20100310-165222-Wt7l:0']}}
>>>
>>>   AE:9D:94:6A:9b:f9    : mac address
>>>   20100310-165222-Wt7l : instance attribute of VM
>>>   0                    : index of NIC
>>
>> Why do you use the mac address as a key, instead of the instance string
>> + nic index?  When the mac address is used as a key, each key has a list
>> of values instead of just one value.  This order seems unnatural.  If it
>> were the other way around (i.e. key = VM instance + nic index, value =
>> mac address), then each key would have exactly one value, and I think
>> this patch would be shorter and simpler.
> 
> One mac address may be used by two VMs, eg. migration.

Sure, that's why I thought the opposite direction would be better: keys
= VMs (nics), values = mac addresses.  That way we have one value per
key, instead of a list of values per key.

To clarify, instead of using:

{'AE:9D:94:6A:9b:f9': ['20100310-165222-Wt7l:0',
'20100310-165222-Wt7l:1', '20100310-165222-Wt7l:2']}

I suggest:

{'20100310-165222-Wt7l:0': 'AE:9D:94:6A:9b:f9',
 '20100310-165222-Wt7l:1': 'AE:9D:94:6A:9b:f9',
 '20100310-165222-Wt7l:2': 'AE:9D:94:6A:9b:f9'}

>>> 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..7c0946e 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,104 @@ def get_sub_dict_names(dict, keyword):
>>>  
>>>  # Functions related to MAC/IP addresses
>>>  
>>> +def get_mac_from_pool(root_dir, vm, nic_index, prefix='00:11:22:33:'):
>>
>> The name of this function is confusing because it does the exact
>> opposite: it puts a mac address in address_pool.  Maybe the pool you're
>> referring to in the name isn't address_pool, but still a less confusing
>> name should probably be used.
> 
> How about allocate_mac(...) ?
> address_pool -> address_container
> 
> Allocate mac address and record into address_container.

Yes, something like that, sounds less confusing.

>>> +    """
>>> +    random generated mac address.
>>> +
>>> +    1) First try to generate macaddress based on the mac address prefix.
>>> +    2) And then try to use total random generated mac address.
>>> +
>>> +    @param root_dir: Root dir for kvm
>>> +    @param 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_filename = os.path.join(root_dir, "mac_lock")
>>> +    lock_file = open(lock_filename, 'w')
>>> +    fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
>>> +    mac_filename = os.path.join(root_dir, "address_pool")
>>
>> Maybe it makes sense to put address_pool and the lock file in /tmp,
>> where they can be shared by more than a single autotest instance running
>> on the same host (unlikely, but theoretically possible).
> 
> good idea.
>  
>>> +    mac_shelve = shelve.open(mac_filename, writeback=False)
>>> +
>>> +    mac_pool = mac_shelve.get("macpool")
>>
>> Why is this 'macpool' needed?  Why not put the keys directly in the
>> shelve object?
>  
> yes, put keys directly in the shelve object is better.
> 
>>> +    if not mac_pool:
>>> +        mac_pool = {}
>>> +    found = False
>>> +
>>> +    val = "%s:%s" % (vm, nic_index)
>>> +    for key in mac_pool.keys():
>>> +        if val in mac_pool[key]:
>>> +            mac_pool[key].append(val)
>>
>> Why append val to mac_pool[key] if val is already in mac_pool[key]?
> 
> need drop it.
> 
>>> +            found = True
>>> +            mac = key
>>> +
>>> +    while not found:
>>> +        postfix = "%02x:%02x" % (random.randint(0x00,0xfe),
>>> +                                random.randint(0x00,0xfe))
>>> +        mac = prefix + postfix
>>> +        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]
>>
>> Why is this needed?  Most mac addresses begin with 00. If the mac
>> address is generated from the address of eth0 (using the method in this
>> patch) it begins with 00, which is fine. If the prefix is set by the
>> user using mac_prefix, I don't think we should modify it.
> 
> 
> linux-2.6/include/linux/etherdevice.h
> 
> /**
>  * random_ether_addr - Generate software assigned random Ethernet address
>  * @addr: Pointer to a six-byte array containing the Ethernet address
>  *
>  * Generate a random Ethernet address (MAC) that is not multicast
>  * and has the local assigned bit set.
>  */
> static inline void random_ether_addr(u8 *addr)
> {
>         get_random_bytes (addr, ETH_ALEN);
>         addr [0] &= 0xfe;       /* clear multicast bit */
>         addr [0] |= 0x02;       /* set local assignment bit (IEEE802) */
> }

I saw this googling, but I still don't understand why it's necessary
when real devices start with 00.

>>> +        mac = ":".join(mac_list)
>>> +        if mac not in mac_pool.keys() or 0 == len(mac_pool[mac]):
>>> +            mac_pool[mac] = ["%s:%s" % (vm,nic_index)]
>>> +            found = True
>>> +    mac_shelve["macpool"] = mac_pool
>>> +    logging.debug("generating mac addr %s " % mac)
>>> +
>>> +    mac_shelve.close()
>>> +    fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN)
>>> +    lock_file.close()
>>> +    return mac
>>> +
>>> +
>>> +def put_mac_to_pool(root_dir, mac, vm):
>>
>> This function removes a mac address from address_pool.  I wonder why you
>> chose this name.
> 
> address_pool file is a container to record macaddress.
> The 'pool' we get/put mac is an abstract resource pool.
> This is really confused ;)

Yes, I guessed it would be something like that.  Still a bit confusing,
I agree, but it's not a big deal to change function names.

>>> +    """
>>> +    Put the macaddress back to address pool
>>> +
>>> +    @param root_dir: Root dir for kvm
>>> +    @param vm: Here we use instance attribute of vm
>>> +    @param mac: mac address will be put.
>>> +    @Return: mac address.
>>> +    """
>>> +
>>> +    lock_filename = os.path.join(root_dir, "mac_lock")
>>> +    lock_file = open(lock_filename, 'w')
>>> +    fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
>>> +    mac_filename = os.path.join(root_dir, "address_pool")
>>> +    mac_shelve = shelve.open(mac_filename, writeback=False)
>>> +
>>> +    mac_pool = mac_shelve.get("macpool")
>>> +
>>> +    if not mac_pool or (not mac in mac_pool):
>>> +        logging.debug("Try to free a macaddress does no in pool")
>>> +        logging.debug("macaddress is %s" % mac)
>>> +        logging.debug("pool is %s" % mac_pool)
>>> +    else:
>>> +        if len(mac_pool[mac]) <= 1:
>>> +            mac_pool.pop(mac)
>>> +        else:
>>> +            for value in mac_pool[mac]:
>>> +                if vm in value:
>>> +                    mac_pool[mac].remove(value)
>>> +                    break
>>> +            if len(mac_pool[mac]) == 0:
>>> +                mac_pool.pop(mac)
>>> +
>>> +    mac_shelve["macpool"] = mac_pool
>>> +    logging.debug("freeing mac addr %s " % mac)
>>> +
>>> +    mac_shelve.close()
>>> +    fcntl.lockf(lock_file.fileno(), fcntl.LOCK_UN)
>>> +    lock_file.close()
>>> +    return mac
>>> +
>>> +
>>>  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 6cd0688..a9ba6e7 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, mac_clone=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 mac_clone: Clone mac address or not.
>>>          """
>>>          if name is None:
>>>              name = self.name
>>> @@ -147,9 +156,19 @@ 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 mac_clone:
>>> +            # Clone mac address by coping 'self.instance' to the new vm.
>>> +            vm.instance = self.instance
>>
>> Copying self.instance isn't a good idea because the monitor, serial
>> console and testlog filenames use self.instance.  self.instance should
>> be unique.  If we still want to use the same mac address for 2 VMs, for
>> example in migration, a different solution should be found.
> 
> We almost only clone mac in migration test, the src guest would be killed.
> If the instance of dest guest is same as src guest, the serial connection would not break,
> it would continually write log to original log file.

What about the monitor(s)?  The fact that the serial console connection
doesn't break doesn't necessarily mean everything's OK.

>>> +        return vm
>>>  
>>>  
>>> +    def free_mac_address(self):
>>> +        nic_num = len(kvm_utils.get_sub_dict_names(self.params, "nics"))
>>> +        for nic in range(nic_num):
>>> +            mac = self.get_macaddr(nic_index=nic)
>>> +            kvm_utils.put_mac_to_pool(self.root_dir, mac, vm=self.instance)
>>> +
>>>      def make_qemu_command(self, name=None, params=None, root_dir=None):
>>>          """
>>>          Generate a qemu command line. All parameters are optional. If a
>>> @@ -383,6 +402,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.get_mac_from_pool(self.root_dir,
>>> +                                                  vm=self.instance,
>>> +                                                  nic_index=vlan,
>>> +                                                  prefix=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
>>> @@ -396,7 +422,7 @@ class VM:
>>>              if tftp:
>>>                  tftp = kvm_utils.get_path(root_dir, tftp)
>>>              qemu_cmd += add_net(help, vlan, nic_params.get("nic_mode", "user"),
>>> -                                nic_params.get("nic_ifname"),
>>> +                                self.get_ifname(vlan),
>>
>> Why is get_ifname() useful here?
> 
> dynamically generate the ifname is better.

But why?  Is this ifname useful for any test?

>>>                                  script, downscript, tftp,
>>>                                  nic_params.get("bootp"), redirs,
>>>                                  self.netdev_id[vlan])
>>> @@ -720,7 +746,7 @@ class VM:
>>>              lockfile.close()
>>>  
>>>  
>>> -    def destroy(self, gracefully=True):
>>> +    def destroy(self, gracefully=True, free_macaddr=True):
>>>          """
>>>          Destroy the VM.
>>>  
>>> @@ -731,6 +757,7 @@ class VM:
>>>          @param gracefully: Whether an attempt will be made to end the VM
>>>                  using a shell command before trying to end the qemu process
>>>                  with a 'quit' or a kill signal.
>>> +        @param free_macaddr: Whether free macaddresses when destory a vm.
>>>          """
>>>          try:
>>>              # Is it already dead?
>>> @@ -751,11 +778,18 @@ 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, free mac address.")
>>> +                            # free mac address
>>> +                            if free_macaddr:
>>> +                                self.free_mac_address()
>>>                              return
>>>                      finally:
>>>                          session.close()
>>>  
>>> +            # free mac address
>>> +            if free_macaddr:
>>> +                self.free_mac_address()
>>> +
>>>              if self.monitor:
>>>                  # Try to destroy with a monitor command
>>>                  logging.debug("Trying to kill VM with monitor command...")
>>> @@ -881,10 +915,14 @@ 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
>>> +            else:
>>> +                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)
>>> @@ -897,6 +935,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 +964,71 @@ class VM:
>>>                               "redirected" % port)
>>>              return self.redirs.get(port)
>>>  
>>> +    def get_ifname(self, nic_index=0):
>>> +        """
>>> +        Return the ifname of tap device for the guest nic.
>>> +
>>> +        @param nic_index: Index of the NIC
>>> +        """
>>> +
>>> +        nics = kvm_utils.get_sub_dict_names(self.params, "nics")
>>> +        nic_name = nics[nic_index]
>>> +        nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
>>> +        if nic_params.get("nic_ifname"):
>>> +            return nic_params.get("nic_ifname")
>>> +        else:
>>> +            return "%s_%s_%s" % (nic_params.get("nic_model"),
>>> +                                 nic_index, self.vnc_port)
>>
>> What's the purpose of this string?
> 
> Just avoid repeated ifname.  The vnc_port is unique for each VM, nic_index is unique for each nic of one VM.

self.instance should also be unique, though it's quite long.

>>> +    def get_macaddr(self, nic_index=0):
>>> +        """
>>> +        Return the macaddr of guest nic.
>>> +
>>> +        @param nic_index: Index of the NIC
>>> +        """
>>> +        mac_filename = os.path.join(self.root_dir, "address_pool")
>>> +        mac_shelve = shelve.open(mac_filename, writeback=False)
>>> +        mac_pool = mac_shelve.get("macpool")
>>> +        val = "%s:%s" % (self.instance, nic_index)
>>> +        for key in mac_pool.keys():
>>> +            if val in mac_pool[key]:
>>> +                return key
>>> +        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_filename = os.path.join(self.root_dir, "mac_lock")
>>> +        lock_file = open(lock_filename, 'w')
>>> +        fcntl.lockf(lock_file.fileno() ,fcntl.LOCK_EX)
>>> +        mac_filename = os.path.join(self.root_dir, "address_pool")
>>> +        mac_shelve = shelve.open(mac_filename, writeback=False)
>>> +        mac_pool = mac_shelve.get("macpool")
>>> +        if not mac_pool:
>>> +            mac_pool = {}
>>> +        value = "%s:%s" % (self.instance, nic_index)
>>> +        if mac not in mac_pool.keys():
>>> +            for key in mac_pool.keys():
>>> +                if value in mac_pool[key]:
>>> +                    mac_pool[key].remove(value)
>>> +                if len(mac_pool[key]) == 0:
>>> +                    mac_pool.pop(key)
>>> +            mac_pool[mac] = [value]
>>> +        else:
>>> +            if shareable:
>>> +                mac_pool[mac].append(value)
>>> +            else:
>>> +                logging.error("Mac address already be used!")
>>> +                return False
>>> +        mac_shelve["macpool"] = mac_pool
>>> +        mac_shelve.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 fd9e72f..6710c00 100644
>>> --- a/client/tests/kvm/tests_base.cfg.sample
>>> +++ b/client/tests/kvm/tests_base.cfg.sample
>>> @@ -51,7 +51,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


[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux