summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa
diff options
context:
space:
mode:
authorStefan Stanacar <stefanx.stanacar@intel.com>2014-03-30 17:47:35 +0300
committerRichard Purdie <richard.purdie@linuxfoundation.org>2014-03-31 22:53:46 +0100
commit397225996446ac91c5623972f2ca5c8b3a17c31e (patch)
treeed58f0040fb09b10e4219d1069f80ce5e4474da6 /meta/lib/oeqa
parent2764a40093644595cad9c095fb344912f9dc60fa (diff)
downloadpoky-397225996446ac91c5623972f2ca5c8b3a17c31e.tar.gz
lib/oeqa: add a test target controller for EFI targets
The purpose of this module is to deploy a test image on a EFI-enabled hardware and run our runtime tests. A bit of background: - testimage.bbclass uses the concept of TEST_TARGET which is a class name that is responsible for target deploying. A layer can provide it's own TEST_TARGET. Right now has OE-core has a QemuTarget and a SimpleRemoteTarget (ssh into an already up and running machine and run tests), the default one being qemu. - basically testimage does something like: target.deploy() try: target.start() runTests() finally: target.stop() This module assumes a running EFI machine with gummiboot as bootloader and core-image-testmaster installed (or similar). Also your hardware under test has to be in a DHCP-enabled network that gives it the same IP for each reboot. One time setup (master image): - build core-image-testmaster with EFI_PROVIDER = "gummiboot" - install the image on the target Test image setup: - build your test image, e.g core-image-sato as you usually do, but with these in local.conf: IMAGE_FSTYPES += "tar.gz" - Now run the tests: INHERIT += "testimage" TEST_TARGET = "GummibootTarget" TEST_TARGET_IP = "192.168.2.3" bitbake core-image-sato -c testimage Other notes: - TEST_POWERCONTROL_CMD (togheter with TEST_POWERCONTROL_EXTRA_ARGS) can be a command that runs on the host and does power cycling. The test code passes one argument to that command: off, on or cycle (off then on). In my case I use something like TEST_POWERCONTROL_CMD="powercontrol.exp test 10.11.12.1 nuc1" in local.conf. Basically my expect script does: 'ssh test@10.11.12.1 "pyctl nuc1 <arg>" and runs a python script there that controls power for a label called nuc1'. The reason why my expect script has to ssh into another machine is because of network topology, and that machine is the one actually connected to the test rack and the power strip. That's why TEST_POWERCONTROL_CMD and _ARGS need to be customized for one's setup, the only requirement being that it accepts: on/off/cycle as the last argument. - if no command is defined it would use classic reboot. This is fine as long as the machine actually reboots (as in the ssh test hasn't failed), but it's useful for "simple-setup-with-one-board-on-the-desk" scenario, where some manual interaction is okay from time to time. [YOCTO #5614] (From OE-Core rev: e00f888a88d0851b088c232dec66418e575a2e90) Signed-off-by: Stefan Stanacar <stefanx.stanacar@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r--meta/lib/oeqa/controllers/masterimage.py133
-rw-r--r--meta/lib/oeqa/runtime/ssh.py2
2 files changed, 135 insertions, 0 deletions
diff --git a/meta/lib/oeqa/controllers/masterimage.py b/meta/lib/oeqa/controllers/masterimage.py
new file mode 100644
index 0000000000..188c630bcd
--- /dev/null
+++ b/meta/lib/oeqa/controllers/masterimage.py
@@ -0,0 +1,133 @@
1import os
2import bb
3import traceback
4import time
5
6import oeqa.targetcontrol
7import oeqa.utils.sshcontrol as sshcontrol
8import oeqa.utils.commands as commands
9
10class GummibootTarget(oeqa.targetcontrol.SimpleRemoteTarget):
11
12 def __init__(self, d):
13 # let our base class do the ip thing
14 super(GummibootTarget, self).__init__(d)
15
16 # test rootfs + kernel
17 self.rootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + '.tar.gz')
18 self.kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("KERNEL_IMAGETYPE"))
19 if not os.path.isfile(self.rootfs):
20 # we could've checked that IMAGE_FSTYPES contains tar.gz but the config for running testimage might not be
21 # the same as the config with which the image was build, ie
22 # you bitbake core-image-sato with IMAGE_FSTYPES += "tar.gz"
23 # and your autobuilder overwrites the config, adds the test bits and runs bitbake core-image-sato -c testimage
24 bb.fatal("No rootfs found. Did you build the image ?\nIf yes, did you build it with IMAGE_FSTYPES += \"tar.gz\" ? \
25 \nExpected path: %s" % self.rootfs)
26 if not os.path.isfile(self.kernel):
27 bb.fatal("No kernel found. Expected path: %s" % self.kernel)
28
29 # if the user knows what he's doing, then by all means...
30 # test-rootfs.tar.gz and test-kernel are hardcoded names in other places
31 # they really have to be used like that in commands though
32 cmds = d.getVar("TEST_DEPLOY_CMDS", True)
33
34 # this the value we need to set in the LoaderEntryOneShot EFI variable
35 # so the system boots the 'test' bootloader label and not the default
36 # The first four bytes are EFI bits, and the rest is an utf-16le string
37 # (EFI vars values need to be utf-16)
38 # $ echo -en "test\0" | iconv -f ascii -t utf-16le | hexdump -C
39 # 00000000 74 00 65 00 73 00 74 00 00 00 |t.e.s.t...|
40 self.efivarvalue = r'\x07\x00\x00\x00\x74\x00\x65\x00\x73\x00\x74\x00\x00\x00'
41
42 if cmds:
43 self.deploy_cmds = cmds.split("\n")
44 else:
45 self.deploy_cmds = [
46 'mount -L boot /boot',
47 'mkdir -p /mnt/testrootfs',
48 'mount -L testrootfs /mnt/testrootfs',
49 'modprobe efivarfs',
50 'mount -t efivarfs efivarfs /sys/firmware/efi/efivars',
51 'cp ~/test-kernel /boot',
52 'rm -rf /mnt/testrootfs/*',
53 'tar xzvf ~/test-rootfs.tar.gz -C /mnt/testrootfs',
54 'printf "%s" > /sys/firmware/efi/efivars/LoaderEntryOneShot-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f' % self.efivarvalue
55 ]
56
57 # master ssh connection
58 self.master = None
59
60 # this is the name of the command that controls the power for a board
61 # e.g: TEST_POWERCONTROL_CMD = "/home/user/myscripts/powercontrol.py ${MACHINE} what-ever-other-args-the-script-wants"
62 # the command should take as the last argument "off" and "on" and "cycle" (off, on)
63 self.powercontrol_cmd = d.getVar("TEST_POWERCONTROL_CMD", True) or None
64 self.powercontrol_args = d.getVar("TEST_POWERCONTROL_EXTRA_ARGS") or ""
65 self.origenv = os.environ
66 if self.powercontrol_cmd:
67 if self.powercontrol_args:
68 self.powercontrol_cmd = "%s %s" % (self.powercontrol_cmd, self.powercontrol_args)
69 # the external script for controlling power might use ssh
70 # ssh + keys means we need the original user env
71 bborigenv = d.getVar("BB_ORIGENV", False) or {}
72 for key in bborigenv:
73 val = bborigenv.getVar(key, True)
74 if val is not None:
75 self.origenv[key] = str(val)
76 self.power_ctl("on")
77
78 def power_ctl(self, msg):
79 if self.powercontrol_cmd:
80 cmd = "%s %s" % (self.powercontrol_cmd, msg)
81 commands.runCmd(cmd, preexec_fn=os.setsid, env=self.origenv)
82
83 def power_cycle(self, conn):
84 if self.powercontrol_cmd:
85 # be nice, don't just cut power
86 conn.run("shutdown -h now")
87 time.sleep(10)
88 self.power_ctl("cycle")
89 else:
90 status, output = conn.run("reboot")
91 if status != 0:
92 bb.error("Failed rebooting target and no power control command defined. You need to manually reset the device.\n%s" % output)
93
94 def deploy(self):
95 bb.plain("%s - deploying image on target" % self.pn)
96 # base class just sets the ssh log file for us
97 super(GummibootTarget, self).deploy()
98 self.master = sshcontrol.SSHControl(ip=self.ip, logfile=self.sshlog, timeout=600, port=self.port)
99 try:
100 self._deploy()
101 except Exception as e:
102 bb.fatal("Failed deploying test image: %s" % e)
103
104 def _deploy(self):
105 # make sure we are in the right image
106 status, output = self.master.run("cat /etc/masterimage")
107 if status != 0:
108 raise Exception("No ssh connectivity or target isn't running a master image.\n%s" % output)
109
110 # make sure these aren't mounted
111 self.master.run("umount /boot; umount /mnt/testrootfs; umount /sys/firmware/efi/efivars;")
112
113 # from now on, every deploy cmd should return 0
114 # else an exception will be thrown by sshcontrol
115 self.master.ignore_status = False
116 self.master.copy_to(self.rootfs, "~/test-rootfs.tar.gz")
117 self.master.copy_to(self.kernel, "~/test-kernel")
118 for cmd in self.deploy_cmds:
119 self.master.run(cmd)
120
121
122 def start(self, params=None):
123 bb.plain("%s - boot test image on target" % self.pn)
124 self.power_cycle(self.master)
125 # there are better ways than a timeout but this should work for now
126 time.sleep(120)
127 # set the ssh object for the target/test image
128 self.connection = sshcontrol.SSHControl(self.ip, logfile=self.sshlog, port=self.port)
129 bb.plain("%s - start running tests" % self.pn)
130
131 def stop(self):
132 bb.plain("%s - reboot/powercycle target" % self.pn)
133 self.power_cycle(self.connection)
diff --git a/meta/lib/oeqa/runtime/ssh.py b/meta/lib/oeqa/runtime/ssh.py
index 8c96020e54..e64866019f 100644
--- a/meta/lib/oeqa/runtime/ssh.py
+++ b/meta/lib/oeqa/runtime/ssh.py
@@ -14,3 +14,5 @@ class SshTest(oeRuntimeTest):
14 def test_ssh(self): 14 def test_ssh(self):
15 (status, output) = self.target.run('uname -a') 15 (status, output) = self.target.run('uname -a')
16 self.assertEqual(status, 0, msg="SSH Test failed: %s" % output) 16 self.assertEqual(status, 0, msg="SSH Test failed: %s" % output)
17 (status, output) = self.target.run('cat /etc/masterimage')
18 self.assertEqual(status, 1, msg="This isn't the right image - /etc/masterimage shouldn't be here %s" % output)