[dts] [PATCH v1 14/16] framework/qemu_kvm: support multiple VMs module

Marvin Liu yong.liu at intel.com
Mon Jan 8 03:49:27 CET 2018


1. Remove qga and utilize serial port for control session, two types
'telnet'|'socket' are supported for serial port
2. Protect qemu start action with parallel lock
3. Support attach function
4. Control virtual machine start process by start time/logout time/login
prompt/passwork prompt variables.

Signed-off-by: Marvin Liu <yong.liu at intel.com>

diff --git a/framework/qemu_kvm.py b/framework/qemu_kvm.py
index 84f961b..82933de 100644
--- a/framework/qemu_kvm.py
+++ b/framework/qemu_kvm.py
@@ -37,7 +37,8 @@ import os
 from virt_base import VirtBase
 from virt_base import ST_NOTSTART, ST_PAUSE, ST_RUNNING, ST_UNKNOWN
 from exception import StartVMFailedException
-from settings import get_host_ip
+from settings import get_host_ip, load_global_setting, DTS_PARALLEL_SETTING
+from utils import parallel_lock, RED
 
 # This name is derictly defined in the qemu guest serivce
 # So you can not change it except it is changed by the service
@@ -65,6 +66,17 @@ class QEMUKvm(VirtBase):
                 "fi"
 
     QEMU_IFUP_PATH = '/etc/qemu-ifup'
+    # Default login session timeout value
+    LOGIN_TIMEOUT = 60
+    # By default will wait 120 seconds for VM start
+    # If VM not ready in this period, will try restart it once
+    START_TIMEOUT = 120
+    # Default timeout value for operation when VM starting
+    OPERATION_TIMEOUT = 20
+    # Default login prompt
+    LOGIN_PROMPT = "login:"
+    # Default password prompt
+    PASSWORD_PROMPT = "Password:"
 
     def __init__(self, dut, vm_name, suite_name):
         super(QEMUKvm, self).__init__(dut, vm_name, suite_name)
@@ -79,9 +91,6 @@ class QEMUKvm(VirtBase):
         # initialize some resource used by guest.
         self.init_vm_request_resource()
 
-        QGA_CLI_PATH = '-r dep/QMP/'
-        self.host_session.copy_file_to(QGA_CLI_PATH)
-
         # charater and network device default index
         self.char_idx = 0
         self.netdev_idx = 0
@@ -105,15 +114,41 @@ class QEMUKvm(VirtBase):
         # if there is not the values of the specified options
         self.set_vm_default()
 
+        self.am_attached = False
+
+        # allow restart VM when can't login
+        self.restarted = False
+
+    def check_alive(self):
+        """
+        Check whether VM is alive for has been start up
+        """
+        pid_regx = r'p(\d+)'
+        out = self.host_session.send_expect("lsof -Fp /tmp/.%s.pid" % self.vm_name, "#", timeout=30)
+        for line in out.splitlines():
+            m = re.match(pid_regx, line)
+            if m:
+                self.host_logger.info("Found VM %s already running..." % m.group(0))
+                return True
+        return False
+
+    def kill_alive(self):
+        pid_regx = r'p(\d+)'
+        out = self.host_session.send_expect("lsof -Fp /tmp/.%s.pid" % self.vm_name, "# ")
+        for line in out.splitlines():
+            m = re.match(pid_regx, line)
+            if m:
+                self.host_session.send_expect("kill -9 %s" % m.group(0)[1:], "# ")
+
     def set_vm_default(self):
         self.set_vm_name(self.vm_name)
         if self.arch == 'aarch64':
             self.set_vm_machine('virt')
         self.set_vm_enable_kvm()
         self.set_vm_pid_file()
-        self.set_vm_qga()
         self.set_vm_daemon()
         self.set_vm_monitor()
+        self.set_vm_serial()
 
         if not self.__default_nic:
             # add default control interface
@@ -375,8 +410,7 @@ class QEMUKvm(VirtBase):
                 options['opt_media']:
             disk_boot_line += separator + 'media=%s' % options['opt_media']
 
-        if self.__string_has_multi_fields(disk_boot_line, separator):
-            self.__add_boot_line(disk_boot_line)
+        self.__add_boot_line(disk_boot_line)
 
     def add_vm_pflash(self, **options):
         """
@@ -386,6 +420,19 @@ class QEMUKvm(VirtBase):
             pflash_boot_line = '-pflash %s' % options['file']
             self.__add_boot_line(pflash_boot_line)
 
+    def add_vm_start(self, **options):
+        """
+        Update VM start and login related settings
+        """
+        if 'wait_seconds' in options.keys():
+            self.START_TIMEOUT = int(options['wait_seconds'])
+        if 'login_timeout' in options.keys():
+            self.LOGIN_TIMEOUT = int(options['login_timeout'])
+        if 'login_prompt' in options.keys():
+            self.LOGIN_PROMPT = options['login_prompt']
+        if 'password_prompt' in options.keys():
+            self.PASSWORD_PROMPT = options['password_prompt']
+
     def add_vm_login(self, **options):
         """
         user: login username of virtual machine
@@ -538,8 +585,11 @@ class QEMUKvm(VirtBase):
 
         # get the host port in the option
         host_port = field(opt_hostfwd, 2).split('-')[0]
+
+        # if no host assigned, just allocate it
         if not host_port:
-            host_port = str(self.virt_pool.alloc_port(self.vm_name))
+            host_port = str(self.virt_pool.alloc_port(self.vm_name, port_type='connect'))
+
         self.redir_port = host_port
 
         # get the guest addr
@@ -637,6 +687,9 @@ class QEMUKvm(VirtBase):
         else:
             self.params.append({'device': [opts]})
 
+        # start up time may increase after add device
+        self.START_TIMEOUT += 8
+
     def add_vm_device(self, **options):
         """
         driver: [pci-assign | virtio-net-pci | ...]
@@ -653,8 +706,8 @@ class QEMUKvm(VirtBase):
                 self.__add_vm_virtio_user_pci(**options)
             elif options['driver'] == 'vhost-cuse':
                 self.__add_vm_virtio_cuse_pci(**options)
-            if options['driver'] == 'vfio-pci':
-                self.__add_vm_pci_vfio(**options)
+            elif options['driver'] == 'vfio-pci':
+                 self.__add_vm_pci_vfio(**options)
 
     def __add_vm_pci_vfio(self, **options):
         """
@@ -708,7 +761,17 @@ class QEMUKvm(VirtBase):
         """
         separator = ','
         # chardev parameter
-        if 'opt_path' in options.keys() and options['opt_path']:
+        netdev_id = 'netdev%d' % self.netdev_idx
+        if 'opt_script' in options.keys() and options['opt_script']:
+            if 'opt_br' in options.keys() and \
+                    options['opt_br']:
+                bridge = options['opt_br']
+            else:
+                bridge = self.DEFAULT_BRIDGE
+            self.__generate_net_config_script(str(bridge))
+            dev_boot_line = '-netdev tap,id=%s,script=%s' % (netdev_id, options['opt_script'])
+            self.netdev_idx += 1
+        elif 'opt_path' in options.keys() and options['opt_path']:
             dev_boot_line = '-chardev socket'
             char_id = 'char%d' % self.char_idx
             if 'opt_server' in options.keys() and options['opt_server']:
@@ -734,12 +797,16 @@ class QEMUKvm(VirtBase):
                     netdev_id, char_id)
             self.__add_boot_line(dev_boot_line)
             # device parameter
-            opts = {'opt_netdev': '%s' % netdev_id}
-            if 'opt_mac' in options.keys() and \
-                    options['opt_mac']:
-                opts['opt_mac'] = options['opt_mac']
-            if 'opt_settings' in options.keys() and options['opt_settings']:
-                opts['opt_settings'] = options['opt_settings']
+        opts = {'opt_netdev': '%s' % netdev_id}
+        if 'opt_mac' in options.keys() and \
+                options['opt_mac']:
+            opts['opt_mac'] = options['opt_mac']
+        if 'opt_settings' in options.keys() and options['opt_settings']:
+            opts['opt_settings'] = options['opt_settings']
+        if 'opt_legacy' in options.keys() and options['opt_legacy']:
+            opts['opt_legacy'] = options['opt_legacy']
+        if 'opt_settings' in options.keys() and options['opt_settings']:
+            opts['opt_settings'] = options['opt_settings']
         self.__add_vm_virtio_net_pci(**opts)
 
     def __add_vm_virtio_cuse_pci(self, **options):
@@ -795,6 +862,9 @@ class QEMUKvm(VirtBase):
         if 'opt_addr' in options.keys() and \
                 options['opt_addr']:
             dev_boot_line += separator + 'addr=%s' % options['opt_addr']
+        if 'opt_legacy' in options.keys() and \
+                options['opt_legacy']:
+            dev_boot_line += separator + 'disable-modern=%s' % options['opt_legacy']
         if 'opt_settings' in options.keys() and \
                 options['opt_settings']:
             dev_boot_line += separator + '%s' % options['opt_settings']
@@ -841,41 +911,6 @@ class QEMUKvm(VirtBase):
         else:
             self.monitor_sock_path = None
 
-    def set_vm_qga(self, enable='yes'):
-        """
-        Set VM qemu-guest-agent.
-        """
-        index = self.find_option_index('qga')
-        if index:
-            self.params[index] = {'qga': [{'enable': '%s' % enable}]}
-        else:
-            self.params.append({'qga': [{'enable': '%s' % enable}]})
-        QGA_SOCK_PATH = QGA_SOCK_PATH_TEMPLATE % {'vm_name': self.vm_name}
-        self.qga_sock_path = QGA_SOCK_PATH
-
-    def add_vm_qga(self, **options):
-        """
-        enable: 'yes'
-        Make sure qemu-guest-agent servie up in vm
-        """
-        QGA_DEV_ID = '%(vm_name)s_qga0' % {'vm_name': self.vm_name}
-        QGA_SOCK_PATH = QGA_SOCK_PATH_TEMPLATE % {'vm_name': self.vm_name}
-
-        separator = ' '
-
-        if 'enable' in options.keys():
-            if options['enable'] == 'yes':
-                qga_boot_block = '-chardev socket,path=%(SOCK_PATH)s,server,nowait,id=%(ID)s' + \
-                                 separator + '-device virtio-serial' + separator + \
-                                 '-device virtserialport,chardev=%(ID)s,name=%(DEV_NAME)s'
-                qga_boot_line = qga_boot_block % {'SOCK_PATH': QGA_SOCK_PATH,
-                                                  'DEV_NAME': QGA_DEV_NAME,
-                                                  'ID': QGA_DEV_ID}
-                self.__add_boot_line(qga_boot_line)
-                self.qga_sock_path = QGA_SOCK_PATH
-            else:
-                self.qga_sock_path = ''
-
     def add_vm_migration(self, **options):
         """
         enable: yes
@@ -889,49 +924,135 @@ class QEMUKvm(VirtBase):
                     self.migrate_port = options['port']
                 else:
                     self.migrate_port = str(
-                        self.virt_pool.alloc_port(self.vm_name))
+                        self.virt_pool.alloc_port(self.vm_name), port_type="migrate")
                 migrate_boot_line = migrate_cmd % {
                     'migrate_port': self.migrate_port}
                 self.__add_boot_line(migrate_boot_line)
 
-    def add_vm_serial_port(self, **options):
+
+    def set_vm_serial(self):
         """
-        enable: 'yes'
+        Set serial device into options
         """
-        if 'enable' in options.keys():
-            if options['enable'] == 'yes':
-                self.serial_path = "/tmp/%s_serial.sock" % self.vm_name
-                serial_boot_line = '-serial unix:%s,server,nowait' % self.serial_path
-                self.__add_boot_line(serial_boot_line)
+        index = self.find_option_index('serial')
+        if index:
+            self.params[index] = {'serial': [{'type': 'telnet'}]}
+        else:
+            self.params.append({'serial': [{'type': 'telnet'}]})
+
+    def add_vm_serial(self, **options):
+        """
+        type : 'telnet' | 'socket'
+        """
+        self.serial_type = options['type']
+        if self.serial_type == 'telnet':
+            if 'port' in options:
+                self.serial_port = int(options['port'])
             else:
-                pass
+                self.serial_port = self.virt_pool.alloc_port(self.vm_name, port_type="serial")
+            serial_boot_line = '-display none -serial telnet::%d,server,nowait' % self.serial_port
+        else:
+            self.serial_path = "/tmp/%s_serial.sock" % self.vm_name
+            serial_boot_line = '-display none -serial unix:%s,server,nowait' % self.serial_path
+
+        self.__add_boot_line(serial_boot_line)
 
-    def connect_serial_port(self, name="", first=True):
+    def connect_serial_port(self, name=""):
         """
         Connect to serial port and return connected session for usage
         if connected failed will return None
         """
-        if getattr(self, 'serial_path', None):
-            self.serial_session = self.host_dut.new_session(suite=name)
-            self.serial_session.send_command("nc -U %s" % self.serial_path)
-            if first:
-                # login into Fedora os, not sure can work on all distributions
-                self.serial_session.send_expect("", "login:")
-                self.serial_session.send_expect(
-                    "%s" % self.username, "Password:")
-                self.serial_session.send_expect("%s" % self.password, "# ")
-            return self.serial_session
+        shell_reg = r"(\s*)\[(.*)\]# "
+        try:
+            if getattr(self, 'serial_session', None) is None:
+                self.serial_session = self.host_session
 
-        return None
+                self.serial_session.send_command("nc -U %s" % self.serial_path)
+
+            # login message not ouput if timeout too small
+            out = self.serial_session.send_command("", timeout=5).replace('\r', '').replace('\n', '')
 
-    def close_serial_port(self):
+            if len(out) == 0:
+                raise StartVMFailedException("Can't get output from [%s:%s]" % (self.host_dut.crb['My IP'], self.vm_name))
+
+            m = re.match(shell_reg, out)
+            if m:
+                # dmidecode output contain #, so use other matched string
+                out = self.serial_session.send_expect("dmidecode -t system", "Product Name", timeout=self.OPERATION_TIMEOUT)
+                # cleanup previous output
+                self.serial_session.get_session_before(timeout=0.1)
+
+                # if still on host, need reconnect
+                if 'QEMU' not in out:
+                    raise StartVMFailedException("Not real login [%s]" % self.vm_name)
+                else:
+                    # has enter into VM shell
+                    return True
+
+            # login into Redhat os, not sure can work on all distributions
+            if self.LOGIN_PROMPT not in out:
+                raise StartVMFailedException("Can't login [%s] now!!!" % self.vm_name)
+            else:
+                self.serial_session.send_expect("%s" % self.username, self.PASSWORD_PROMPT, timeout=self.LOGIN_TIMEOUT)
+                # system maybe busy here, enlarge timeout equal to login timeout
+                self.serial_session.send_expect("%s" % self.password, "#", timeout=self.LOGIN_TIMEOUT)
+                return self.serial_session
+        except Exception as e:
+            # when exception happened, force close serial connection and reconnect
+            print RED("[%s:%s] exception [%s] happened" % (self.host_dut.crb['My IP'], self.vm_name, str(e)))
+            self.close_serial_session(dut_id=self.host_dut.dut_id)
+            return False
+
+    def connect_telnet_port(self, name=""):
         """
-        Close serial session if it existed
+        Connect to serial port and return connected session for usage
+        if connected failed will return None
         """
-        if getattr(self, 'serial_session', None):
-            # exit from nc first
-            self.serial_session.send_expect("^C", "# ")
-            self.host_dut.close_session(self.serial_session)
+        shell_reg = r"(\s*)\[(.*)\]# "
+        scan_cmd = "lsof -i:%d | grep telnet | awk '{print $2}'" % self.serial_port
+
+        try:
+            # assume serial is not connect
+            if getattr(self, 'serial_session', None) is None:
+                self.serial_session = self.host_session
+
+                self.serial_session.send_expect("telnet localhost %d" % self.serial_port, "Connected to localhost", timeout=self.OPERATION_TIMEOUT)
+
+            # output will be empty if timeout too small
+            out = self.serial_session.send_command("", timeout=5).replace('\r', '').replace('\n', '')
+
+            # if no output from serial port, either connection close or system hang
+            if len(out) == 0:
+                raise StartVMFailedException("Can't get output from [%s]" % self.vm_name)
+
+            # if enter into shell
+            m = re.match(shell_reg, out)
+            if m:
+                # dmidecode output contain #, so use other matched string
+                out = self.serial_session.send_expect("dmidecode -t system", "Product Name", timeout=self.OPERATION_TIMEOUT)
+                # cleanup previous output
+                self.serial_session.get_session_before(timeout=0.1)
+
+                # if still on host, need reconnect
+                if 'QEMU' not in out:
+                    raise StartVMFailedException("Not real login [%s]" % self.vm_name)
+                else:
+                    # has enter into VM shell
+                    return True
+
+            # login into Redhat os, not sure can work on all distributions
+            if "x86_64 on an x86_64" not in out:
+                print RED("[%s:%s] not ready for login" % (self.host_dut.crb['My IP'], self.vm_name))
+                return False
+            else:
+                self.serial_session.send_expect("%s" % self.username, "Password:", timeout=self.LOGIN_TIMEOUT)
+                self.serial_session.send_expect("%s" % self.password, "#", timeout=self.LOGIN_TIMEOUT)
+                return True
+        except Exception as e:
+            # when exception happened, force close serial connection and reconnect
+            print RED("[%s:%s] exception [%s] happened" % (self.host_dut.crb['My IP'], self.vm_name, str(e)))
+            self.close_serial_session(dut_id=self.host_dut.dut_id)
+            return False
 
     def add_vm_vnc(self, **options):
         """
@@ -979,6 +1100,79 @@ class QEMUKvm(VirtBase):
             cmd = options['cmd']
         self.__add_boot_line(cmd)
 
+    def _check_vm_status(self):
+        """
+        Check and restart QGA if not ready, wait for network ready
+        """
+        self.__wait_vm_ready()
+
+        self.__wait_vmnet_ready()
+
+    def _attach_vm(self):
+        """
+        Attach VM
+        Collected information : serial/monitor/qga sock file
+                              : hostfwd address
+        """
+        self.am_attached = True
+
+        if not self._query_pid():
+            raise StartVMFailedException("Can't strip process pid!!!")
+
+        cmdline = self.host_session.send_expect('cat /proc/%d/cmdline' % self.pid, '# ')
+        qemu_boot_line = cmdline.replace('\x00', ' ')
+        self.qemu_boot_line = qemu_boot_line.split(' ', 1)[1]
+        self.qemu_emulator = qemu_boot_line.split(' ', 1)[0]
+
+        serial_reg = ".*serial\x00unix:(.*?),"
+        telnet_reg = ".*serial\x00telnet::(\d+),"
+        monitor_reg = ".*monitor\x00unix:(.*?),"
+        hostfwd_reg = ".*hostfwd=tcp:(.*):(\d+)-:"
+        migrate_reg = ".*incoming\x00tcp::(\d+)"
+
+        # support both telnet and unix domain socket serial device
+        m = re.match(serial_reg, cmdline)
+        if not m:
+            m1 = re.match(telnet_reg, cmdline)
+            if not m1:
+                raise StartVMFailedException("No serial sock available!!!")
+            else:
+                self.serial_port = int(m1.group(1))
+                self.serial_type = "telnet"
+        else:
+            self.serial_path = m.group(1)
+            self.serial_type = "socket"
+
+        m = re.match(monitor_reg, cmdline)
+        if not m:
+            raise StartVMFailedException("No monitor sock available!!!")
+        self.monitor_sock_path = m.group(1)
+
+        m = re.match(hostfwd_reg, cmdline)
+        if not m:
+            raise StartVMFailedException("No host fwd config available!!!")
+
+        self.net_type = 'hostfwd'
+        self.host_port = m.group(2)
+        self.hostfwd_addr = m.group(1) + ':' + self.host_port
+
+        # record start time, need call before check_vm_status
+        self.start_time = time.time()
+
+        try:
+            self.update_status()
+        except:
+            self.host_logger.error("Can't query vm status!!!")
+
+        if self.vm_status is not ST_PAUSE:
+            self._check_vm_status()
+        else:
+            m = re.match(migrate_reg, cmdline)
+            if not m:
+                raise StartVMFailedException("No migrate port available!!!")
+
+            self.migrate_port = int(m.group(1))
+
     def _start_vm(self):
         """
         Start VM.
@@ -987,25 +1181,86 @@ class QEMUKvm(VirtBase):
 
         qemu_boot_line = self.generate_qemu_boot_line()
 
-        # Start VM using the qemu command
-        ret = self.host_session.send_expect(qemu_boot_line, '# ', verify=True)
-        if type(ret) is int and ret != 0:
-            raise StartVMFailedException('Start VM failed!!!')
+        self.__send_qemu_cmd(qemu_boot_line, dut_id=self.host_dut.dut_id)
 
         self.__get_pci_mapping()
 
         # query status
         self.update_status()
 
+        # sleep few seconds for bios/grub
+        time.sleep(10)
+
         # when vm is waiting for migration, can't ping
         if self.vm_status is not ST_PAUSE:
-            # if VM waiting for migration, can't return ping
-            out = self.__control_session('ping', '120')
-            if "Not responded" in out:
-                raise StartVMFailedException('Not response in 120 seconds!!!')
+            self.__wait_vm_ready()
 
             self.__wait_vmnet_ready()
 
+    # Start VM using the qemu command
+    # lock critical action like start qemu
+    @parallel_lock(num=4)
+    def __send_qemu_cmd(self, qemu_boot_line, dut_id):
+        # add more time for qemu start will be slow when system is busy
+        ret = self.host_session.send_expect(qemu_boot_line, '# ', verify=True, timeout=30)
+
+        # record start time
+        self.start_time = time.time()
+
+        # wait for qemu process ready
+        time.sleep(2)
+        if type(ret) is int and ret != 0:
+            raise StartVMFailedException('Start VM failed!!!')
+
+    def _quick_start_vm(self):
+        self.__alloc_assigned_pcis()
+
+        qemu_boot_line = self.generate_qemu_boot_line()
+
+        self.__send_qemu_cmd(qemu_boot_line, dut_id=self.host_dut.dut_id)
+
+        self.__get_pci_mapping()
+
+        # query status
+        self.update_status()
+
+        # sleep few seconds for bios and grub
+        time.sleep(10)
+
+    def __ping_vm(self):
+        logged_in = False
+        cur_time = time.time()
+        time_diff = cur_time - self.start_time
+        try_times = 0
+        while (time_diff < self.START_TIMEOUT):
+            if self.control_session('ping') == "Success":
+                logged_in = True
+                break
+
+            # update time consume
+            cur_time = time.time()
+            time_diff = cur_time - self.start_time
+
+            self.host_logger.warning("Can't login [%s] on [%s], retry %d times!!!" % (self.vm_name, self.host_dut.crb['My IP'], try_times + 1))
+            time.sleep(self.OPERATION_TIMEOUT)
+            try_times += 1
+            continue
+
+        return logged_in
+
+    def __wait_vm_ready(self):
+        logged_in = self.__ping_vm()
+        if not logged_in:
+            if not self.restarted:
+                # make sure serial session has been quit
+                self.close_serial_session(dut_id=self.host_dut.dut_id)
+                self.vm_status = ST_NOTSTART
+                self._stop_vm()
+                self.restarted = True
+                self._start_vm()
+            else:
+                raise StartVMFailedException('Not response in %d seconds!!!' % self.START_TIMEOUT)
+
     def start_migration(self, remote_ip, remote_port):
         """
         Send migration command to host and check whether start migration
@@ -1047,18 +1302,14 @@ class QEMUKvm(VirtBase):
         """
         Generate the whole QEMU boot line.
         """
-        qemu_emulator = self.qemu_emulator
-
-        if self.vcpus_pinned_to_vm.strip():
-            vcpus = self.__alloc_vcpus()
-
-            if vcpus.strip():
-                qemu_boot_line = 'taskset -c %s ' % vcpus + \
-                    qemu_emulator + ' ' + \
-                    self.qemu_boot_line
+        if self.vcpus_pinned_to_vm:
+            vcpus = self.vcpus_pinned_to_vm.replace(' ', ',')
+            qemu_boot_line = 'taskset -c %s ' % vcpus + \
+                             self.qemu_emulator + ' ' + \
+                             self.qemu_boot_line
         else:
-            qemu_boot_line = qemu_emulator + ' ' + \
-                self.qemu_boot_line
+            qemu_boot_line = self.qemu_emulator + ' ' + \
+                             self.qemu_boot_line
 
         return qemu_boot_line
 
@@ -1067,16 +1318,12 @@ class QEMUKvm(VirtBase):
         wait for 120 seconds for vm net ready
         10.0.2.* is the default ip address allocated by qemu
         """
-        count = 40
-        while count:
-            out = self.__control_session('ifconfig')
-            if "10.0.2" in out:
-                return True
-            time.sleep(6)
-            count -= 1
-
-        raise StartVMFailedException(
-            'Virtual machine control net not ready in 120 seconds!!!')
+        ret = self.control_session("network")
+        # network has been ready, just return
+        if ret == "Success":
+            return True
+        else:
+            raise StartVMFailedException('Virtual machine control net not ready!!!')
 
     def __alloc_vcpus(self):
         """
@@ -1201,10 +1448,10 @@ class QEMUKvm(VirtBase):
         """
         Get IP which VM is connected by bridge.
         """
-        out = self.__control_session('ping', '60')
+        out = self.control_session('ping', '60')
         if not out:
             time.sleep(10)
-            out = self.__control_session('ifconfig')
+            out = self.control_session('ifconfig')
             ips = re.findall(r'inet (\d+\.\d+\.\d+\.\d+)', out)
 
             if '127.0.0.1' in ips:
@@ -1267,7 +1514,7 @@ class QEMUKvm(VirtBase):
         Query and update VM status
         """
         out = self.__monitor_session('info', 'status')
-        self.host_logger.info("Virtual machine status: %s" % out)
+        self.host_logger.warning("Virtual machine status: %s" % out)
 
         if 'paused' in out:
             self.vm_status = ST_PAUSE
@@ -1278,12 +1525,23 @@ class QEMUKvm(VirtBase):
 
         info = self.host_session.send_expect('cat %s' % self.__pid_file, "# ")
         try:
-            pid = int(info)
+            pid = int(info.split()[0])
             # save pid into dut structure
             self.host_dut.virt_pids.append(pid)
         except:
             self.host_logger.info("Failed to capture pid!!!")
 
+    def _query_pid(self):
+        info = self.host_session.send_expect('cat %s' % self.__pid_file, "# ")
+        try:
+            # sometimes saw to lines in pid file
+            pid = int(info.splitlines()[0])
+            # save pid into dut structure
+            self.pid = pid
+            return True
+        except:
+            return False
+
     def __strip_guest_pci(self):
         """
         Strip all pci-passthrough device information, based on qemu monitor
@@ -1315,43 +1573,154 @@ class QEMUKvm(VirtBase):
 
         return pcis
 
-    def __control_session(self, command, *args):
+    def __strip_guest_core(self):
+        """
+        Strip all lcore-thread binding information
+        Return array will be [thread0, thread1, ...]
+        """
+        cores = []
+        # CPU #0: pc=0xffffffff8104c416 (halted) thread_id=40677
+        core_reg = r'^.*CPU #(\d+): (.*) thread_id=(\d+)'
+        out = self.__monitor_session('info', 'cpus')
+
+        if out is None:
+            return cores
+
+        lines = out.split("\r\n")
+        for line in lines:
+            m = re.match(core_reg, line)
+            if m:
+                cores.append(int(m.group(3)))
+
+        return cores
+
+    def handle_serial_session(func):
+        """
+        Wrapper function to handle serial port, must return serial to host session
+        """
+        def _handle_serial_session(self, command):
+            # just raise error if connect failed, for func can't all any more
+            try:
+                if self.serial_type == 'socket':
+                    assert (self.connect_serial_port(name=self.vm_name)), "Can't connect to serial socket"
+                elif self.serial_type == 'telnet':
+                    assert (self.connect_telnet_port(name=self.vm_name)), "Can't connect to serial port"
+            except:
+                return 'Failed'
+
+            try:
+                out = func(self, command)
+                self.quit_serial_session()
+                return out
+            except Exception as e:
+                print RED("Exception happend on [%s] serial with cmd [%s]" % (self.vm_name, command))
+                print RED(e)
+                self.close_serial_session(dut_id=self.host_dut.dut_id)
+                return 'Failed'
+
+        return _handle_serial_session
+
+    def quit_serial_session(self):
+        """
+        Quit from serial session gracefully
         """
-        Use the qemu guest agent service to control VM.
+        if self.serial_type == 'socket':
+            self.serial_session.send_expect("^C", "# ")
+        elif self.serial_type == 'telnet':
+            self.serial_session.send_command("^]")
+            self.serial_session.send_command("quit")
+        self.serial_session = None
+
+    @parallel_lock()
+    def close_serial_session(self, dut_id):
+        """
+        Force kill serial connection from DUT when exception happened
+        """
+        # return serial_session to host_session
+        if self.serial_type == 'socket':
+            scan_cmd = "ps -e -o pid,cmd  |grep 'nc -U %s' |grep -v grep" % self.serial_path
+            out = self.host_dut.send_expect(scan_cmd, "#")
+            proc_info = out.strip().split()
+            try:
+                pid = int(proc_info[0])
+                self.host_dut.send_expect('kill %d' % pid, "#")
+            except:
+                pass
+            self.host_dut.send_expect("", "# ")
+        elif self.serial_type == 'telnet':
+            scan_cmd = "lsof -i:%d | grep telnet | awk '{print $2}'" % self.serial_port
+            proc_info = self.host_dut.send_expect(scan_cmd, "#")
+            try:
+                pid = int(proc_info)
+                self.host_dut.send_expect('kill %d' % pid, "#")
+            except:
+                pass
+
+        self.serial_session = None
+        return
+
+    @handle_serial_session
+    def control_session(self, command):
+        """
+        Use the serial port to control VM.
         Note:
             :command: there are these commands as below:
-                       cat, fsfreeze, fstrim, halt, ifconfig, info,\
-                       ping, powerdown, reboot, shutdown, suspend
+                    ping, network, powerdown
             :args: give different args by the different commands.
         """
-        if not self.qga_sock_path:
-            self.host_logger.info(
-                "No QGA service between host [ %s ] and guest [ %s ]" %
-                (self.host_dut.NAME, self.vm_name))
-            return None
-
-        cmd_head = '~/QMP/' + \
-            "qemu-ga-client " + \
-            "--address=%s %s" % \
-            (self.qga_sock_path, command)
-
-        cmd = cmd_head
-        for arg in args:
-            cmd = cmd_head + ' ' + str(arg)
 
-        if command is "ping":
-            out = self.host_session.send_expect(cmd, '# ', int(args[0]))
+        if command == "ping":
+            # disable stty input characters for send_expect function
+            self.serial_session.send_expect("stty -echo", "#", timeout=self.OPERATION_TIMEOUT)
+            return "Success"
+        elif command == "network":
+            intf = self.serial_session.send_expect("ls -1 /sys/bus/pci/devices/0000:00:1f.0/net", "#", timeout=self.OPERATION_TIMEOUT)
+            out = self.serial_session.send_expect("ifconfig %s" % intf, "#", timeout=self.OPERATION_TIMEOUT)
+            if "10.0.2" not in out:
+                self.serial_session.send_expect("dhclient %s -timeout 10" % intf, "#", timeout=30)
+            else:
+                return "Success"
+
+            out = self.serial_session.send_expect("ifconfig", "#", timeout=self.OPERATION_TIMEOUT)
+            if "10.0.2" not in out:
+                return "Failed"
+
+            return "Success"
+        elif command == "powerdown":
+            self.serial_session.send_command("init 0")
+            # conflict with handle_serial_session
+            if self.serial_type == "socket":
+                self.serial_session.send_expect("^C", "# ")
+            elif self.serial_type == "telnet":
+                self.serial_session.send_command("^]")
+                self.serial_session.send_command("quit")
+            time.sleep(10)
+            self.kill_alive()
+            return "Success"
         else:
-            out = self.host_session.send_expect(cmd, '# ')
-
-        return out
+            out = self.serial_session.send_command(command)
+            return out
 
     def _stop_vm(self):
         """
         Stop VM.
         """
         if self.vm_status is ST_RUNNING:
-            self.__control_session('powerdown')
+            self.control_session('powerdown')
         else:
             self.__monitor_session('quit')
         time.sleep(5)
+        # remove temporary file
+        self.host_session.send_expect("rm -f %s" % self.__pid_file, "#")
+
+    def pin_threads(self, lcores):
+        """
+        Pin thread to assigned cores
+        """
+        thread_reg = r'CPU #(\d+): .* thread_id=(\d+)'
+        output = self.__monitor_session('info', 'cpus')
+        thread_cores = re.findall(thread_reg, output)
+        cores_map = zip(thread_cores, lcores)
+        for thread_info, core_id in cores_map:
+            cpu_id, thread_id = thread_info
+            self.host_session.send_expect("taskset -pc %d %s" % (core_id, thread_id), "#")
-- 
1.9.3



More information about the dts mailing list