From eac9152e6edc916ce37b97fe854a792e95f16f96 Mon Sep 17 00:00:00 2001
From: Zhu Huankai <zhuhuankai1@huawei.com>
Date: Tue, 12 Oct 2021 15:52:05 +0800
Subject: [PATCH 2/4] hydropper/basevm: adapt basevm

Signed-off-by: Zhu Huankai <zhuhuankai1@huawei.com>
---
 tests/hydropper/virt/basevm.py | 328 ++++++++++++++-------------------
 1 file changed, 140 insertions(+), 188 deletions(-)

diff --git a/tests/hydropper/virt/basevm.py b/tests/hydropper/virt/basevm.py
index 0766068..d44c4e4 100644
--- a/tests/hydropper/virt/basevm.py
+++ b/tests/hydropper/virt/basevm.py
@@ -57,8 +57,9 @@ class BaseVM:
             wrapper = []
         if args is None:
             args = []
-        self.__qmp = None
-        self.__qmp_set = True
+        self.qmp_protocol = None
+        # self.__qmp = None
+        # self.__qmp_set = True
         # Copy args in case ew modify them.
         self._args = list(args)
         self._console_address = None
@@ -149,10 +150,10 @@ class BaseVM:
 
         self._pre_shutdown()
         if self.daemon or self.is_running():
-            if self.__qmp:
+            if self.qmp_protocol.qmp_set:
                 try:
                     if not has_quit:
-                        self.cmd('quit')
+                        self.qmp_protocol.qmp_command('quit')
                         self.event_wait(name='SHUTDOWN', timeout=10,
                                         match={'data': {'guest': False, 'reason': 'host-qmp-quit'}})
                 # Kill popen no matter what exception occurs
@@ -218,8 +219,8 @@ class BaseVM:
                 command = ''
             LOG.warning(msg, exitcode, command)
 
-        if self.__qmp:
-            self.close_sock()
+        if self.qmp_protocol.qmp_set:
+            self.qmp_protocol.close_sock()
 
         if self.serial_session:
             self.serial_session.run_func("close")
@@ -234,15 +235,14 @@ class BaseVM:
             subprocess.run("rm -rf %s" % self.pidfile, shell=True, check=True)
 
     def _pre_launch(self):
-        if self.__qmp_set:
-            if self._monitor_address is not None:
-                self._vm_monitor = self._monitor_address
-                if not isinstance(self._vm_monitor, tuple):
-                    self._remove_files.append(self._vm_monitor)
-            else:
-                self._vm_monitor = os.path.join(self._sock_dir,
-                                                self._name + "_" + self.vmid + ".sock")
+        if self._monitor_address is not None:
+            self._vm_monitor = self._monitor_address
+            if not isinstance(self._vm_monitor, tuple):
                 self._remove_files.append(self._vm_monitor)
+        else:
+            self._vm_monitor = os.path.join(self._sock_dir,
+                                            self._name + "_" + self.vmid + ".sock")
+            self._remove_files.append(self._vm_monitor)
 
         self.parser_config_to_args()
 
@@ -340,11 +340,11 @@ class BaseVM:
     def post_launch_qmp(self):
         """Set a QMPMonitorProtocol"""
         if isinstance(self.mon_sock, tuple):
-            self.qmp_monitor_protocol(self.mon_sock)
+            self.qmp_protocol = QMPProtocol(self.mon_sock)
         else:
-            self.qmp_monitor_protocol(self._vm_monitor)
-        if self.__qmp:
-            self.connect()
+            self.qmp_protocol = QMPProtocol(self._vm_monitor)
+        if self.qmp_protocol.qmp_set:
+            self.qmp_protocol.connect()
 
     def post_launch_vnet(self):
         """Nothing is needed at present"""
@@ -444,17 +444,16 @@ class BaseVM:
         # uuid is not supported yet, no need to add uuid
         if self.logpath:
             args.extend(['-D', self.logpath])
-        if self.__qmp_set:
-            if "stratovirt" in self.vmtype:
-                if isinstance(self.mon_sock, tuple):
-                    args.extend(['-qmp',
-                                 "tcp:" + str(self.mon_sock[0]) + ":" + str(self.mon_sock[1])])
-                else:
-                    args.extend(['-qmp', "unix:" + self.mon_sock + ",server,nowait"])
+        if "stratovirt" in self.vmtype:
+            if isinstance(self.mon_sock, tuple):
+                args.extend(['-qmp',
+                                "tcp:" + str(self.mon_sock[0]) + ":" + str(self.mon_sock[1])])
             else:
-                moncdev = 'socket,id=mon,path=%s' % self.mon_sock
-                args.extend(['-chardev', moncdev, '-mon',
-                             'chardev=mon,mode=control'])
+                args.extend(['-qmp', "unix:" + self.mon_sock + ",server,nowait"])
+        else:
+            moncdev = 'socket,id=mon,path=%s' % self.mon_sock
+            args.extend(['-chardev', moncdev, '-mon',
+                            'chardev=mon,mode=control'])
 
         if self.withpid:
             self.pidfile = os.path.join(self._sock_dir, self._name + "_" + "pid.file")
@@ -717,33 +716,33 @@ class BaseVM:
 
     def stop(self):
         """Pause all vcpu"""
-        return self.qmp_command("stop")
+        return self.qmp_protocol.qmp_command("stop")
 
     def cont(self):
         """Resume paused vcpu"""
-        return self.qmp_command("cont")
+        return self.qmp_protocol.qmp_command("cont")
 
     def device_add(self, **kwargs):
         """Hotplug device"""
-        return self.qmp_command("device_add", **kwargs)
+        return self.qmp_protocol.qmp_command("device_add", **kwargs)
 
     def device_del(self, **kwargs):
         """Unhotplug device"""
-        return self.qmp_command("device_del", **kwargs)
+        return self.qmp_protocol.qmp_command("device_del", **kwargs)
 
     def netdev_add(self, **kwargs):
         """Hotplug a netdev"""
-        return self.qmp_command("netdev_add", **kwargs)
+        return self.qmp_protocol.qmp_command("netdev_add", **kwargs)
 
     def netdev_del(self, **kwargs):
         """Unhotplug a netdev"""
-        return self.qmp_command("netdev_del", **kwargs)
+        return self.qmp_protocol.qmp_command("netdev_del", **kwargs)
 
     def add_disk(self, diskpath, index=1, check=True):
         """Hotplug a disk to vm"""
         LOG.debug("hotplug disk %s to vm" % diskpath)
         devid = "drive-%d" % index
-        resp = self.qmp_command("blockdev-add", node_name="drive-%d" % index,
+        resp = self.qmp_protocol.qmp_command("blockdev-add", node_name="drive-%d" % index,
                                 file={"driver": "file", "filename": diskpath})
 
         LOG.debug("blockdev-add return %s" % resp)
@@ -800,49 +799,67 @@ class BaseVM:
 
     def query_hotpluggable_cpus(self):
         """Query hotpluggable cpus"""
-        return self.qmp_command("query-hotpluggable-cpus")
+        return self.qmp_protocol.qmp_command("query-hotpluggable-cpus")
 
     def query_cpus(self):
         """Query cpus"""
-        return self.qmp_command("query-cpus")
+        return self.qmp_protocol.qmp_command("query-cpus")
 
     def query_status(self):
         """Query status"""
-        return self.qmp_command("query-status")
+        return self.qmp_protocol.qmp_command("query-status")
 
     def query_balloon(self):
         """Query balloon size"""
-        return self.qmp_command("query-balloon")
+        return self.qmp_protocol.qmp_command("query-balloon")
 
     def balloon_set(self, **kwargs):
         """Set balloon size"""
-        return self.qmp_command("balloon", **kwargs)
+        return self.qmp_protocol.qmp_command("balloon", **kwargs)
 
-    def qmp_monitor_protocol(self, address):
-        """Set QMPMonitorProtocol"""
-        self.__qmp = {'events': [],
-                      'address': address,
-                      'sock': socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-                      }
+    def qmp_reconnect(self):
+        """Reconnect qmp when sock is dead"""
+        if self.qmp_protocol:
+            self.qmp_protocol.close_sock()
+        if isinstance(self.mon_sock, tuple):
+            self.qmp_protocol = QMPProtocol(self.mon_sock)
+        else:
+            self.qmp_protocol = QMPProtocol(self._vm_monitor)
+        if self.qmp_protocol:
+            self.qmp_protocol.connect()
 
-    def enable_qmp_set(self):
-        """
-        Enable qmp monitor
-        set in preparation phase
+    def event_wait(self, name, timeout=60.0, match=None):
         """
-        self.__qmp_set = True
+        Wait for an qmp event to match exception event.
 
-    def disable_qmp_set(self):
-        """
-        Disable qmp monitor
-        set in preparation phase
+        Args:
+            match: qmp match event, such as
+            {'data':{'guest':False,'reason':'host-qmp-quit'}}
         """
-        self.__qmp = None
-        self.__qmp_set = False
+        while True:
+            event = self.qmp_protocol.get_events(wait=timeout, only_event=True)
+            try:
+                if event['event'] == name:
+                    for key in match:
+                        if key in event and match[key] == event[key]:
+                            return event
+            except TypeError:
+                if event['event'] == name:
+                    return event
+            self._events.append(event)
+
+class QMPProtocol:
+    
+    '''Set qmp monitor protocol'''
+    def __init__(self, address):
+        self.qmp_set = True
+        self.events = list()
+        self.address = address
+        self.sock =  socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
     def __sock_recv(self, only_event=False):
         """Get data from socket"""
-        recv = self.__qmp['sock'].recv(1024).decode('utf-8').split('\n')
+        recv = self.sock.recv(1024).decode('utf-8').split('\n')
         if recv and not recv[-1]:
             recv.pop()
         resp = None
@@ -850,84 +867,13 @@ class BaseVM:
             resp = json.loads(recv.pop(0))
             if 'event' not in resp:
                 return resp
-            self.logger.debug("-> %s", resp)
-            self.__qmp['events'].append(resp)
+            LOG.debug("-> %s", resp)
+            self.events.append(resp)
             if only_event:
                 return resp
         return resp
 
-    def get_events(self, wait=False, only_event=False):
-        """
-        Get new events or event from socket.
-        Push them to __qmp['events']
-
-        Args:
-            wait (bool): block until an event is available.
-            wait (float): If wait is a float, treat it as a timeout value.
-
-        Raises:
-            QMPTimeoutError: If a timeout float is provided and the timeout
-            period elapses.
-            QMPConnectError: If wait is True but no events could be retrieved
-            or if some other error occurred.
-        """
-
-        # Wait for new events, if needed.
-        # if wait is 0.0, this means "no wait" and is also implicitly false.
-        if not self.__qmp['events'] and wait:
-            if isinstance(wait, float):
-                self.__qmp['sock'].settimeout(wait)
-            try:
-                ret = self.__sock_recv(only_event=True)
-            except socket.timeout:
-                raise QMPTimeoutError("Timeout waiting for event")
-            except:
-                raise QMPConnectError("Error while receiving from socket")
-            if ret is None:
-                raise QMPConnectError("Error while receiving from socket")
-            self.__qmp['sock'].settimeout(None)
-
-        if self.__qmp['events']:
-            if only_event:
-                return self.__qmp['events'].pop(0)
-            return self.__qmp['events']
-        return None
-
-    def connect(self):
-        """
-        Connect to the QMP Monitor and perform capabilities negotiation.
-
-        Returns:
-            QMP greeting if negotiate is true
-            None if negotiate is false
-
-        Raises:
-            QMPConnectError if the greeting is not received or QMP not in greetiong
-            QMPCapabilitiesError if fails to negotiate capabilities
-        """
-        self.__qmp['sock'].connect(self.__qmp['address'])
-        greeting = self.__sock_recv()
-        if greeting is None or "QMP" not in greeting:
-            raise QMPConnectError
-        # Greeting seems ok, negotiate capabilities
-        resp = self.cmd('qmp_capabilities')
-        if resp and "return" in resp:
-            return greeting
-        raise QMPCapabilitiesError
-
-    def qmp_reconnect(self):
-        """Reconnect qmp when sock is dead"""
-        if self.__qmp:
-            self.close_sock()
-
-        if isinstance(self.mon_sock, tuple):
-            self.qmp_monitor_protocol(self.mon_sock)
-        else:
-            self.qmp_monitor_protocol(self._vm_monitor)
-        if self.__qmp:
-            self.connect()
-
-    def cmd(self, name, args=None, cmd_id=None):
+    def _cmd(self, name, args=None, cmd_id=None):
         """
         Build a QMP command and send it to the monitor.
 
@@ -942,41 +888,17 @@ class BaseVM:
         if cmd_id:
             qmp_cmd.update({'id': cmd_id})
 
-        self.logger.debug("<- %s", qmp_cmd)
+        LOG.debug("<- %s", qmp_cmd)
         try:
-            self.__qmp['sock'].sendall(json.dumps(qmp_cmd).encode('utf-8'))
+            self.sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
         except OSError as err:
             if err.errno == errno.EPIPE:
                 return None
             raise err
         resp = self.__sock_recv()
-        self.logger.debug("-> %s", resp)
+        LOG.debug("-> %s", resp)
         return resp
 
-    def error_cmd(self, cmd, **kwds):
-        """Build and send a QMP command to the monitor, report errors if any"""
-        ret = self.cmd(cmd, kwds)
-        if "error" in ret:
-            raise Exception(ret['error']['desc'])
-        return ret['return']
-
-    def clear_events(self):
-        """Clear current list of pending events."""
-        self.__qmp['events'] = []
-
-    def close_sock(self):
-        """Close the socket and socket file."""
-        if self.__qmp['sock']:
-            self.__qmp['sock'].close()
-
-    def settimeout(self, timeout):
-        """Set the socket timeout."""
-        self.__qmp['sock'].settimeout(timeout)
-
-    def is_af_unix(self):
-        """Check if the socket family is AF_UNIX."""
-        return socket.AF_UNIX == self.__qmp['sock'].family
-
     def qmp_command(self, cmd, **args):
         """Run qmp command"""
         qmp_dict = dict()
@@ -986,46 +908,76 @@ class BaseVM:
             else:
                 qmp_dict[key] = value
 
-        rep = self.cmd(cmd, args=qmp_dict)
+        rep = self._cmd(cmd, args=qmp_dict)
         if rep is None:
             raise QMPError("Monitor was closed")
 
         return rep
 
-    def qmp_event_acquire(self, wait=False, return_list=False):
+    def connect(self):
         """
-        Get qmp event or events.
+        Connect to the QMP Monitor and perform capabilities negotiation.
 
-        Args:
-            return_list: if return_list is True, then return qmp
-            events. Else, return a qmp event.
+        Returns:
+            QMP greeting if negotiate is true
+            None if negotiate is false
+
+        Raises:
+            QMPConnectError if the greeting is not received or QMP not in greetiong
+            QMPCapabilitiesError if fails to negotiate capabilities
         """
-        if not return_list:
-            if not self._events:
-                return self.get_events(wait=wait, only_event=True)
-            return self._events.pop(0)
-        event_list = self.get_events(wait=wait)
-        event_list.extend(self._events)
-        self._events.clear()
-        self.clear_events()
-        return event_list
+        self.sock.connect(self.address)
+        greeting = self.__sock_recv()
+        if greeting is None or "QMP" not in greeting:
+            raise QMPConnectError
+        # Greeting seems ok, negotiate capabilities
+        resp = self._cmd('qmp_capabilities')
+        if resp and "return" in resp:
+            return greeting
+        raise QMPCapabilitiesError
 
-    def event_wait(self, name, timeout=60.0, match=None):
+    def clear_events(self):
+        """Clear current list of pending events."""
+        self.events = []
+
+    def close_sock(self):
+        """Close the socket and socket file."""
+        if self.sock:
+            self.sock.close()
+
+    def get_events(self, wait=False, only_event=False):
         """
-        Wait for an qmp event to match exception event.
+        Get new events or event from socket.
+        Push them to __qmp['events']
 
         Args:
-            match: qmp match event, such as
-            {'data':{'guest':False,'reason':'host-qmp-quit'}}
+            wait (bool): block until an event is available.
+            wait (float): If wait is a float, treat it as a timeout value.
+
+        Raises:
+            QMPTimeoutError: If a timeout float is provided and the timeout
+            period elapses.
+            QMPConnectError: If wait is True but no events could be retrieved
+            or if some other error occurred.
         """
-        while True:
-            event = self.get_events(wait=timeout, only_event=True)
+
+        # Wait for new events, if needed.
+        # if wait is 0.0, this means "no wait" and is also implicitly false.
+        if not self.events and wait:
+            if isinstance(wait, float):
+                self.sock.settimeout(wait)
             try:
-                if event['event'] == name:
-                    for key in match:
-                        if key in event and match[key] == event[key]:
-                            return event
-            except TypeError:
-                if event['event'] == name:
-                    return event
-            self._events.append(event)
+                ret = self.__sock_recv(only_event=True)
+            except socket.timeout:
+                raise QMPTimeoutError("Timeout waiting for event")
+            except:
+                raise QMPConnectError("Error while receiving from socket")
+            if ret is None:
+                raise QMPConnectError("Error while receiving from socket")
+            self.sock.settimeout(None)
+
+        if self.events:
+            if only_event:
+                return self.events.pop(0)
+            return self.events
+        return None
\ No newline at end of file
-- 
2.30.0

