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

[RFC XEN PATCH 05/13] pytest: Introduce reading the console, and add a test



From: Anthony PERARD <anthony.perard@xxxxxxxxxx>

The test will do more than just checking dom0 booted in a later patch.

Signed-off-by: Anthony PERARD <anthony.perard@xxxxxxxxxx>
---
 automation/pytest/lib/ipmi.py    | 86 ++++++++++++++++++++++++++++++++
 automation/pytest/pyproject.toml |  1 +
 automation/pytest/test_boot.py   | 24 +++++++++
 3 files changed, 111 insertions(+)

diff --git a/automation/pytest/lib/ipmi.py b/automation/pytest/lib/ipmi.py
index 0d3b085f704c..bda5d8077577 100644
--- a/automation/pytest/lib/ipmi.py
+++ b/automation/pytest/lib/ipmi.py
@@ -1,6 +1,8 @@
 import logging
 import paramiko
 import re
+import time
+from typing import Generator
 
 
 class IPMI:
@@ -67,3 +69,87 @@ class IPMI:
 
     def power_off(self):
         self._power('off force')
+
+    def get_console(self):
+        logging.info(f"Start console acquisition on {self.blade_node}")
+        ssh = self._new_client()
+        channel = ssh.invoke_shell()
+
+        output = ""
+        logging.debug("wait for prompt")
+        while True:
+            if channel.recv_ready():
+                output += channel.recv(9999).decode('utf-8')
+                # wait for prompt
+                logging.debug(output)
+                if output.strip().endswith('hpiLO->'):
+                    logging.debug("got prompt")
+                    break
+            elif channel.exit_status_ready():
+                raise Exception("Shell closed unexpectedly.")
+            else:
+                time.sleep(0.1)  # Wait a bit for more output
+
+        logging.debug("run connect node vsp acquire")
+        cmd = f"connect node vsp acquire {self.blade_node}\n"
+        channel.send(cmd)
+
+        self.channel = channel
+
+    def console_output_generator(self) -> Generator[str]:
+        if not self.channel:
+            raise Exception("Must call get_console")
+        channel = self.channel
+        previous_output = ''
+        # `count` when this reach 0, we had a line from the console that isn't
+        # finished, like a prompt, for sometime already, so display it in the
+        # debug.
+        count = 0
+        ansi_escape = re.compile(r'''
+            \x1B  # ESC
+            (?:   # 7-bit C1 Fe (except CSI)
+                [@-Z\\-_]
+            |     # or [ for CSI, followed by a control sequence
+                \[
+                [0-?]*  # Parameter bytes
+                [ -/]*  # Intermediate bytes
+                [@-~]   # Final byte
+            )
+        ''', re.VERBOSE)
+        while True:
+            if channel.recv_ready():
+                output = previous_output
+                output += channel.recv(9999).decode('utf-8')
+                output_lines = output.splitlines(keepends=True)
+                for line in output_lines[:-1]:
+                    line = ansi_escape.sub('', line)
+                    line = line.rstrip()
+                    if line in ['', '\r', '\r\n']:
+                        continue
+                    logging.debug(line)
+                    yield line
+                previous_output = output_lines[-1]
+                if previous_output.endswith('\n'):
+                    line = previous_output
+                    line = ansi_escape.sub('', line)
+                    line = line.rstrip()
+                    if line not in ['', '\r', '\r\n']:
+                        logging.debug(line)
+                    previous_output = ''
+                    count = 0
+                else:
+                    count = 20
+                # previous_output = output[-len(expect):]
+            # Check if the shell closed unexpectedly
+            elif channel.exit_status_ready():
+                raise Exception("Shell closed unexpectedly.")
+            else:
+                if count > 1:
+                    count -= 1
+                elif count == 1:
+                    line = previous_output
+                    line = ansi_escape.sub('', line)
+                    if line not in ['\r']:
+                        logging.debug("XXXXX output buffer: %s XXXXXX" % 
repr(line))
+                    count -= 1
+                time.sleep(0.5)  # Wait a bit for more output
diff --git a/automation/pytest/pyproject.toml b/automation/pytest/pyproject.toml
index 78f60c4f8e77..4ab7094e0d84 100644
--- a/automation/pytest/pyproject.toml
+++ b/automation/pytest/pyproject.toml
@@ -6,6 +6,7 @@ dependencies = [
     "paramiko>=4.0.0",
     "pip>=26.0.1",
     "pytest>=9.0.2",
+    "pytest-timeout>=2.4.0",
     "types-paramiko>=4.0.0.20260322",
 ]
 [dependency-groups]
diff --git a/automation/pytest/test_boot.py b/automation/pytest/test_boot.py
index b7610736fa25..158f1a30dcb6 100644
--- a/automation/pytest/test_boot.py
+++ b/automation/pytest/test_boot.py
@@ -1,3 +1,4 @@
+import logging
 import os
 from pathlib import Path
 import pytest
@@ -53,8 +54,31 @@ def machine_on_ipmi(prepare_netboot: None) -> 
Generator[IPMI]:
             password=os.environ['MOON_CONSOLE_PASS'],
             blade_node=os.environ['IPMI_BLADE_NODE'],
             )
+    # start console
+    ipmi.get_console()
     ipmi.power_off()
     ipmi.power_status()
     ipmi.power_on()
     yield ipmi
     ipmi.power_off()
+
+
+@pytest.mark.timeout(600)
+def test_xen_tools_tests(machine_on_ipmi: IPMI,
+                         ) -> None:
+    expect_string = "Dom0 ready"
+    for line in machine_on_ipmi.console_output_generator():
+        #
+        # Wait for `expect_string` for the test to succeed
+        #
+        if expect_string in line:
+            logging.info(f"got expect_string '{line}'.")
+            break
+        #
+        # Some common expected or not console outputs.
+        #
+        if "Latest ChangeSet:" in line:
+            logging.info(f"Test xen build from '{line}'.")
+        if "Manual reset required ('noreboot' specified)" in line:
+            # Test failed, could wait a bit before failing the test.
+            logging.error("Xen panic")
-- 
Anthony PERARD




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.