blob: 4b764b062a587eff6407caa352a18bf3480c3d9b [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
Luis Lozano036c9232015-12-10 10:47:01 -0800144 retval, device_checksum, _ = cmd_executer.CrosRunCommandWOutput(
145 command, chromeos_root=options.chromeos_root, machine=options.remote)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700146
cmticee5bc63b2015-05-27 16:59:37 -0700147 device_checksum = device_checksum.strip()
148 image_checksum = str(image_checksum)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700149
cmticee5bc63b2015-05-27 16:59:37 -0700150 l.LogOutput("Image checksum: " + image_checksum)
151 l.LogOutput("Device checksum: " + device_checksum)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700152
cmticee5bc63b2015-05-27 16:59:37 -0700153 if image_checksum != device_checksum:
154 [found, located_image] = LocateOrCopyImage(options.chromeos_root,
155 image,
156 board=board)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700157
cmticee5bc63b2015-05-27 16:59:37 -0700158 reimage = True
159 l.LogOutput("Checksums do not match. Re-imaging...")
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700160
cmticee5bc63b2015-05-27 16:59:37 -0700161 is_test_image = IsImageModdedForTest(options.chromeos_root,
162 located_image, log_level)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700163
cmticee5bc63b2015-05-27 16:59:37 -0700164 if not is_test_image and not options.force:
165 logger.GetLogger().LogFatal("Have to pass --force to image a non-test"
166 " image!")
cmtice0cc4e772014-01-30 15:52:37 -0800167 else:
cmticee5bc63b2015-05-27 16:59:37 -0700168 reimage = True
169 found = True
170 l.LogOutput("Using non-local image; Re-imaging...")
Luis Lozano54db5382015-05-20 15:57:19 -0700171
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700172
cmticee5bc63b2015-05-27 16:59:37 -0700173 if reimage:
174 # If the device has /tmp mounted as noexec, image_to_live.sh can fail.
175 command = "mount -o remount,rw,exec /tmp"
176 cmd_executer.CrosRunCommand(command,
177 chromeos_root=options.chromeos_root,
178 machine=options.remote)
cmticeb1340082014-01-13 13:22:37 -0800179
cmticee5bc63b2015-05-27 16:59:37 -0700180 real_src_dir = os.path.join(os.path.realpath(options.chromeos_root),
181 "src")
182 real_chroot_dir = os.path.join(os.path.realpath(options.chromeos_root),
183 "chroot")
184 if local_image:
185 if located_image.find(real_src_dir) != 0:
186 if located_image.find(real_chroot_dir) != 0:
187 raise Exception("Located image: %s not in chromeos_root: %s" %
188 (located_image, options.chromeos_root))
189 else:
190 chroot_image = located_image[len(real_chroot_dir):]
191 else:
192 chroot_image = os.path.join(
Rahul Chaudhrye30d3422015-06-23 15:41:13 -0700193 "~/trunk/src",
cmticee5bc63b2015-05-27 16:59:37 -0700194 located_image[len(real_src_dir):].lstrip("/"))
195
196 # Check to see if cros flash will work for the remote machine.
197 CheckForCrosFlash(options.chromeos_root, options.remote, log_level)
198
199 if local_image:
200 cros_flash_args = ["--board=%s" % board,
201 "--clobber-stateful",
202 options.remote,
203 chroot_image]
204 else:
205 cros_flash_args = ["--board=%s" % board,
206 "--clobber-stateful",
207 options.remote,
208 image]
209
210 command = ("cros flash %s" % " ".join(cros_flash_args))
211
212 # Workaround for crosbug.com/35684.
213 os.chmod(misc.GetChromeOSKeyFile(options.chromeos_root), 0600)
cmtice13909242014-03-11 13:38:07 -0700214 if log_level == "quiet":
cmtice13909242014-03-11 13:38:07 -0700215 l.LogOutput("CMD : %s" % command)
cmticee5bc63b2015-05-27 16:59:37 -0700216 elif log_level == "average":
217 cmd_executer.SetLogLevel("verbose");
cmticeb1340082014-01-13 13:22:37 -0800218 retval = cmd_executer.ChrootRunCommand(options.chromeos_root,
cmtice6de7f8f2014-03-14 14:08:21 -0700219 command, command_timeout=1800)
cmticeb1340082014-01-13 13:22:37 -0800220
cmticee5bc63b2015-05-27 16:59:37 -0700221 retries = 0
222 while retval != 0 and retries < 2:
223 retries += 1
224 if log_level == "quiet":
225 l.LogOutput("Imaging failed. Retry # %d." % retries)
226 l.LogOutput("CMD : %s" % command)
227 retval = cmd_executer.ChrootRunCommand(options.chromeos_root,
228 command, command_timeout=1800)
cmtice13909242014-03-11 13:38:07 -0700229
cmtice13909242014-03-11 13:38:07 -0700230 if log_level == "average":
cmticee5bc63b2015-05-27 16:59:37 -0700231 cmd_executer.SetLogLevel(log_level)
cmtice0cc4e772014-01-30 15:52:37 -0800232
cmticee5bc63b2015-05-27 16:59:37 -0700233 if found == False:
234 temp_dir = os.path.dirname(located_image)
235 l.LogOutput("Deleting temp image dir: %s" % temp_dir)
236 shutil.rmtree(temp_dir)
237
238 logger.GetLogger().LogFatalIf(retval, "Image command failed")
239
240 # Unfortunately cros_image_to_target.py sometimes returns early when the
241 # machine isn't fully up yet.
242 retval = EnsureMachineUp(options.chromeos_root, options.remote,
243 log_level)
244
245 # If this is a non-local image, then the retval returned from
246 # EnsureMachineUp is the one that will be returned by this function;
247 # in that case, make sure the value in 'retval' is appropriate.
248 if not local_image and retval == True:
249 retval = 0
250 else:
251 retval = 1
252
253 if local_image:
254 if log_level == "average":
255 l.LogOutput("Verifying image.")
256 command = "echo %s > %s && chmod -w %s" % (image_checksum,
257 checksum_file,
258 checksum_file)
259 retval = cmd_executer.CrosRunCommand(command,
260 chromeos_root=options.chromeos_root,
261 machine=options.remote)
262 logger.GetLogger().LogFatalIf(retval, "Writing checksum failed.")
263
264 successfully_imaged = VerifyChromeChecksum(options.chromeos_root,
265 image,
266 options.remote, log_level)
267 logger.GetLogger().LogFatalIf(not successfully_imaged,
268 "Image verification failed!")
269 TryRemountPartitionAsRW(options.chromeos_root, options.remote,
270 log_level)
271 else:
272 l.LogOutput("Checksums match. Skipping reimage")
273 return retval
274 finally:
275 if should_unlock:
276 locks.ReleaseLock(list(options.remote.split()), options.chromeos_root)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700277
278
279def LocateOrCopyImage(chromeos_root, image, board=None):
280 l = logger.GetLogger()
281 if board is None:
282 board_glob = "*"
283 else:
284 board_glob = board
285
286 chromeos_root_realpath = os.path.realpath(chromeos_root)
287 image = os.path.realpath(image)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800288
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700289 if image.startswith("%s/" % chromeos_root_realpath):
290 return [True, image]
291
292 # First search within the existing build dirs for any matching files.
293 images_glob = ("%s/src/build/images/%s/*/*.bin" %
294 (chromeos_root_realpath,
295 board_glob))
296 images_list = glob.glob(images_glob)
297 for potential_image in images_list:
298 if filecmp.cmp(potential_image, image):
299 l.LogOutput("Found matching image %s in chromeos_root." % potential_image)
300 return [True, potential_image]
cmtice13909242014-03-11 13:38:07 -0700301 # We did not find an image. Copy it in the src dir and return the copied
302 # file.
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700303 if board is None:
304 board = ""
305 base_dir = ("%s/src/build/images/%s" %
306 (chromeos_root_realpath,
307 board))
308 if not os.path.isdir(base_dir):
309 os.makedirs(base_dir)
310 temp_dir = tempfile.mkdtemp(prefix="%s/tmp" % base_dir)
311 new_image = "%s/%s" % (temp_dir, os.path.basename(image))
312 l.LogOutput("No matching image found. Copying %s to %s" %
313 (image, new_image))
314 shutil.copyfile(image, new_image)
315 return [False, new_image]
316
317
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800318def GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp):
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700319 image_dir = os.path.dirname(image)
320 image_file = os.path.basename(image)
321 mount_command = ("cd %s/src/scripts &&"
322 "./mount_gpt_image.sh --from=%s --image=%s"
323 " --safe --read_only"
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800324 " --rootfs_mountpt=%s"
325 " --stateful_mountpt=%s" %
326 (chromeos_root, image_dir, image_file, rootfs_mp,
327 stateful_mp))
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700328 return mount_command
329
330
cmtice13909242014-03-11 13:38:07 -0700331def MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level,
332 unmount=False):
333 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800334 command = GetImageMountCommand(chromeos_root, image, rootfs_mp, stateful_mp)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700335 if unmount:
336 command = "%s --unmount" % command
337 retval = cmd_executer.RunCommand(command)
338 logger.GetLogger().LogFatalIf(retval, "Mount/unmount command failed!")
339 return retval
340
341
cmtice13909242014-03-11 13:38:07 -0700342def IsImageModdedForTest(chromeos_root, image, log_level):
343 if log_level != "verbose":
344 log_level = "quiet"
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800345 rootfs_mp = tempfile.mkdtemp()
346 stateful_mp = tempfile.mkdtemp()
cmtice13909242014-03-11 13:38:07 -0700347 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800348 lsb_release_file = os.path.join(rootfs_mp, "etc/lsb-release")
Ahmad Shariff395c262012-10-09 17:48:09 -0700349 lsb_release_contents = open(lsb_release_file).read()
350 is_test_image = re.search("test", lsb_release_contents, re.IGNORECASE)
cmtice13909242014-03-11 13:38:07 -0700351 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level,
352 unmount=True)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700353 return is_test_image
354
355
cmtice13909242014-03-11 13:38:07 -0700356def VerifyChromeChecksum(chromeos_root, image, remote, log_level):
357 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800358 rootfs_mp = tempfile.mkdtemp()
359 stateful_mp = tempfile.mkdtemp()
cmtice13909242014-03-11 13:38:07 -0700360 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800361 image_chrome_checksum = FileUtils().Md5File("%s/opt/google/chrome/chrome" %
cmtice13909242014-03-11 13:38:07 -0700362 rootfs_mp,
363 log_level=log_level)
364 MountImage(chromeos_root, image, rootfs_mp, stateful_mp, log_level,
365 unmount=True)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700366
367 command = "md5sum /opt/google/chrome/chrome"
Luis Lozano036c9232015-12-10 10:47:01 -0800368 [_, o, _] = cmd_executer.CrosRunCommandWOutput(
369 command, chromeos_root=chromeos_root, machine=remote)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700370 device_chrome_checksum = o.split()[0]
371 if image_chrome_checksum.strip() == device_chrome_checksum.strip():
372 return True
373 else:
374 return False
375
Luis Lozanof81680c2013-03-15 14:44:13 -0700376# Remount partition as writable.
377# TODO: auto-detect if an image is built using --noenable_rootfs_verification.
cmtice13909242014-03-11 13:38:07 -0700378def TryRemountPartitionAsRW(chromeos_root, remote, log_level):
Luis Lozanof81680c2013-03-15 14:44:13 -0700379 l = logger.GetLogger()
cmtice13909242014-03-11 13:38:07 -0700380 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Luis Lozanof81680c2013-03-15 14:44:13 -0700381 command = "sudo mount -o remount,rw /"
382 retval = cmd_executer.CrosRunCommand(\
383 command, chromeos_root=chromeos_root, machine=remote, terminated_timeout=10)
384 if retval:
385 ## Safely ignore.
386 l.LogWarning("Failed to remount partition as rw, "
387 "probably the image was not built with "
388 "\"--noenable_rootfs_verification\", "
389 "you can safely ignore this.")
390 else:
391 l.LogOutput("Re-mounted partition as writable.")
392
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700393
cmtice13909242014-03-11 13:38:07 -0700394def EnsureMachineUp(chromeos_root, remote, log_level):
Ahmad Sharif4467f002012-12-20 12:09:49 -0800395 l = logger.GetLogger()
cmtice13909242014-03-11 13:38:07 -0700396 cmd_executer = command_executer.GetCommandExecuter(log_level=log_level)
Ahmad Sharif4467f002012-12-20 12:09:49 -0800397 timeout = 600
398 magic = "abcdefghijklmnopqrstuvwxyz"
399 command = "echo %s" % magic
400 start_time = time.time()
401 while True:
402 current_time = time.time()
403 if current_time - start_time > timeout:
404 l.LogError("Timeout of %ss reached. Machine still not up. Aborting." %
405 timeout)
406 return False
407 retval = cmd_executer.CrosRunCommand(command,
408 chromeos_root=chromeos_root,
409 machine=remote)
410 if not retval:
411 return True
412
413
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700414if __name__ == "__main__":
cmticee5bc63b2015-05-27 16:59:37 -0700415 retval = DoImage(sys.argv)
416 sys.exit(retval)