blob: e8a094f1df5ed5ff8619c1096343173813e7cb46 [file] [log] [blame]
Brad Bishopbec4ebc2022-08-03 09:55:16 -04001import pathlib
2import pexpect
3import os
4
5from oeqa.core.target.ssh import OESSHTarget
6from fvp import conffile, runner
7
8
9class OEFVPSSHTarget(OESSHTarget):
10 """
11 Base class for meta-arm FVP targets.
12 Contains common logic to start and stop an FVP.
13 """
14 def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
Patrick Williams7784c422022-11-17 07:29:11 -060015 port=None, dir_image=None, rootfs=None, bootlog=None, **kwargs):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040016 super().__init__(logger, target_ip, server_ip, timeout, user, port)
17 image_dir = pathlib.Path(dir_image)
18 # rootfs may have multiple extensions so we need to strip *all* suffixes
19 basename = pathlib.Path(rootfs)
20 basename = basename.name.replace("".join(basename.suffixes), "")
21 self.fvpconf = image_dir / (basename + ".fvpconf")
22 self.config = conffile.load(self.fvpconf)
Patrick Williams7784c422022-11-17 07:29:11 -060023 self.bootlog = bootlog
Brad Bishopbec4ebc2022-08-03 09:55:16 -040024
25 if not self.fvpconf.exists():
26 raise FileNotFoundError(f"Cannot find {self.fvpconf}")
27
Patrick Williams7784c422022-11-17 07:29:11 -060028 def _after_start(self):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040029 pass
30
Brad Bishopbec4ebc2022-08-03 09:55:16 -040031 def start(self, **kwargs):
Patrick Williams7784c422022-11-17 07:29:11 -060032 self.fvp_log = self._create_logfile("fvp")
33 self.fvp = runner.FVPRunner(self.logger)
34 self.fvp.start(self.config, stdout=self.fvp_log)
35 self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
36 self._after_start()
Brad Bishopbec4ebc2022-08-03 09:55:16 -040037
38 def stop(self, **kwargs):
Patrick Williams7784c422022-11-17 07:29:11 -060039 returncode = self.fvp.stop()
40 self.logger.debug(f"Stopped FVP with return code {returncode}")
41
42 def _create_logfile(self, name):
43 if not self.bootlog:
44 return None
45
46 test_log_path = pathlib.Path(self.bootlog).parent
47 test_log_suffix = pathlib.Path(self.bootlog).suffix
48 fvp_log_file = f"{name}_log{test_log_suffix}"
49 fvp_log_path = pathlib.Path(test_log_path, fvp_log_file)
50 fvp_log_symlink = pathlib.Path(test_log_path, f"{name}_log")
51 try:
52 os.remove(fvp_log_symlink)
53 except:
54 pass
55 os.symlink(fvp_log_file, fvp_log_symlink)
56 return open(fvp_log_path, 'wb')
Brad Bishopbec4ebc2022-08-03 09:55:16 -040057
58
59class OEFVPTarget(OEFVPSSHTarget):
60 """
61 For compatibility with OE-core test cases, this target's start() method
62 waits for a Linux shell before returning to ensure that SSH commands work
63 with the default test dependencies.
64 """
Patrick Williams7784c422022-11-17 07:29:11 -060065 def __init__(self, logger, target_ip, server_ip, **kwargs):
Brad Bishopbec4ebc2022-08-03 09:55:16 -040066 super().__init__(logger, target_ip, server_ip, **kwargs)
Patrick Williams7784c422022-11-17 07:29:11 -060067 self.logfile = self.bootlog and open(self.bootlog, "wb") or None
Brad Bishopbec4ebc2022-08-03 09:55:16 -040068
69 # FVPs boot slowly, so allow ten minutes
70 self.boot_timeout = 10 * 60
71
Patrick Williams7784c422022-11-17 07:29:11 -060072 def _after_start(self):
73 with open(self.fvp_log.name, 'rb') as logfile:
74 parser = runner.ConsolePortParser(logfile)
75 self.logger.debug(f"Awaiting console on terminal {self.config['consoles']['default']}")
76 port = parser.parse_port(self.config['consoles']['default'])
77 console = self.fvp.create_pexpect(port)
78 try:
79 console.expect("login\\:", timeout=self.boot_timeout)
80 self.logger.debug("Found login prompt")
81 except pexpect.TIMEOUT:
82 self.logger.info("Timed out waiting for login prompt.")
83 self.logger.info("Boot log follows:")
84 self.logger.info(b"\n".join(console.before.splitlines()[-200:]).decode("utf-8", errors="replace"))
85 raise RuntimeError("Failed to start FVP.")
Brad Bishopbec4ebc2022-08-03 09:55:16 -040086
87
88class OEFVPSerialTarget(OEFVPSSHTarget):
89 """
90 This target is intended for interaction with the target over one or more
91 telnet consoles using pexpect.
Patrick Williams7784c422022-11-17 07:29:11 -060092
Brad Bishopbec4ebc2022-08-03 09:55:16 -040093 This still depends on OEFVPSSHTarget so SSH commands can still be run on
94 the target, but note that this class does not inherently guarantee that
95 the SSH server is running prior to running test cases. Test cases that use
96 SSH should first validate that SSH is available, e.g. by depending on the
97 "linuxboot" test case in meta-arm.
98 """
99 DEFAULT_CONSOLE = "default"
100
Patrick Williams7784c422022-11-17 07:29:11 -0600101 def __init__(self, logger, target_ip, server_ip, **kwargs):
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400102 super().__init__(logger, target_ip, server_ip, **kwargs)
103 self.terminals = {}
104
Patrick Williams7784c422022-11-17 07:29:11 -0600105 def _after_start(self):
106 with open(self.fvp_log.name, 'rb') as logfile:
107 parser = runner.ConsolePortParser(logfile)
108 for name, console in self.config["consoles"].items():
109 logfile = self._create_logfile(name)
110 self.logger.info(f'Creating terminal {name} on {console}')
111 port = parser.parse_port(console)
112 self.terminals[name] = \
113 self.fvp.create_pexpect(port, logfile=logfile)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400114
Patrick Williams7784c422022-11-17 07:29:11 -0600115 # testimage.bbclass expects to see a log file at `bootlog`,
116 # so make a symlink to the 'default' log file
117 if name == 'default':
118 default_test_file = f"{name}_log{self.test_log_suffix}"
119 os.symlink(default_test_file, self.bootlog)
Brad Bishopbec4ebc2022-08-03 09:55:16 -0400120
121 def _get_terminal(self, name):
122 return self.terminals[name]
123
124 def __getattr__(self, name):
125 """
126 Magic method which automatically exposes the whole pexpect API on the
127 target, with the first argument being the terminal name.
128
129 e.g. self.target.expect(self.target.DEFAULT_CONSOLE, "login\\:")
130 """
131 def call_pexpect(terminal, *args, **kwargs):
132 attr = getattr(self.terminals[terminal], name)
133 if callable(attr):
134 return attr(*args, **kwargs)
135 else:
136 return attr
137
138 return call_pexpect