blob: 7090845f64e775ecc9af7c4dd1c37eb3279e5cef [file] [log] [blame]
rtc@google.comded22402009-10-26 22:36:21 +00001# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
rtc@google.com64244662009-11-12 00:52:08 +00005from buildutil import BuildObject
rtc@google.comded22402009-10-26 22:36:21 +00006from xml.dom import minidom
7
8import os
Darin Petkov798fe7d2010-03-22 15:18:13 -07009import shutil
rtc@google.comded22402009-10-26 22:36:21 +000010import web
11
rtc@google.com64244662009-11-12 00:52:08 +000012class Autoupdate(BuildObject):
Darin Petkov798fe7d2010-03-22 15:18:13 -070013 # Basic functionality of handling ChromeOS autoupdate pings
rtc@google.com21a5ca32009-11-04 18:23:23 +000014 # and building/serving update images.
15 # TODO(rtc): Clean this code up and write some tests.
rtc@google.comded22402009-10-26 22:36:21 +000016
Sean O'Connor1f7fd362010-04-07 16:34:52 -070017 def __init__(self, serve_only=None, test_image=False, urlbase=None,
18 *args, **kwargs):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070019 super(Autoupdate, self).__init__(*args, **kwargs)
Sean O'Connor1f7fd362010-04-07 16:34:52 -070020 self.serve_only = serve_only
21 self.test_image=test_image
22 self.static_urlbase = urlbase
23 if serve_only:
24 # If we're serving out of an archived build dir (e.g. a
25 # buildbot), prepare this webserver's magic 'static/' dir with a
26 # link to the build archive.
27 web.debug('Autoupdate in "serve update images only" mode.')
28 if os.path.exists('static/archive'):
29 archive_symlink = os.readlink('static/archive')
30 if archive_symlink != self.static_dir:
31 web.debug('removing stale symlink to %s' % self.static_dir)
32 os.unlink('static/archive')
33 else:
34 os.symlink(self.static_dir, 'static/archive')
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070035
rtc@google.com21a5ca32009-11-04 18:23:23 +000036 def GetUpdatePayload(self, hash, size, url):
37 payload = """<?xml version="1.0" encoding="UTF-8"?>
38 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
39 <app appid="{%s}" status="ok">
40 <ping status="ok"/>
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070041 <updatecheck
42 codebase="%s"
43 hash="%s"
44 needsadmin="false"
45 size="%s"
rtc@google.com21a5ca32009-11-04 18:23:23 +000046 status="ok"/>
47 </app>
48 </gupdate>
49 """
50 return payload % (self.app_id, url, hash, size)
rtc@google.comded22402009-10-26 22:36:21 +000051
rtc@google.com21a5ca32009-11-04 18:23:23 +000052 def GetNoUpdatePayload(self):
53 payload = """<?xml version="1.0" encoding="UTF-8"?>
54 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0">
55 <app appid="{%s}" status="ok">
56 <ping status="ok"/>
57 <updatecheck status="noupdate"/>
58 </app>
59 </gupdate>
60 """
61 return payload % self.app_id
rtc@google.comded22402009-10-26 22:36:21 +000062
Sam Leffler76382042010-02-18 09:58:42 -080063 def GetLatestImagePath(self, board_id):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070064 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id)
rtc@google.com21a5ca32009-11-04 18:23:23 +000065 return os.popen(cmd).read().strip()
rtc@google.comded22402009-10-26 22:36:21 +000066
rtc@google.com21a5ca32009-11-04 18:23:23 +000067 def GetLatestVersion(self, latest_image_path):
68 latest_version = latest_image_path.split('/')[-1]
Ryan Cairns1b05beb2010-02-05 17:05:24 -080069
70 # Removes the portage build prefix.
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070071 latest_version = latest_version.lstrip('g-')
rtc@google.com21a5ca32009-11-04 18:23:23 +000072 return latest_version.split('-')[0]
rtc@google.comded22402009-10-26 22:36:21 +000073
rtc@google.com21a5ca32009-11-04 18:23:23 +000074 def CanUpdate(self, client_version, latest_version):
75 """
76 Returns true iff the latest_version is greater than the client_version.
77 """
78 client_tokens = client_version.split('.')
79 latest_tokens = latest_version.split('.')
Sean O'Connor14b6a0a2010-03-20 23:23:48 -070080 web.debug('client version %s latest version %s' \
Charlie Lee8c993082010-02-24 13:27:37 -080081 % (client_version, latest_version))
rtc@google.com21a5ca32009-11-04 18:23:23 +000082 for i in range(0,4):
83 if int(latest_tokens[i]) == int(client_tokens[i]):
84 continue
85 return int(latest_tokens[i]) > int(client_tokens[i])
rtc@google.comded22402009-10-26 22:36:21 +000086 return False
rtc@google.comded22402009-10-26 22:36:21 +000087
Darin Petkov55604f12010-04-12 11:09:25 -070088 def UnpackImage(self, image_path, kernel_file, rootfs_file):
89 if os.path.exists(rootfs_file) and os.path.exists(kernel_file):
Darin Petkovcbcd2bd2010-04-06 10:14:08 -070090 return True
91 if self.test_image:
92 image_file = 'chromiumos_test_image.bin'
93 else:
94 image_file = 'chromiumos_image.bin'
Sean O'Connor1f7fd362010-04-07 16:34:52 -070095 if self.serve_only:
Darin Petkov55604f12010-04-12 11:09:25 -070096 os.system('cd %s && unzip -o image.zip' %
Sean O'Connor1f7fd362010-04-07 16:34:52 -070097 (image_path, image_file))
Darin Petkovcbcd2bd2010-04-06 10:14:08 -070098 os.system('rm -f %s/part_*' % image_path)
99 os.system('cd %s && ./unpack_partitions.sh %s' % (image_path, image_file))
Darin Petkov55604f12010-04-12 11:09:25 -0700100 shutil.move(os.path.join(image_path, 'part_2'), kernel_file)
Darin Petkovcbcd2bd2010-04-06 10:14:08 -0700101 shutil.move(os.path.join(image_path, 'part_3'), rootfs_file)
102 os.system('rm -f %s/part_*' % image_path)
103 return True
104
rtc@google.com21a5ca32009-11-04 18:23:23 +0000105 def BuildUpdateImage(self, image_path):
Darin Petkov55604f12010-04-12 11:09:25 -0700106 kernel_file = '%s/kernel.image' % image_path
107 rootfs_file = '%s/rootfs.image' % image_path
Darin Petkovcbcd2bd2010-04-06 10:14:08 -0700108
Darin Petkov55604f12010-04-12 11:09:25 -0700109 if not self.UnpackImage(image_path, kernel_file, rootfs_file):
110 web.debug('failed to unpack image.')
Darin Petkovcbcd2bd2010-04-06 10:14:08 -0700111 return False
112
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700113 update_file = '%s/update.gz' % image_path
114 if (os.path.exists(update_file) and
Darin Petkov55604f12010-04-12 11:09:25 -0700115 os.path.getmtime(update_file) >= os.path.getmtime(rootfs_file)):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700116 web.debug('Found cached update image %s/update.gz' % image_path)
117 else:
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700118 web.debug('generating update image %s' % update_file)
Darin Petkov55604f12010-04-12 11:09:25 -0700119 mkupdate = ('%s/mk_memento_images.sh %s %s' %
120 (self.scripts_dir, kernel_file, rootfs_file))
rtc@google.com21a5ca32009-11-04 18:23:23 +0000121 web.debug(mkupdate)
122 err = os.system(mkupdate)
123 if err != 0:
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700124 web.debug('failed to create update image')
rtc@google.com21a5ca32009-11-04 18:23:23 +0000125 return False
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700126 if not self.serve_only:
127 web.debug('Found an image, copying it to static')
128 try:
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700129 shutil.copy(update_file, self.static_dir)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700130 except Exception, e:
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700131 web.debug('Unable to copy %s to %s' % (update_file, self.static_dir))
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700132 return False
rtc@google.com21a5ca32009-11-04 18:23:23 +0000133 return True
rtc@google.comded22402009-10-26 22:36:21 +0000134
rtc@google.com21a5ca32009-11-04 18:23:23 +0000135 def GetSize(self, update_path):
136 return os.path.getsize(update_path)
rtc@google.comded22402009-10-26 22:36:21 +0000137
rtc@google.com21a5ca32009-11-04 18:23:23 +0000138 def GetHash(self, update_path):
Darin Petkov8ef83452010-03-23 16:52:29 -0700139 cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \
140 % update_path
141 web.debug(cmd)
142 return os.popen(cmd).read()
143
rtc@google.comded22402009-10-26 22:36:21 +0000144
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700145 def HandleUpdatePing(self, data, label=None):
rtc@google.com21a5ca32009-11-04 18:23:23 +0000146 update_dom = minidom.parseString(data)
147 root = update_dom.firstChild
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700148 query = root.getElementsByTagName('o:app')[0]
Charlie Lee8c993082010-02-24 13:27:37 -0800149 client_version = query.getAttribute('version')
150 board_id = query.hasAttribute('board') and query.getAttribute('board') \
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700151 or 'x86-generic'
Charlie Lee8c993082010-02-24 13:27:37 -0800152 latest_image_path = self.GetLatestImagePath(board_id)
153 latest_version = self.GetLatestVersion(latest_image_path)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700154 if client_version != 'ForcedUpdate' \
Charlie Lee8c993082010-02-24 13:27:37 -0800155 and not self.CanUpdate(client_version, latest_version):
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700156 web.debug('no update')
rtc@google.com21a5ca32009-11-04 18:23:23 +0000157 return self.GetNoUpdatePayload()
rtc@google.com21a5ca32009-11-04 18:23:23 +0000158 hostname = web.ctx.host
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700159 if label:
160 web.debug('Client requested version %s' % label)
161 # Check that matching build exists
162 image_path = '%s/%s' % (self.static_dir, label)
163 if not os.path.exists(image_path):
164 web.debug('%s not found.' % image_path)
165 return self.GetNoUpdatePayload()
166 # Construct a response
167 ok = self.BuildUpdateImage(image_path)
168 if ok != True:
169 web.debug('Failed to build an update image')
170 return self.GetNoUpdatePayload()
171 web.debug('serving update: ')
172 hash = self.GetHash('%s/%s/update.gz' % (self.static_dir, label))
173 size = self.GetSize('%s/%s/update.gz' % (self.static_dir, label))
Sean O'Connor1f7fd362010-04-07 16:34:52 -0700174 # In case we configured images to be hosted elsewhere
175 # (e.g. buildbot's httpd), use that. Otherwise, serve it
176 # ourselves using web.py's static resource handler.
177 if self.static_urlbase:
178 urlbase = self.static_urlbase
179 else:
180 urlbase = 'http://%s/static/archive/' % hostname
181
182 url = '%s/%s/update.gz' % (urlbase, label)
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700183 return self.GetUpdatePayload(hash, size, url)
184 web.debug( 'DONE')
185 else:
186 web.debug('update found %s ' % latest_version)
187 ok = self.BuildUpdateImage(latest_image_path)
188 if ok != True:
189 web.debug('Failed to build an update image')
190 return self.GetNoUpdatePayload()
rtc@google.comded22402009-10-26 22:36:21 +0000191
Sean O'Connor14b6a0a2010-03-20 23:23:48 -0700192 hash = self.GetHash('%s/update.gz' % self.static_dir)
193 size = self.GetSize('%s/update.gz' % self.static_dir)
194
195 url = 'http://%s/static/update.gz' % hostname
196 return self.GetUpdatePayload(hash, size, url)