OLD | NEW |
1 # Copyright (c) 2009 The Chromium OS Authors. All rights reserved. | 1 # 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 from buildutil import BuildObject | 5 from buildutil import BuildObject |
6 from xml.dom import minidom | 6 from xml.dom import minidom |
7 | 7 |
8 import os | 8 import os |
9 import web | 9 import web |
10 | 10 |
11 class Autoupdate(BuildObject): | 11 class Autoupdate(BuildObject): |
12 | |
13 # Basic functionality of handling ChromeOS autoupdate pings | 12 # Basic functionality of handling ChromeOS autoupdate pings |
14 # and building/serving update images. | 13 # and building/serving update images. |
15 # TODO(rtc): Clean this code up and write some tests. | 14 # TODO(rtc): Clean this code up and write some tests. |
16 | 15 |
| 16 def __init__(self, serve_only=None, test_image=False, *args, **kwargs): |
| 17 self.serve_only = serve_only |
| 18 if serve_only: |
| 19 web.debug('Autoupdate in "serve update images only" mode.') |
| 20 self.test_image=test_image |
| 21 super(Autoupdate, self).__init__(*args, **kwargs) |
| 22 |
17 def GetUpdatePayload(self, hash, size, url): | 23 def GetUpdatePayload(self, hash, size, url): |
18 payload = """<?xml version="1.0" encoding="UTF-8"?> | 24 payload = """<?xml version="1.0" encoding="UTF-8"?> |
19 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0"> | 25 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0"> |
20 <app appid="{%s}" status="ok"> | 26 <app appid="{%s}" status="ok"> |
21 <ping status="ok"/> | 27 <ping status="ok"/> |
22 <updatecheck | 28 <updatecheck |
23 codebase="%s" | 29 codebase="%s" |
24 hash="%s" | 30 hash="%s" |
25 needsadmin="false" | 31 needsadmin="false" |
26 size="%s" | 32 size="%s" |
27 status="ok"/> | 33 status="ok"/> |
28 </app> | 34 </app> |
29 </gupdate> | 35 </gupdate> |
30 """ | 36 """ |
31 return payload % (self.app_id, url, hash, size) | 37 return payload % (self.app_id, url, hash, size) |
32 | 38 |
33 def GetNoUpdatePayload(self): | 39 def GetNoUpdatePayload(self): |
34 payload = """<?xml version="1.0" encoding="UTF-8"?> | 40 payload = """<?xml version="1.0" encoding="UTF-8"?> |
35 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0"> | 41 <gupdate xmlns="http://www.google.com/update2/response" protocol="2.0"> |
36 <app appid="{%s}" status="ok"> | 42 <app appid="{%s}" status="ok"> |
37 <ping status="ok"/> | 43 <ping status="ok"/> |
38 <updatecheck status="noupdate"/> | 44 <updatecheck status="noupdate"/> |
39 </app> | 45 </app> |
40 </gupdate> | 46 </gupdate> |
41 """ | 47 """ |
42 return payload % self.app_id | 48 return payload % self.app_id |
43 | 49 |
44 def GetLatestImagePath(self, board_id): | 50 def GetLatestImagePath(self, board_id): |
45 cmd = "%s/get_latest_image.sh --board %s" % (self.scripts_dir, board_id) | 51 cmd = '%s/get_latest_image.sh --board %s' % (self.scripts_dir, board_id) |
46 return os.popen(cmd).read().strip() | 52 return os.popen(cmd).read().strip() |
47 | 53 |
48 def GetLatestVersion(self, latest_image_path): | 54 def GetLatestVersion(self, latest_image_path): |
49 latest_version = latest_image_path.split('/')[-1] | 55 latest_version = latest_image_path.split('/')[-1] |
50 | 56 |
51 # Removes the portage build prefix. | 57 # Removes the portage build prefix. |
52 latest_version = latest_version.lstrip("g-") | 58 latest_version = latest_version.lstrip('g-') |
53 return latest_version.split('-')[0] | 59 return latest_version.split('-')[0] |
54 | 60 |
55 def CanUpdate(self, client_version, latest_version): | 61 def CanUpdate(self, client_version, latest_version): |
56 """ | 62 """ |
57 Returns true iff the latest_version is greater than the client_version. | 63 Returns true iff the latest_version is greater than the client_version. |
58 """ | 64 """ |
59 client_tokens = client_version.split('.') | 65 client_tokens = client_version.split('.') |
60 latest_tokens = latest_version.split('.') | 66 latest_tokens = latest_version.split('.') |
61 web.debug("client version %s latest version %s" \ | 67 web.debug('client version %s latest version %s' \ |
62 % (client_version, latest_version)) | 68 % (client_version, latest_version)) |
63 for i in range(0,4): | 69 for i in range(0,4): |
64 if int(latest_tokens[i]) == int(client_tokens[i]): | 70 if int(latest_tokens[i]) == int(client_tokens[i]): |
65 continue | 71 continue |
66 return int(latest_tokens[i]) > int(client_tokens[i]) | 72 return int(latest_tokens[i]) > int(client_tokens[i]) |
67 return False | 73 return False |
68 | 74 |
69 def BuildUpdateImage(self, image_path): | 75 def BuildUpdateImage(self, image_path): |
70 image_file = "%s/rootfs.image" % image_path | 76 if self.test_image: |
71 web.debug("checking image file %s/update.gz" % image_path) | 77 image_file = '%s/rootfs_test.image' % image_path |
72 if not os.path.exists("%s/update.gz" % image_path): | 78 else: |
73 mkupdate = "%s/mk_memento_images.sh %s" % (self.scripts_dir, image_file) | 79 image_file = '%s/rootfs.image' % image_path |
| 80 update_file = '%s/update.gz' % image_path |
| 81 if (os.path.exists(update_file) and |
| 82 os.path.getmtime(update_file) >= os.path.getmtime(image_file)): |
| 83 web.debug('Found cached update image %s/update.gz' % image_path) |
| 84 else: |
| 85 web.debug('generating update image %s/update.gz' % image_path) |
| 86 mkupdate = '%s/mk_memento_images.sh %s' % (self.scripts_dir, image_file) |
74 web.debug(mkupdate) | 87 web.debug(mkupdate) |
75 err = os.system(mkupdate) | 88 err = os.system(mkupdate) |
76 if err != 0: | 89 if err != 0: |
77 web.debug("failed to create update image") | 90 web.debug('failed to create update image') |
78 return False | 91 return False |
79 | 92 if not self.serve_only: |
80 web.debug("Found an image, copying it to static") | 93 web.debug('Found an image, copying it to static') |
81 err = os.system("cp %s/update.gz %s" % (image_path, self.static_dir)) | 94 try: |
82 if err != 0: | 95 shutil.copyfile('%s/update.gz' % image_path, self.static_dir) |
83 web.debug("Unable to move update.gz from %s to %s" \ | 96 except Exception, e: |
84 % (image_path, self.static_dir)) | 97 web.debug('Unable to copy update.gz from %s to %s' \ |
85 return False | 98 % (image_path, self.static_dir)) |
| 99 return False |
86 return True | 100 return True |
87 | 101 |
88 def GetSize(self, update_path): | 102 def GetSize(self, update_path): |
89 return os.path.getsize(update_path) | 103 return os.path.getsize(update_path) |
90 | 104 |
91 def GetHash(self, update_path): | 105 def GetHash(self, update_path): |
92 cmd = "cat %s | openssl sha1 -binary | openssl base64 | tr \'\\n\' \' \';" \ | 106 update_dir = os.path.dirname(update_path) |
93 % update_path | 107 if not os.path.exists('%s/cksum' % update_dir): |
94 web.debug(cmd) | 108 cmd = ('cat %s | openssl sha1 -binary' |
95 return os.popen(cmd).read() | 109 '| openssl base64 | tr \'\\n\' \' \' | tee %s/cksum' % |
| 110 (update_path, update_dir)) |
| 111 web.debug(cmd) |
| 112 return os.popen(cmd).read() |
| 113 else: |
| 114 web.debug('using cached checksum for %s' % update_path) |
| 115 return file('%s/cksum' % update_dir).read() |
96 | 116 |
97 def HandleUpdatePing(self, data): | 117 def HandleUpdatePing(self, data, label=None): |
98 update_dom = minidom.parseString(data) | 118 update_dom = minidom.parseString(data) |
99 root = update_dom.firstChild | 119 root = update_dom.firstChild |
100 query = root.getElementsByTagName("o:app")[0] | 120 query = root.getElementsByTagName('o:app')[0] |
101 client_version = query.getAttribute('version') | 121 client_version = query.getAttribute('version') |
102 board_id = query.hasAttribute('board') and query.getAttribute('board') \ | 122 board_id = query.hasAttribute('board') and query.getAttribute('board') \ |
103 or "x86-generic" | 123 or 'x86-generic' |
104 latest_image_path = self.GetLatestImagePath(board_id) | 124 latest_image_path = self.GetLatestImagePath(board_id) |
105 latest_version = self.GetLatestVersion(latest_image_path) | 125 latest_version = self.GetLatestVersion(latest_image_path) |
106 if client_version != "ForcedUpdate" \ | 126 if client_version != 'ForcedUpdate' \ |
107 and not self.CanUpdate(client_version, latest_version): | 127 and not self.CanUpdate(client_version, latest_version): |
108 web.debug("no update") | 128 web.debug('no update') |
109 return self.GetNoUpdatePayload() | 129 return self.GetNoUpdatePayload() |
| 130 hostname = web.ctx.host |
| 131 if label: |
| 132 web.debug('Client requested version %s' % label) |
| 133 # Check that matching build exists |
| 134 image_path = '%s/%s' % (self.static_dir, label) |
| 135 if not os.path.exists(image_path): |
| 136 web.debug('%s not found.' % image_path) |
| 137 return self.GetNoUpdatePayload() |
| 138 # Construct a response |
| 139 ok = self.BuildUpdateImage(image_path) |
| 140 if ok != True: |
| 141 web.debug('Failed to build an update image') |
| 142 return self.GetNoUpdatePayload() |
| 143 web.debug('serving update: ') |
| 144 hash = self.GetHash('%s/%s/update.gz' % (self.static_dir, label)) |
| 145 size = self.GetSize('%s/%s/update.gz' % (self.static_dir, label)) |
| 146 url = 'http://%s/static/archive/%s/update.gz' % (hostname, label) |
| 147 return self.GetUpdatePayload(hash, size, url) |
| 148 web.debug( 'DONE') |
| 149 else: |
| 150 web.debug('update found %s ' % latest_version) |
| 151 ok = self.BuildUpdateImage(latest_image_path) |
| 152 if ok != True: |
| 153 web.debug('Failed to build an update image') |
| 154 return self.GetNoUpdatePayload() |
110 | 155 |
111 web.debug("update found %s " % latest_version) | 156 hash = self.GetHash('%s/update.gz' % self.static_dir) |
112 ok = self.BuildUpdateImage(latest_image_path) | 157 size = self.GetSize('%s/update.gz' % self.static_dir) |
113 if ok != True: | |
114 web.debug("Failed to build an update image") | |
115 return self.GetNoUpdatePayload() | |
116 | 158 |
117 hash = self.GetHash("%s/update.gz" % self.static_dir) | 159 url = 'http://%s/static/update.gz' % hostname |
118 size = self.GetSize("%s/update.gz" % self.static_dir) | 160 return self.GetUpdatePayload(hash, size, url) |
119 hostname = web.ctx.host | |
120 url = "http://%s/static/update.gz" % hostname | |
121 return self.GetUpdatePayload(hash, size, url) | |
122 | |
OLD | NEW |