blob: 1d977e03c3d2c3b49dfe15af252c07eff0afc51e [file] [log] [blame]
Ahmad Sharif4467f002012-12-20 12:09:49 -08001#!/usr/bin/python
Ahmad Sharif70de27b2011-06-15 17:51:24 -07002#
3# Copyright 2011 Google Inc. All Rights Reserved.
4
5"""Script to image a ChromeOS device.
6
7This script images a remote ChromeOS device with a specific image."
8"""
9
10__author__ = "asharif@google.com (Ahmad Sharif)"
11
12import filecmp
13import glob
14import optparse
15import os
Ahmad Shariff395c262012-10-09 17:48:09 -070016import re
Ahmad Sharif70de27b2011-06-15 17:51:24 -070017import shutil
18import sys
19import tempfile
Ahmad Sharif4467f002012-12-20 12:09:49 -080020import time
21
Ahmad Sharif70de27b2011-06-15 17:51:24 -070022from utils import command_executer
cmticee5bc63b2015-05-27 16:59:37 -070023from utils import locks
Ahmad Sharif70de27b2011-06-15 17:51:24 -070024from utils import logger
Ahmad Shariffd356fb2012-05-07 12:02:16 -070025from utils import misc
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080026from utils.file_utils import FileUtils
Ahmad Sharif70de27b2011-06-15 17:51:24 -070027
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080028checksum_file = "/usr/local/osimage_checksum_file"
Ahmad Sharif4467f002012-12-20 12:09:49 -080029lock_file = "/tmp/image_chromeos_lock/image_chromeos_lock"
Ahmad Sharif70de27b2011-06-15 17:51:24 -070030
31def Usage(parser, message):
32 print "ERROR: " + message
33 parser.print_help()
34 sys.exit(0)
35
Ahmad Shariff395c262012-10-09 17:48:09 -070036
cmtice13909242014-03-11 13:38:07 -070037def CheckForCrosFlash(chromeos_root, remote, log_level):
38 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
cmtice0cc4e772014-01-30 15:52:37 -080039
Luis Lozano54db5382015-05-20 15:57:19 -070040 # Check to see if remote machine has cherrypy, ctypes
41 command = "python -c 'import cherrypy, ctypes'"
42 retval = cmd_executer.CrosRunCommand(command,
43 chromeos_root=chromeos_root,
44 machine=remote)
Han Shen96d936c2015-03-25 12:03:12 -070045 logger.GetLogger().LogFatalIf(
46 retval == 255, "Failed ssh to %s (for checking cherrypy)" % remote)
Luis Lozano54db5382015-05-20 15:57:19 -070047 logger.GetLogger().LogFatalIf(
48 retval != 0, "Failed to find cherrypy or ctypes on remote '{}', "
49 "cros flash cannot work.".format(remote))
50
cmtice0cc4e772014-01-30 15:52:37 -080051
Ahmad Sharif4467f002012-12-20 12:09:49 -080052def DoImage(argv):
Han Shenba649282015-08-05 17:19:55 -070053 """Image ChromeOS."""
Ahmad Sharif4467f002012-12-20 12:09:49 -080054
Ahmad Sharif70de27b2011-06-15 17:51:24 -070055 parser = optparse.OptionParser()
56 parser.add_option("-c", "--chromeos_root", dest="chromeos_root",
57 help="Target directory for ChromeOS installation.")
58 parser.add_option("-r", "--remote", dest="remote",
59 help="Target device.")
60 parser.add_option("-i", "--image", dest="image",
61 help="Image binary file.")
62 parser.add_option("-b", "--board", dest="board",
63 help="Target board override.")
64 parser.add_option("-f", "--force", dest="force",
65 action="store_true",
66 default=False,
67 help="Force an image even if it is non-test.")
cmticee5bc63b2015-05-27 16:59:37 -070068 parser.add_option("-n", "--no_lock", dest="no_lock",
69 default=False, action="store_true",
70 help="Do not attempt to lock remote before imaging. "
71 "This option should only be used in cases where the "
72 "exclusive lock has already been acquired (e.g. in "
73 "a script that calls this one).")
cmtice13909242014-03-11 13:38:07 -070074 parser.add_option("-l", "--logging_level", dest="log_level",
75 default="verbose",
76 help="Amount of logging to be used. Valid levels are "
77 "'quiet', 'average', and 'verbose'.")
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080078 parser.add_option("-a",
Ahmad Sharif4467f002012-12-20 12:09:49 -080079 "--image_args",
80 dest="image_args")
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080081
Ahmad Sharif70de27b2011-06-15 17:51:24 -070082
83 options = parser.parse_args(argv[1:])[0]
84
cmtice13909242014-03-11 13:38:07 -070085 if not options.log_level in command_executer.LOG_LEVEL:
86 Usage(parser, "--logging_level must be 'quiet', 'average' or 'verbose'")
87 else:
88 log_level = options.log_level
89
90 # Common initializations
91 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
92 l = logger.GetLogger()
93
Ahmad Sharif70de27b2011-06-15 17:51:24 -070094 if options.chromeos_root is None:
95 Usage(parser, "--chromeos_root must be set")
96
97 if options.remote is None:
98 Usage(parser, "--remote must be set")
99
100 options.chromeos_root = os.path.expanduser(options.chromeos_root)
101
102 if options.board is None:
103 board = cmd_executer.CrosLearnBoard(options.chromeos_root, options.remote)
104 else:
105 board = options.board
106
107 if options.image is None:
Ahmad Shariffd356fb2012-05-07 12:02:16 -0700108 images_dir = misc.GetImageDir(options.chromeos_root, board)
109 image = os.path.join(images_dir,
110 "latest",
111 "chromiumos_test_image.bin")
112 if not os.path.exists(image):
113 image = os.path.join(images_dir,
114 "latest",
115 "chromiumos_image.bin")
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700116 else:
117 image = options.image
cmtice0cc4e772014-01-30 15:52:37 -0800118 if image.find("xbuddy://") < 0:
119 image = os.path.expanduser(image)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700120
cmtice0cc4e772014-01-30 15:52:37 -0800121 if image.find("xbuddy://") < 0:
122 image = os.path.realpath(image)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700123
cmtice0cc4e772014-01-30 15:52:37 -0800124 if not os.path.exists(image) and image.find("xbuddy://") < 0:
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700125 Usage(parser, "Image file: " + image + " does not exist!")
126
cmticee5bc63b2015-05-27 16:59:37 -0700127 try:
128 should_unlock = False
129 if not options.no_lock:
130 try:
131 status = locks.AcquireLock(list(options.remote.split()),
132 options.chromeos_root)
133 should_unlock = True
134 except Exception as e:
135 raise Exception("Error acquiring machine: %s" % str(e))
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700136
cmticee5bc63b2015-05-27 16:59:37 -0700137 reimage = False
138 local_image = False
139 if image.find("xbuddy://") < 0:
140 local_image = True
141 image_checksum = FileUtils().Md5File(image, log_level=log_level)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700142
cmticee5bc63b2015-05-27 16:59:37 -0700143 command = "cat " + checksum_file
144 retval, device_checksum, _ = cmd_executer.CrosRunCommand(
145 command,
146 return_output=True,
147 chromeos_root=options.chromeos_root,
148 machine=options.remote)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700149
cmticee5bc63b2015-05-27 16:59:37 -0700150 device_checksum = device_checksum.strip()
151 image_checksum = str(image_checksum)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700152
cmticee5bc63b2015-05-27 16:59:37 -0700153 l.LogOutput("Image checksum: " + image_checksum)
154 l.LogOutput("Device checksum: " + device_checksum)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700155
cmticee5bc63b2015-05-27 16:59:37 -0700156 if image_checksum != device_checksum:
157 [found, located_image] = LocateOrCopyImage(options.chromeos_root,
158 image,
159 board=board)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700160
cmticee5bc63b2015-05-27 16:59:37 -0700161 reimage = True
162 l.LogOutput("Checksums do not match. Re-imaging...")
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700163
cmticee5bc63b2015-05-27 16:59:37 -0700164 is_test_image = IsImageModdedForTest(options.chromeos_root,
165 located_image, log_level)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700166
cmticee5bc63b2015-05-27 16:59:37 -0700167 if not is_test_image and not options.force:
168 logger.GetLogger().LogFatal("Have to pass --force to image a non-test"
169 " image!")
cmtice0cc4e772014-01-30 15:52:37 -0800170 else:
cmticee5bc63b2015-05-27 16:59:37 -0700171 reimage = True
172 found = True
173 l.LogOutput("Using non-local image; Re-imaging...")
Luis Lozano54db5382015-05-20 15:57:19 -0700174
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700175
cmticee5bc63b2015-05-27 16:59:37 -0700176 if reimage:
177 # If the device has /tmp mounted as noexec, image_to_live.sh can fail.
178 command = "mount -o remount,rw,exec /tmp"
179 cmd_executer.CrosRunCommand(command,
180 chromeos_root=options.chromeos_root,
181 machine=options.remote)
cmticeb1340082014-01-13 13:22:37 -0800182
cmticee5bc63b2015-05-27 16:59:37 -0700183 real_src_dir = os.path.join(os.path.realpath(options.chromeos_root),
184 "src")
185 real_chroot_dir = os.path.join(os.path.realpath(options.chromeos_root),
186 "chroot")
187 if local_image:
188 if located_image.find(real_src_dir) != 0:
189 if located_image.find(real_chroot_dir) != 0:
190 raise Exception("Located image: %s not in chromeos_root: %s" %
191 (located_image, options.chromeos_root))
192 else:
193 chroot_image = located_image[len(real_chroot_dir):]
194 else:
195 chroot_image = os.path.join(
Rahul Chaudhrye30d3422015-06-23 15:41:13 -0700196 "~/trunk/src",
cmticee5bc63b2015-05-27 16:59:37 -0700197 located_image[len(real_src_dir):].lstrip("/"))
198
199 # Check to see if cros flash will work for the remote machine.
200 CheckForCrosFlash(options.chromeos_root, options.remote, log_level)
201
202 if local_image:
203 cros_flash_args = ["--board=%s" % board,
204 "--clobber-stateful",
205 options.remote,
206 chroot_image]
207 else:
208 cros_flash_args = ["--board=%s" % board,
209 "--clobber-stateful",
210 options.remote,
211 image]
212
213 command = ("cros flash %s" % " ".join(cros_flash_args))
214
215 # Workaround for crosbug.com/35684.
216 os.chmod(misc.GetChromeOSKeyFile(options.chromeos_root), 0600)
cmtice13909242014-03-11 13:38:07 -0700217 if log_level == "quiet":
cmtice13909242014-03-11 13:38:07 -0700218 l.LogOutput("CMD : %s" % command)
cmticee5bc63b2015-05-27 16:59:37 -0700219 elif log_level == "average":
220 cmd_executer.SetLogLevel("verbose");
cmticeb1340082014-01-13 13:22:37 -0800221 retval = cmd_executer.ChrootRunCommand(options.chromeos_root,
cmtice6de7f8f2014-03-14 14:08:21 -0700222 command, command_timeout=1800)
cmticeb1340082014-01-13 13:22:37 -0800223
cmticee5bc63b2015-05-27 16:59:37 -0700224 retries = 0
225 while retval != 0 and retries < 2:
226 retries += 1
227 if log_level == "quiet":
228 l.LogOutput("Imaging failed. Retry # %d." % retries)
229 l.LogOutput("CMD : %s" % command)
230 retval = cmd_executer.ChrootRunCommand(options.chromeos_root,
231 command, command_timeout=1800)
cmtice13909242014-03-11 13:38:07 -0700232
cmtice13909242014-03-11 13:38:07 -0700233 if log_level == "average":
cmticee5bc63b2015-05-27 16:59:37 -0700234 cmd_executer.SetLogLevel(log_level)
cmtice0cc4e772014-01-30 15:52:37 -0800235
cmticee5bc63b2015-05-27 16:59:37 -0700236 if found == False:
237 temp_dir = os.path.dirname(located_image)
238 l.LogOutput("Deleting temp image dir: %s" % temp_dir)
239 shutil.rmtree(temp_dir)
240
241 logger.GetLogger().LogFatalIf(retval, "Image command failed")
242
243 # Unfortunately cros_image_to_target.py sometimes returns early when the
244 # machine isn't fully up yet.
245 retval = EnsureMachineUp(options.chromeos_root, options.remote,
246 log_level)
247
248 # If this is a non-local image, then the retval returned from
249 # EnsureMachineUp is the one that will be returned by this function;
250 # in that case, make sure the value in 'retval' is appropriate.
251 if not local_image and retval == True:
252 retval = 0
253 else:
254 retval = 1
255
256 if local_image:
257 if log_level == "average":
258 l.LogOutput("Verifying image.")
259 command = "echo %s > %s && chmod -w %s" % (image_checksum,
260 checksum_file,
261 checksum_file)
262 retval = cmd_executer.CrosRunCommand(command,
263 chromeos_root=options.chromeos_root,
264 machine=options.remote)
265 logger.GetLogger().LogFatalIf(retval, "Writing checksum failed.")
266
267 successfully_imaged = VerifyChromeChecksum(options.chromeos_root,
268 image,
269 options.remote, log_level)
270 logger.GetLogger().LogFatalIf(not successfully_imaged,
271 "Image verification failed!")
272 TryRemountPartitionAsRW(options.chromeos_root, options.remote,
273 log_level)
274 else:
275 l.LogOutput("Checksums match. Skipping reimage")
276 return retval
277 finally:
278 if should_unlock:
279 locks.ReleaseLock(list(options.remote.split()), options.chromeos_root)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700280
281
282def LocateOrCopyImage(chromeos_root, image, board=None):
283 l = logger.GetLogger()
284 if board is None:
285 board_glob = "*"
286 else:
287 board_glob = board
288
289 chromeos_root_realpath = os.path.realpath(chromeos_root)
290 image = os.path.realpath(image)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800291
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700292 if image.startswith("%s/" % chromeos_root_realpath):
293 return [True, image]
294
295 # First search within the existing build dirs for any matching files.
296 images_glob = ("%s/src/build/images/%s/*/*.bin" %
297 (chromeos_root_realpath,
298 board_glob))
299 images_list = glob.glob(images_glob)
300 for potential_image in images_list:
301 if filecmp.cmp(potential_image, image):
302 l.LogOutput("Found matching image %s in chromeos_root." % potential_image)
303 return [True, potential_image]
cmtice13909242014-03-11 13:38:07 -0700304 # We did not find an image. Copy it in the src dir and return the copied
305 # file.
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700306 if board is None:
307 board = ""
308 base_dir = ("%s/src/build/images/%s" %
309 (chromeos_root_realpath,
310 board))
311 if not os.path.isdir(base_dir):
312 os.makedirs(base_dir)
313 temp_dir = tempfile.mkdtemp(prefix="%s/tmp" % base_dir)
314 new_image = "%s/%s" % (temp_dir, os.path.basename(image))
315 l.LogOutput("No matching image found. Copying %s to %s" %
316 (image, new_image))
317 shutil.copyfile(image, new_image)
318 return [False, new_image]
319
320
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800321def GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp):
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700322 image_dir = os.path.dirname(image)
323 image_file = os.path.basename(image)
324 mount_command = ("cd %s/src/scripts &&"
325 "./mount_gpt_image.sh --from=%s --image=%s"
326 " --safe --read_only"
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800327 " --rootfs_mountpt=%s"
328 " --stateful_mountpt=%s" %
329 (chromeos_root, image_dir, image_file, rootfs_mp,
330 stateful_mp))
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700331 return mount_command
332
333
cmtice13909242014-03-11 13:38:07 -0700334def MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level,
335 unmount=False):
336 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800337 command = GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700338 if unmount:
339 command = "%s --unmount" % command
340 retval = cmd_executer.RunCommand(command)
341 logger.GetLogger().LogFatalIf(retval, "Mount/unmount command failed!")
342 return retval
343
344
cmtice13909242014-03-11 13:38:07 -0700345def IsImageModdedForTest(chromeos_root, image, log_level):
346 if log_level != "verbose":
347 log_level = "quiet"
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800348 rootfs_mp = tempfile.mkdtemp()
349 stateful_mp = tempfile.mkdtemp()
cmtice13909242014-03-11 13:38:07 -0700350 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800351 lsb_release_file = os.path.join(rootfs_mp, "etc/lsb-release")
Ahmad Shariff395c262012-10-09 17:48:09 -0700352 lsb_release_contents = open(lsb_release_file).read()
353 is_test_image = re.search("test", lsb_release_contents, re.IGNORECASE)
cmtice13909242014-03-11 13:38:07 -0700354 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level,
355 unmount=True)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700356 return is_test_image
357
358
cmtice13909242014-03-11 13:38:07 -0700359def VerifyChromeChecksum(chromeos_root, image, remote, log_level):
360 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800361 rootfs_mp = tempfile.mkdtemp()
362 stateful_mp = tempfile.mkdtemp()
cmtice13909242014-03-11 13:38:07 -0700363 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800364 image_chrome_checksum = FileUtils().Md5File("%s/opt/google/chrome/chrome" %
cmtice13909242014-03-11 13:38:07 -0700365 rootfs_mp,
366 log_level=log_level)
367 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level,
368 unmount=True)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700369
370 command = "md5sum /opt/google/chrome/chrome"
Luis Lozano54db5382015-05-20 15:57:19 -0700371 [_, o, _] = cmd_executer.CrosRunCommand(command,
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700372 return_output=True,
373 chromeos_root=chromeos_root,
374 machine=remote)
375 device_chrome_checksum = o.split()[0]
376 if image_chrome_checksum.strip() == device_chrome_checksum.strip():
377 return True
378 else:
379 return False
380
Luis Lozanof81680c2013-03-15 14:44:13 -0700381# Remount partition as writable.
382# TODO: auto-detect if an image is built using --noenable_rootfs_verification.
cmtice13909242014-03-11 13:38:07 -0700383def TryRemountPartitionAsRW(chromeos_root, remote, log_level):
Luis Lozanof81680c2013-03-15 14:44:13 -0700384 l = logger.GetLogger()
cmtice13909242014-03-11 13:38:07 -0700385 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Luis Lozanof81680c2013-03-15 14:44:13 -0700386 command = "sudo mount -o remount,rw /"
387 retval = cmd_executer.CrosRunCommand(\
388 command, chromeos_root=chromeos_root, machine=remote, terminated_timeout=10)
389 if retval:
390 ## Safely ignore.
391 l.LogWarning("Failed to remount partition as rw, "
392 "probably the image was not built with "
393 "\"--noenable_rootfs_verification\", "
394 "you can safely ignore this.")
395 else:
396 l.LogOutput("Re-mounted partition as writable.")
397
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700398
cmtice13909242014-03-11 13:38:07 -0700399def EnsureMachineUp(chromeos_root, remote, log_level):
Ahmad Sharif4467f002012-12-20 12:09:49 -0800400 l = logger.GetLogger()
cmtice13909242014-03-11 13:38:07 -0700401 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Ahmad Sharif4467f002012-12-20 12:09:49 -0800402 timeout = 600
403 magic = "abcdefghijklmnopqrstuvwxyz"
404 command = "echo %s" % magic
405 start_time = time.time()
406 while True:
407 current_time = time.time()
408 if current_time - start_time > timeout:
409 l.LogError("Timeout of %ss reached. Machine still not up. Aborting." %
410 timeout)
411 return False
412 retval = cmd_executer.CrosRunCommand(command,
413 chromeos_root=chromeos_root,
414 machine=remote)
415 if not retval:
416 return True
417
418
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700419if __name__ == "__main__":
cmticee5bc63b2015-05-27 16:59:37 -0700420 retval = DoImage(sys.argv)
421 sys.exit(retval)