blob: 0e498fb895f01793c6685dbb2466f610fec5414c [file] [log] [blame]
Chris Sosada9632e2013-03-04 12:28:06 -08001#!/usr/bin/python
2#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Integration test to test the basic functionality of dev-install and gmerge.
8
9This module contains a test that runs some sanity integration tests against
10a VM. First it starts a VM test image and turns it into a base image by wiping
11all of the stateful partition. Once done, runs dev_install to restore the
12stateful partition and then runs gmerge.
13"""
14
15import logging
16import optparse
17import os
18import shutil
19import socket
20import sys
21import tempfile
22
23import constants
24sys.path.append(constants.SOURCE_ROOT)
25sys.path.append(constants.CROS_PLATFORM_ROOT)
26
27from chromite.lib import cros_build_lib
Chris Sosa928085e2013-03-08 17:25:30 -080028from chromite.lib import remote_access
Chris Sosada9632e2013-03-04 12:28:06 -080029from crostestutils.lib import dev_server_wrapper
30from crostestutils.lib import mount_helper
31from crostestutils.lib import test_helper
32
33
34_LOCALHOST = 'localhost'
35_PRIVATE_KEY = os.path.join(constants.CROSUTILS_DIR, 'mod_for_test_scripts',
36 'ssh_keys', 'testing_rsa')
Chris Sosa5bc23bf2013-07-03 12:14:18 -070037_MAX_SSH_ATTEMPTS = 3
Chris Sosada9632e2013-03-04 12:28:06 -080038
39class TestError(Exception):
40 """Raised on any error during testing. It being raised is a test failure."""
41
42
43class DevModeTest(object):
44 """Wrapper for dev mode tests."""
45 def __init__(self, image_path, board, binhost):
46 """
47 Args:
48 image_path: Filesystem path to the image to test.
49 board: Board of the image under test.
50 binhost: Binhost override. Binhost as defined here is where dev-install
51 or gmerge go to search for binary packages. By default this will
52 be set to the devserver url of the host running this script.
53 If no override i.e. the default is ok, set to None.
54 """
55 self.image_path = image_path
56 self.board = board
57 self.binhost = binhost
58
59 self.tmpdir = tempfile.mkdtemp('DevModeTest')
Chris Sosada9632e2013-03-04 12:28:06 -080060 self.tmpkvmpid = os.path.join(self.tmpdir, 'kvm_pid')
61
62 self.working_image_path = None
63 self.devserver = None
Chris Sosa928085e2013-03-08 17:25:30 -080064 self.remote_access = None
Chris Sosada9632e2013-03-04 12:28:06 -080065 self.port = None
66
67 def Cleanup(self):
68 """Clean up any state at the end of the test."""
69 try:
70 if self.working_image_path:
71 os.remove(self.working_image_path)
72
73 if self.devserver:
74 self.devserver.Stop()
75
76 self.devserver = None
Chris Sosa5bc23bf2013-07-03 12:14:18 -070077 self._StopVM()
Chris Sosada9632e2013-03-04 12:28:06 -080078
79 if self.tmpdir:
80 shutil.rmtree(self.tmpdir, ignore_errors=True)
81
82 self.tmpdir = None
83 except Exception:
84 logging.warning('Received error during cleanup', exc_info=True)
85
86 def _SetupSSH(self):
87 """Sets up the necessary items for running ssh."""
88 self.port = self._FindUnusedPort()
Chris Sosa928085e2013-03-08 17:25:30 -080089 self.remote_access = remote_access.RemoteAccess(
90 _LOCALHOST, self.tmpdir, self.port,
91 debug_level=logging.DEBUG, interactive=False)
Chris Sosada9632e2013-03-04 12:28:06 -080092
93 def _WipeStatefulPartition(self):
94 """Deletes everything from the working image path's stateful partition."""
95 r_mount_point = os.path.join(self.tmpdir, 'm')
96 s_mount_point = os.path.join(self.tmpdir, 's')
97 mount_helper.MountImage(self.working_image_path,
98 r_mount_point, s_mount_point, read_only=False,
99 safe=True)
100 # Run in shell mode to interpret '*' as a glob.
101 cros_build_lib.SudoRunCommand('rm -rf %s/*' % s_mount_point, shell=True,
102 debug_level=logging.DEBUG)
103 mount_helper.UnmountImage(r_mount_point, s_mount_point)
104
105 def _FindUnusedPort(self):
106 """Returns a currently unused port."""
107 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
108 s.bind((_LOCALHOST, 0))
109 port = s.getsockname()[1]
110 s.close()
111 return port
112
Chris Sosa5bc23bf2013-07-03 12:14:18 -0700113 def _RobustlyStartVMWithSSH(self):
114 """Start test copy of VM and ensure we can ssh into it.
115
116 This command is more robust than just naively starting the VM as it will
117 try to start the VM multiple times if the VM fails to start up. This is
118 inspired by retry_until_ssh in crosutils/lib/cros_vm_lib.sh.
119 """
120 for _ in range(_MAX_SSH_ATTEMPTS):
121 try:
122 cmd = ['%s/bin/cros_start_vm' % constants.CROSUTILS_DIR,
123 '--ssh_port', str(self.port),
124 '--image_path', self.working_image_path,
125 '--no_graphics',
126 '--kvm_pid', self.tmpkvmpid]
127 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
128
129 # Ping the VM to ensure we can SSH into it.
130 self.remote_access.RemoteSh(['true'])
131 return
132 except cros_build_lib.RunCommandError as e:
133 logging.warning('Failed to connect to VM')
134 logging.debug(e)
135 self._StopVM()
136 else:
137 raise TestError('Max attempts to connect to VM exceeded')
138
139 def _StopVM(self):
140 """Stops a running VM set up using _RobustlyStartVMWithSSH."""
141 cmd = ['%s/bin/cros_stop_vm' % constants.CROSUTILS_DIR,
142 '--kvm_pid', self.tmpkvmpid]
143 cros_build_lib.RunCommand(cmd, debug_level=logging.DEBUG)
144
Chris Sosada9632e2013-03-04 12:28:06 -0800145 def PrepareTest(self):
146 """Pre-test modification to the image and env to setup test."""
147 logging.info('Setting up the image %s for vm testing.',
148 self.image_path)
149 self._SetupSSH()
150 vm_path = test_helper.CreateVMImage(self.image_path, self.board,
151 full=False)
152
153 logging.info('Making copy of the vm image %s to manipulate.', vm_path)
David James45b55dd2013-04-24 09:16:40 -0700154 self.working_image_path = os.path.join(self.tmpdir,
155 os.path.basename(vm_path))
Chris Sosada9632e2013-03-04 12:28:06 -0800156 shutil.copyfile(vm_path, self.working_image_path)
157 logging.debug('Copy of vm image stored at %s.', self.working_image_path)
158
159 logging.info('Wiping the stateful partition to prepare test.')
160 self._WipeStatefulPartition()
161
162 logging.info('Starting the vm on port %d.', self.port)
Chris Sosa5bc23bf2013-07-03 12:14:18 -0700163 self._RobustlyStartVMWithSSH()
Chris Sosa068c1e92013-03-17 22:54:20 -0700164
Chris Sosada9632e2013-03-04 12:28:06 -0800165 if not self.binhost:
166 logging.info('Starting the devserver.')
167 self.devserver = dev_server_wrapper.DevServerWrapper(self.tmpdir)
168 self.devserver.start()
169 self.devserver.WaitUntilStarted()
170 self.binhost = dev_server_wrapper.DevServerWrapper.GetDevServerURL(
Chris Sosac9447962013-03-12 10:12:29 -0700171 sub_dir='static/pkgroot/%s/packages' % self.board)
Chris Sosada9632e2013-03-04 12:28:06 -0800172
173 logging.info('Using binhost %s', self.binhost)
174
175 def TestDevInstall(self):
176 """Tests that we can run dev-install and have python work afterwards."""
177 try:
178 logging.info('Running dev install in the vm.')
Chris Sosa928085e2013-03-08 17:25:30 -0800179 self.remote_access.RemoteSh(
Chris Sosada9632e2013-03-04 12:28:06 -0800180 ['bash', '-l', '-c',
181 '"/usr/bin/dev_install --yes --binhost %s"' % self.binhost])
182
183 logging.info('Verifying that python works on the image.')
Chris Sosa928085e2013-03-08 17:25:30 -0800184 self.remote_access.RemoteSh(
Chris Sosada9632e2013-03-04 12:28:06 -0800185 ['sudo', '-u', 'chronos', '--',
186 'python', '-c', '"print \'hello world\'"'])
187 except cros_build_lib.RunCommandError as e:
188 self.devserver.PrintLog()
189 logging.error('dev-install test failed. See devserver log above for more '
190 'details.')
191 raise TestError('dev-install test failed with: %s' % str(e))
192
Chris Sosada9632e2013-03-04 12:28:06 -0800193 def TestGmerge(self):
194 """Evaluates whether the test passed or failed."""
Chris Sosa928085e2013-03-08 17:25:30 -0800195 logging.info('Testing that gmerge works on the image after dev install.')
Chris Sosada9632e2013-03-04 12:28:06 -0800196 try:
Chris Sosac9447962013-03-12 10:12:29 -0700197 self.remote_access.RemoteSh(
198 ['gmerge', 'gmerge', '--accept_stable', '--usepkg',
199 '--devserver_url', self.devserver.GetDevServerURL(),
200 '--board', self.board])
Chris Sosada9632e2013-03-04 12:28:06 -0800201 except cros_build_lib.RunCommandError as e:
202 logging.error('gmerge test failed. See log for details')
203 raise TestError('gmerge test failed with: %s' % str(e))
204
205
206def main():
207 usage = ('%s <board> <path_to_[test|vm]_image>. '
208 'See --help for more options' % os.path.basename(sys.argv[0]))
209 parser = optparse.OptionParser(usage)
210 parser.add_option('--binhost', metavar='URL',
211 help='binhost override. By default, starts up a devserver '
212 'and uses it as the binhost.')
213 parser.add_option('-v', '--verbose', default=False, action='store_true',
214 help='Print out added debugging information')
215
216 (options, args) = parser.parse_args()
217
218 if len(args) != 2:
219 parser.print_usage()
220 parser.error('Need board and path to test image.')
221
222 board = args[0]
223 image_path = os.path.realpath(args[1])
224
225 test_helper.SetupCommonLoggingFormat(verbose=options.verbose)
226
227 test = DevModeTest(image_path, board, options.binhost)
228 try:
229 test.PrepareTest()
230 test.TestDevInstall()
Chris Sosa928085e2013-03-08 17:25:30 -0800231 test.TestGmerge()
Chris Sosada9632e2013-03-04 12:28:06 -0800232 logging.info('All tests passed.')
233 finally:
234 test.Cleanup()
235
236
237if __name__ == '__main__':
238 main()