diff mbox

[KVM-AUTOTEST,11/12] KVM test: make VMs use a dynamic MAC-IP mapping generated by tcpdump

Message ID 473df6e6bfa6fc5a48bf7f0c4474cc7c4ed43bf6.1249582935.git.mgoldish@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Michael Goldish Aug. 6, 2009, 6:41 p.m. UTC
In VM.get_address(), return an IP address from the MAC-IP cache if an IP
address base wasn't specified by the user, or if the user requested that IP
addresses be taken from the dynamic cache.

To force the system to obtain IP addresses from the dynamic cache even when
static base addresses are provided by the user, specify
'always_use_tcpdump = yes'.

Signed-off-by: Michael Goldish <mgoldish@redhat.com>
---
 client/tests/kvm/kvm_address_pools.cfg.sample |    8 +++
 client/tests/kvm/kvm_preprocessing.py         |    3 +-
 client/tests/kvm/kvm_utils.py                 |   28 +++++++++++-
 client/tests/kvm/kvm_vm.py                    |   62 +++++++++++++++++-------
 4 files changed, 81 insertions(+), 20 deletions(-)

Comments

Jason Wang Aug. 11, 2009, 2:43 p.m. UTC | #1
Hello Michael:
Based on our experiences with TAP networking scripts, the
verify_mac_ip_pair() could not work well under multiple nics when
testing linux guests. The main problem is the policy of multiple
interfaces responding in linux, we should take care of the value of
net.ipv4.conf.all.arp_ignore. Its default value(0) may cause the host
get a mac address that does not configured on the target interface of guest.
Maybe we could run a simple 'setup' case for all linux guests to set the
value of arp_ignore to 1.
What's your opinion?
> In VM.get_address(), return an IP address from the MAC-IP cache if an IP
> address base wasn't specified by the user, or if the user requested that IP
> addresses be taken from the dynamic cache.
>
> To force the system to obtain IP addresses from the dynamic cache even when
> static base addresses are provided by the user, specify
> 'always_use_tcpdump = yes'.
>
> Signed-off-by: Michael Goldish <mgoldish@redhat.com>
> ---
>  client/tests/kvm/kvm_address_pools.cfg.sample |    8 +++
>  client/tests/kvm/kvm_preprocessing.py         |    3 +-
>  client/tests/kvm/kvm_utils.py                 |   28 +++++++++++-
>  client/tests/kvm/kvm_vm.py                    |   62 +++++++++++++++++-------
>  4 files changed, 81 insertions(+), 20 deletions(-)
>
> diff --git a/client/tests/kvm/kvm_address_pools.cfg.sample b/client/tests/kvm/kvm_address_pools.cfg.sample
> index 8a27ee1..debfe56 100644
> --- a/client/tests/kvm/kvm_address_pools.cfg.sample
> +++ b/client/tests/kvm/kvm_address_pools.cfg.sample
> @@ -6,6 +6,14 @@
>  # If you wish to use a static MAC-IP mapping, where each MAC address range is
>  # mapped to a known corresponding IP address range, specify the bases of the IP
>  # address ranges in this file.
> +# If you specify a MAC address range without a corresponding IP address range,
> +# the IP addresses for that range will be determined at runtime by listening
> +# to DHCP traffic using tcpdump.
> +# If you wish to determine IP addresses using tcpdump in any case, regardless
> +# of any # IP addresses specified in this file, uncomment the following line:
> +#always_use_tcpdump = yes
> +# You may also specify this parameter for specific hosts by adding it in the
> +# appropriate sections below.
>  
>  variants:
>      # Rename host1 to an actual (short) hostname in the network that will be running the Autotest client
> diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py
> index 04bdb59..a5a32dc 100644
> --- a/client/tests/kvm/kvm_preprocessing.py
> +++ b/client/tests/kvm/kvm_preprocessing.py
> @@ -54,7 +54,8 @@ def preprocess_vm(test, params, env, name):
>          logging.debug("VM object found in environment")
>      else:
>          logging.debug("VM object does not exist; creating it")
> -        vm = kvm_vm.VM(name, params, qemu_path, image_dir, iso_dir, script_dir)
> +        vm = kvm_vm.VM(name, params, qemu_path, image_dir, iso_dir, script_dir,
> +                       env.get("address_cache"))
>          kvm_utils.env_register_vm(env, name, vm)
>  
>      start_vm = False
> diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
> index ef257bc..17c2b73 100644
> --- a/client/tests/kvm/kvm_utils.py
> +++ b/client/tests/kvm/kvm_utils.py
> @@ -1,5 +1,5 @@
>  import md5, thread, subprocess, time, string, random, socket, os, signal, pty
> -import select, re, logging
> +import select, re, logging, commands
>  from autotest_lib.client.bin import utils
>  from autotest_lib.client.common_lib import error
>  import kvm_subprocess
> @@ -152,6 +152,32 @@ def get_mac_ip_pair_from_dict(dict):
>      return (None, None)
>  
>  
> +def verify_mac_ip_pair(mac, ip, timeout=3.0):
> +    """
> +    Connect to a given IP address and make sure its MAC address equals the
> +    given MAC address.
> +
> +    @param mac: A MAC address.
> +    @param ip: An IP address.
> +    @return: True iff ip is assigned to mac.
> +    """
> +    s = socket.socket()
> +    s.setblocking(False)
> +    try:
> +        s.connect((ip, 55555))
> +    except socket.error:
> +        pass
> +    end_time = time.time() + timeout
> +    while time.time() < end_time:
> +        o = commands.getoutput("/sbin/arp -n")
> +        if re.search(r"\b%s\b.*\b%s\b" % (ip, mac), o, re.IGNORECASE):
> +            s.close()
> +            return True
> +        time.sleep(0.1)
> +    s.close()
> +    return False
> +
> +
>  # Functions for working with the environment (a dict-like object)
>  
>  def is_vm(obj):
> diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
> index 0c35e64..6addf77 100644
> --- a/client/tests/kvm/kvm_vm.py
> +++ b/client/tests/kvm/kvm_vm.py
> @@ -1,5 +1,5 @@
>  #!/usr/bin/python
> -import time, socket, os, logging, fcntl
> +import time, socket, os, logging, fcntl, re, commands
>  import kvm_utils, kvm_subprocess
>  
>  """
> @@ -101,7 +101,7 @@ class VM:
>      """
>  
>      def __init__(self, name, params, qemu_path, image_dir, iso_dir,
> -                 script_dir):
> +                 script_dir, address_cache):
>          """
>          Initialize the object and set a few attributes.
>  
> @@ -112,6 +112,7 @@ class VM:
>          @param image_dir: The directory where images reside
>          @param iso_dir: The directory where ISOs reside
>          @param script_dir: The directory where -net tap scripts reside
> +        @param address_cache: A dict that maps MAC addresses to IP addresses
>          """
>          self.process = None
>          self.redirs = {}
> @@ -124,6 +125,7 @@ class VM:
>          self.image_dir = image_dir
>          self.iso_dir = iso_dir
>          self.script_dir = script_dir
> +        self.address_cache = address_cache
>  
>          # Find available monitor filename
>          while True:
> @@ -137,7 +139,7 @@ class VM:
>  
>  
>      def clone(self, name=None, params=None, qemu_path=None, image_dir=None,
> -              iso_dir=None, script_dir=None):
> +              iso_dir=None, script_dir=None, address_cache=None):
>          """
>          Return a clone of the VM object with optionally modified parameters.
>          The clone is initially not alive and needs to be started using create().
> @@ -150,6 +152,7 @@ class VM:
>          @param image_dir: Optional new image dir
>          @param iso_dir: Optional new iso directory
>          @param script_dir: Optional new -net tap script directory
> +        @param address_cache: A dict that maps MAC addresses to IP addresses
>          """
>          if name == None:
>              name = self.name
> @@ -163,7 +166,10 @@ class VM:
>              iso_dir = self.iso_dir
>          if script_dir == None:
>              script_dir = self.script_dir
> -        return VM(name, params, qemu_path, image_dir, iso_dir, script_dir)
> +        if address_cache == None:
> +            address_cache = self.address_cache
> +        return VM(name, params, qemu_path, image_dir, iso_dir, script_dir,
> +                  address_cache)
>  
>  
>      def make_qemu_command(self, name=None, params=None, qemu_path=None,
> @@ -625,21 +631,36 @@ class VM:
>          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)
> +            if not mac:
> +                logging.debug("MAC address unavailable")
> +                return 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)
> +                if not ip:
> +                    logging.debug("Could not find IP address for MAC address: "
> +                                  "%s" % mac)
> +                    return None
> +                # Make sure the IP address is assigned to the right MAC address
> +                if not kvm_utils.verify_mac_ip_pair(mac, ip):
> +                    logging.debug("Could not verify MAC-IP address pair: "
> +                                  "%s ---> %s" % (mac, ip))
> +                    return None
>              return ip
>          else:
>              return "localhost"
>  
>  
> -    def get_port(self, port, index=0):
> +    def get_port(self, port, nic_index=0):
>          """
>          Return the port in host space corresponding to port in guest space.
>  
>          @param port: Port number in host space.
> -        @param index: Index of the NIC.
> +        @param nic_index: Index of the NIC.
>          @return: If port redirection is used, return the host port redirected
>                  to guest port port. Otherwise return port.
>          """
> -        nic_name = kvm_utils.get_sub_dict_names(self.params, "nics")[index]
> +        nic_name = kvm_utils.get_sub_dict_names(self.params, "nics")[nic_index]
>          nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
>          if nic_params.get("nic_mode") == "tap":
>              return port
> @@ -666,17 +687,19 @@ class VM:
>          """
>          address = self.get_address()
>          port = self.get_port(int(self.params.get("ssh_port")))
> -        if not port:
> +        if not address or not port:
> +            logging.debug("IP address or port unavailable")
>              return False
>          return kvm_utils.is_sshd_running(address, port, timeout=timeout)
>  
>  
> -    def ssh_login(self, timeout=10):
> +    def ssh_login(self, nic_index=0, timeout=10):
>          """
>          Log into the guest via SSH/Telnet.
>          If timeout expires while waiting for output from the guest (e.g. a
>          password prompt or a shell prompt) -- fail.
>  
> +        @param nic_index: The index of the NIC to connect to.
>          @param timeout: Time (seconds) before giving up logging into the
>                  guest.
>          @return: kvm_spawn object on success and None on failure.
> @@ -685,9 +708,10 @@ class VM:
>          password = self.params.get("password", "")
>          prompt = self.params.get("ssh_prompt", "[\#\$]")
>          use_telnet = self.params.get("use_telnet") == "yes"
> -        address = self.get_address()
> +        address = self.get_address(nic_index)
>          port = self.get_port(int(self.params.get("ssh_port")))
> -        if not port:
> +        if not address or not port:
> +            logging.debug("IP address or port unavailable")
>              return None
>  
>          if use_telnet:
> @@ -702,7 +726,7 @@ class VM:
>          return session
>  
>  
> -    def scp_to_remote(self, local_path, remote_path, timeout=300):
> +    def scp_to_remote(self, local_path, remote_path, nic_index=0, timeout=300):
>          """
>          Transfer files to the guest via SCP.
>  
> @@ -713,15 +737,16 @@ class VM:
>          """
>          username = self.params.get("username", "")
>          password = self.params.get("password", "")
> -        address = self.get_address()
> +        address = self.get_address(nic_index)
>          port = self.get_port(int(self.params.get("ssh_port")))
> -        if not port:
> +        if not address or not port:
> +            logging.debug("IP address or port unavailable")
>              return None
>          return kvm_utils.scp_to_remote(address, port, username, password,
>                                         local_path, remote_path, timeout)
>  
>  
> -    def scp_from_remote(self, remote_path, local_path, timeout=300):
> +    def scp_from_remote(self, remote_path, local_path, nic_index=0, timeout=300):
>          """
>          Transfer files from the guest via SCP.
>  
> @@ -732,9 +757,10 @@ class VM:
>          """
>          username = self.params.get("username", "")
>          password = self.params.get("password", "")
> -        address = self.get_address()
> +        address = self.get_address(nic_index)
>          port = self.get_port(int(self.params.get("ssh_port")))
> -        if not port:
> +        if not address or not port:
> +            logging.debug("IP address or port unavailable")
>              return None
>          return kvm_utils.scp_from_remote(address, port, username, password,
>                                           remote_path, local_path, timeout)
> @@ -749,7 +775,7 @@ class VM:
>          @return: A tuple (status, output). status is 0 on success and 1 on
>                  failure.
>          """
> -        session = self.ssh_login(timeout)
> +        session = self.ssh_login(timeout=timeout)
>          if not session:
>              return (1, "")
>  
>   

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/client/tests/kvm/kvm_address_pools.cfg.sample b/client/tests/kvm/kvm_address_pools.cfg.sample
index 8a27ee1..debfe56 100644
--- a/client/tests/kvm/kvm_address_pools.cfg.sample
+++ b/client/tests/kvm/kvm_address_pools.cfg.sample
@@ -6,6 +6,14 @@ 
 # If you wish to use a static MAC-IP mapping, where each MAC address range is
 # mapped to a known corresponding IP address range, specify the bases of the IP
 # address ranges in this file.
+# If you specify a MAC address range without a corresponding IP address range,
+# the IP addresses for that range will be determined at runtime by listening
+# to DHCP traffic using tcpdump.
+# If you wish to determine IP addresses using tcpdump in any case, regardless
+# of any # IP addresses specified in this file, uncomment the following line:
+#always_use_tcpdump = yes
+# You may also specify this parameter for specific hosts by adding it in the
+# appropriate sections below.
 
 variants:
     # Rename host1 to an actual (short) hostname in the network that will be running the Autotest client
diff --git a/client/tests/kvm/kvm_preprocessing.py b/client/tests/kvm/kvm_preprocessing.py
index 04bdb59..a5a32dc 100644
--- a/client/tests/kvm/kvm_preprocessing.py
+++ b/client/tests/kvm/kvm_preprocessing.py
@@ -54,7 +54,8 @@  def preprocess_vm(test, params, env, name):
         logging.debug("VM object found in environment")
     else:
         logging.debug("VM object does not exist; creating it")
-        vm = kvm_vm.VM(name, params, qemu_path, image_dir, iso_dir, script_dir)
+        vm = kvm_vm.VM(name, params, qemu_path, image_dir, iso_dir, script_dir,
+                       env.get("address_cache"))
         kvm_utils.env_register_vm(env, name, vm)
 
     start_vm = False
diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py
index ef257bc..17c2b73 100644
--- a/client/tests/kvm/kvm_utils.py
+++ b/client/tests/kvm/kvm_utils.py
@@ -1,5 +1,5 @@ 
 import md5, thread, subprocess, time, string, random, socket, os, signal, pty
-import select, re, logging
+import select, re, logging, commands
 from autotest_lib.client.bin import utils
 from autotest_lib.client.common_lib import error
 import kvm_subprocess
@@ -152,6 +152,32 @@  def get_mac_ip_pair_from_dict(dict):
     return (None, None)
 
 
+def verify_mac_ip_pair(mac, ip, timeout=3.0):
+    """
+    Connect to a given IP address and make sure its MAC address equals the
+    given MAC address.
+
+    @param mac: A MAC address.
+    @param ip: An IP address.
+    @return: True iff ip is assigned to mac.
+    """
+    s = socket.socket()
+    s.setblocking(False)
+    try:
+        s.connect((ip, 55555))
+    except socket.error:
+        pass
+    end_time = time.time() + timeout
+    while time.time() < end_time:
+        o = commands.getoutput("/sbin/arp -n")
+        if re.search(r"\b%s\b.*\b%s\b" % (ip, mac), o, re.IGNORECASE):
+            s.close()
+            return True
+        time.sleep(0.1)
+    s.close()
+    return False
+
+
 # Functions for working with the environment (a dict-like object)
 
 def is_vm(obj):
diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
index 0c35e64..6addf77 100644
--- a/client/tests/kvm/kvm_vm.py
+++ b/client/tests/kvm/kvm_vm.py
@@ -1,5 +1,5 @@ 
 #!/usr/bin/python
-import time, socket, os, logging, fcntl
+import time, socket, os, logging, fcntl, re, commands
 import kvm_utils, kvm_subprocess
 
 """
@@ -101,7 +101,7 @@  class VM:
     """
 
     def __init__(self, name, params, qemu_path, image_dir, iso_dir,
-                 script_dir):
+                 script_dir, address_cache):
         """
         Initialize the object and set a few attributes.
 
@@ -112,6 +112,7 @@  class VM:
         @param image_dir: The directory where images reside
         @param iso_dir: The directory where ISOs reside
         @param script_dir: The directory where -net tap scripts reside
+        @param address_cache: A dict that maps MAC addresses to IP addresses
         """
         self.process = None
         self.redirs = {}
@@ -124,6 +125,7 @@  class VM:
         self.image_dir = image_dir
         self.iso_dir = iso_dir
         self.script_dir = script_dir
+        self.address_cache = address_cache
 
         # Find available monitor filename
         while True:
@@ -137,7 +139,7 @@  class VM:
 
 
     def clone(self, name=None, params=None, qemu_path=None, image_dir=None,
-              iso_dir=None, script_dir=None):
+              iso_dir=None, script_dir=None, address_cache=None):
         """
         Return a clone of the VM object with optionally modified parameters.
         The clone is initially not alive and needs to be started using create().
@@ -150,6 +152,7 @@  class VM:
         @param image_dir: Optional new image dir
         @param iso_dir: Optional new iso directory
         @param script_dir: Optional new -net tap script directory
+        @param address_cache: A dict that maps MAC addresses to IP addresses
         """
         if name == None:
             name = self.name
@@ -163,7 +166,10 @@  class VM:
             iso_dir = self.iso_dir
         if script_dir == None:
             script_dir = self.script_dir
-        return VM(name, params, qemu_path, image_dir, iso_dir, script_dir)
+        if address_cache == None:
+            address_cache = self.address_cache
+        return VM(name, params, qemu_path, image_dir, iso_dir, script_dir,
+                  address_cache)
 
 
     def make_qemu_command(self, name=None, params=None, qemu_path=None,
@@ -625,21 +631,36 @@  class VM:
         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)
+            if not mac:
+                logging.debug("MAC address unavailable")
+                return 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)
+                if not ip:
+                    logging.debug("Could not find IP address for MAC address: "
+                                  "%s" % mac)
+                    return None
+                # Make sure the IP address is assigned to the right MAC address
+                if not kvm_utils.verify_mac_ip_pair(mac, ip):
+                    logging.debug("Could not verify MAC-IP address pair: "
+                                  "%s ---> %s" % (mac, ip))
+                    return None
             return ip
         else:
             return "localhost"
 
 
-    def get_port(self, port, index=0):
+    def get_port(self, port, nic_index=0):
         """
         Return the port in host space corresponding to port in guest space.
 
         @param port: Port number in host space.
-        @param index: Index of the NIC.
+        @param nic_index: Index of the NIC.
         @return: If port redirection is used, return the host port redirected
                 to guest port port. Otherwise return port.
         """
-        nic_name = kvm_utils.get_sub_dict_names(self.params, "nics")[index]
+        nic_name = kvm_utils.get_sub_dict_names(self.params, "nics")[nic_index]
         nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
         if nic_params.get("nic_mode") == "tap":
             return port
@@ -666,17 +687,19 @@  class VM:
         """
         address = self.get_address()
         port = self.get_port(int(self.params.get("ssh_port")))
-        if not port:
+        if not address or not port:
+            logging.debug("IP address or port unavailable")
             return False
         return kvm_utils.is_sshd_running(address, port, timeout=timeout)
 
 
-    def ssh_login(self, timeout=10):
+    def ssh_login(self, nic_index=0, timeout=10):
         """
         Log into the guest via SSH/Telnet.
         If timeout expires while waiting for output from the guest (e.g. a
         password prompt or a shell prompt) -- fail.
 
+        @param nic_index: The index of the NIC to connect to.
         @param timeout: Time (seconds) before giving up logging into the
                 guest.
         @return: kvm_spawn object on success and None on failure.
@@ -685,9 +708,10 @@  class VM:
         password = self.params.get("password", "")
         prompt = self.params.get("ssh_prompt", "[\#\$]")
         use_telnet = self.params.get("use_telnet") == "yes"
-        address = self.get_address()
+        address = self.get_address(nic_index)
         port = self.get_port(int(self.params.get("ssh_port")))
-        if not port:
+        if not address or not port:
+            logging.debug("IP address or port unavailable")
             return None
 
         if use_telnet:
@@ -702,7 +726,7 @@  class VM:
         return session
 
 
-    def scp_to_remote(self, local_path, remote_path, timeout=300):
+    def scp_to_remote(self, local_path, remote_path, nic_index=0, timeout=300):
         """
         Transfer files to the guest via SCP.
 
@@ -713,15 +737,16 @@  class VM:
         """
         username = self.params.get("username", "")
         password = self.params.get("password", "")
-        address = self.get_address()
+        address = self.get_address(nic_index)
         port = self.get_port(int(self.params.get("ssh_port")))
-        if not port:
+        if not address or not port:
+            logging.debug("IP address or port unavailable")
             return None
         return kvm_utils.scp_to_remote(address, port, username, password,
                                        local_path, remote_path, timeout)
 
 
-    def scp_from_remote(self, remote_path, local_path, timeout=300):
+    def scp_from_remote(self, remote_path, local_path, nic_index=0, timeout=300):
         """
         Transfer files from the guest via SCP.
 
@@ -732,9 +757,10 @@  class VM:
         """
         username = self.params.get("username", "")
         password = self.params.get("password", "")
-        address = self.get_address()
+        address = self.get_address(nic_index)
         port = self.get_port(int(self.params.get("ssh_port")))
-        if not port:
+        if not address or not port:
+            logging.debug("IP address or port unavailable")
             return None
         return kvm_utils.scp_from_remote(address, port, username, password,
                                          remote_path, local_path, timeout)
@@ -749,7 +775,7 @@  class VM:
         @return: A tuple (status, output). status is 0 on success and 1 on
                 failure.
         """
-        session = self.ssh_login(timeout)
+        session = self.ssh_login(timeout=timeout)
         if not session:
             return (1, "")