blob: def8cccc93455f0d418b554ee894aab874172182 [file] [log] [blame]
showard9d02fb52008-08-08 18:20:37 +00001#!/usr/bin/python
2
3"""
4This module defines the BasePackageManager Class which provides an
5implementation of the packaging system API providing methods to fetch,
6upload and remove packages. Site specific extensions to any of these methods
7should inherit this class.
8"""
9
10import re, os, sys, traceback, subprocess, shutil, time, traceback, urlparse
showarda290e982009-03-27 20:57:30 +000011import fcntl, logging
mblighe1836812009-04-17 18:25:14 +000012from autotest_lib.client.common_lib import error, utils, global_config
showard9d02fb52008-08-08 18:20:37 +000013
14
15class PackageUploadError(error.AutotestError):
16 'Raised when there is an error uploading the package'
17
18class PackageFetchError(error.AutotestError):
19 'Raised when there is an error fetching the package'
20
21class PackageRemoveError(error.AutotestError):
22 'Raised when there is an error removing the package'
23
24class PackageInstallError(error.AutotestError):
25 'Raised when there is an error installing the package'
26
mblighe1836812009-04-17 18:25:14 +000027class RepoDiskFull(error.AutotestError):
28 'Raised when the destination for packages is full'
29
30class RepoWriteError(error.AutotestError):
31 "Raised when packager cannot write to a repo's desitnation"
32
33class RepoUnknownError(error.AutotestError):
34 "Raised when packager cannot write to a repo's desitnation"
35
36class RepoError(error.AutotestError):
37 "Raised when a repo isn't working in some way"
38
showard9d02fb52008-08-08 18:20:37 +000039# the name of the checksum file that stores the packages' checksums
40CHECKSUM_FILE = "packages.checksum"
41
mblighe1836812009-04-17 18:25:14 +000042
43def parse_ssh_path(repo):
44 '''
45 Parse ssh://xx@xx/path/to/ and return a tuple with host_line and
46 remote path
47 '''
48
49 match = re.search('^ssh://(.*?)(/.*)$', repo)
50 if match:
51 return match.groups()
52 else:
53 raise PackageUploadError("Incorrect SSH path in global_config: %s"
54 % repo)
55
56
57def repo_run_command(repo, cmd, ignore_status=False):
58 """Run a command relative to the repos path"""
59 repo = repo.strip()
60 run_cmd = None
61 if repo.startswith('ssh://'):
62 username = None
63 hostline, remote_path = parse_ssh_path(repo)
64 if '@' in hostline:
65 username, host = hostline.split('@')
66 run_cmd = 'ssh %s@%s "cd %s && %s"' % (username, host,
67 remote_path, cmd)
68 else:
69 run_cmd = 'ssh %s "cd %s && %s"' % (host, remote_path, cmd)
70
71 else:
72 run_cmd = "cd %s && %s" % (repo, cmd)
73
74 if run_cmd:
75 return utils.run(run_cmd, ignore_status=ignore_status)
76
77
78def check_diskspace(repo, min_free=None):
79 if not min_free:
80 min_free = global_config.global_config.get_config_value('PACKAGES',
81 'minimum_free_space',
82 type=int)
83 try:
84 df = repo_run_command(repo, 'df -mP . | tail -1').stdout.split()
85 free_space_gb = int(df[3])/1000.0
86 except Exception, e:
87 raise RepoUnknownError('Unknown Repo Error: %s' % e)
88 if free_space_gb < min_free:
89 raise RepoDiskFull('Not enough disk space available')
90
91
92def check_write(repo):
93 try:
94 repo_testfile = '.repo_test_file'
95 repo_run_command(repo, 'touch %s' % repo_testfile).stdout.strip()
96 repo_run_command(repo, 'rm ' + repo_testfile)
97 except error.CmdError:
98 raise RepoWriteError('Unable to write to ' + repo)
99
100
101def trim_custom_directories(repo, older_than_days=40):
102 older_than_days = global_config.global_config.get_config_value('PACKAGES',
103 'custom_max_age',
104 type=int)
105 cmd = 'find . -type f -atime %s -exec rm -f {} \;' % older_than_days
106 repo_run_command(repo, cmd, ignore_status=True)
107
108
showard9d02fb52008-08-08 18:20:37 +0000109class BasePackageManager(object):
110 _repo_exception = {}
111 REPO_OK = object()
112
mbligh76d19f72008-10-15 16:24:43 +0000113 def __init__(self, pkgmgr_dir, hostname=None, repo_urls=None,
114 upload_paths=None, do_locking=True, run_function=utils.run,
115 run_function_args=[], run_function_dargs={}):
showard9d02fb52008-08-08 18:20:37 +0000116 '''
117 repo_urls: The list of the repository urls which is consulted
118 whilst fetching the package
119 upload_paths: The list of the upload of repositories to which
120 the package is uploaded to
121 pkgmgr_dir : A directory that can be used by the package manager
122 to dump stuff (like checksum files of the repositories
123 etc.).
124 do_locking : Enable locking when the packages are installed.
125
126 run_function is used to execute the commands throughout this file.
127 It defaults to utils.run() but a custom method (if provided) should
128 be of the same schema as utils.run. It should return a CmdResult
129 object and throw a CmdError exception. The reason for using a separate
130 function to run the commands is that the same code can be run to fetch
131 a package on the local machine or on a remote machine (in which case
132 ssh_host's run function is passed in for run_function).
133 '''
134 # In memory dictionary that stores the checksum's of packages
135 self._checksum_dict = {}
136
137 self.pkgmgr_dir = pkgmgr_dir
138 self.do_locking = do_locking
mbligh76d19f72008-10-15 16:24:43 +0000139 self.hostname = hostname
showard9d02fb52008-08-08 18:20:37 +0000140
141 # Process the repository URLs and the upload paths if specified
142 if not repo_urls:
143 self.repo_urls = []
144 else:
mbligh76d19f72008-10-15 16:24:43 +0000145 if hostname:
146 self.repo_urls = repo_urls
147 self.repo_urls = list(self.get_mirror_list())
148 else:
149 self.repo_urls = list(repo_urls)
showard9d02fb52008-08-08 18:20:37 +0000150 if not upload_paths:
151 self.upload_paths = []
152 else:
153 self.upload_paths = list(upload_paths)
154
mblighe1836812009-04-17 18:25:14 +0000155
showard9d02fb52008-08-08 18:20:37 +0000156 # Create an internal function that is a simple wrapper of
157 # run_function and takes in the args and dargs as arguments
158 def _run_command(command, _run_command_args=run_function_args,
159 _run_command_dargs={}):
160 '''
161 Special internal function that takes in a command as
162 argument and passes it on to run_function (if specified).
163 The _run_command_dargs are merged into run_function_dargs
164 with the former having more precedence than the latter.
165 '''
166 new_dargs = dict(run_function_dargs)
167 new_dargs.update(_run_command_dargs)
showard108d73e2009-06-22 18:14:41 +0000168 # avoid polluting logs with extremely verbose packaging output
169 new_dargs.update({'stdout_tee' : None})
showard9d02fb52008-08-08 18:20:37 +0000170
171 return run_function(command, *_run_command_args,
172 **new_dargs)
173
174 self._run_command = _run_command
175
mblighe1836812009-04-17 18:25:14 +0000176 def repo_check(self, repo):
177 '''
178 Check to make sure the repo is in a sane state:
179 ensure we have at least XX amount of free space
180 Make sure we can write to the repo
181 '''
182 try:
183 check_diskspace(repo)
184 check_write(repo)
185 except (RepoWriteError, RepoUnknownError, RepoDiskFull), e:
186 raise RepoError("ERROR: Repo %s: %s" % (repo, e))
187
188
189 def upkeep(self, custom_repos=None):
190 '''
191 Clean up custom upload/download areas
192 '''
mbligh1a519b92009-04-17 18:26:19 +0000193 from autotest_lib.server import subcommand
mblighe1836812009-04-17 18:25:14 +0000194 if not custom_repos:
195 custom_repos = global_config.global_config.get_config_value('PACKAGES',
196 'custom_upload_location').split(',')
197 custom_download = global_config.global_config.get_config_value(
198 'PACKAGES', 'custom_download_location')
199 custom_repos += [custom_download]
200
201 results = subcommand.parallel_simple(trim_custom_directories,
202 custom_repos, log=False, )
203
showard9d02fb52008-08-08 18:20:37 +0000204
205 def install_pkg(self, name, pkg_type, fetch_dir, install_dir,
206 preserve_install_dir=False, repo_url=None):
207 '''
208 Remove install_dir if it already exists and then recreate it unless
209 preserve_install_dir is specified as True.
210 Fetch the package into the pkg_dir. Untar the package into install_dir
211 The assumption is that packages are of the form :
212 <pkg_type>.<pkg_name>.tar.bz2
213 name : name of the package
214 type : type of the package
215 fetch_dir : The directory into which the package tarball will be
216 fetched to.
217 install_dir : the directory where the package files will be untarred to
218 repo_url : the url of the repository to fetch the package from.
219 '''
220
221 # do_locking flag is on by default unless you disable it (typically
222 # in the cases where packages are directly installed from the server
223 # onto the client in which case fcntl stuff wont work as the code
224 # will run on the server in that case..
225 if self.do_locking:
226 lockfile_name = '.%s-%s-lock' % (name, pkg_type)
227 lockfile = open(os.path.join(self.pkgmgr_dir, lockfile_name), 'w')
228
229 try:
230 if self.do_locking:
231 fcntl.flock(lockfile, fcntl.LOCK_EX)
232
233 self._run_command('mkdir -p %s' % fetch_dir)
234
235 pkg_name = self.get_tarball_name(name, pkg_type)
236 fetch_path = os.path.join(fetch_dir, pkg_name)
237 try:
238 # Fetch the package into fetch_dir
mbligh76d19f72008-10-15 16:24:43 +0000239 self.fetch_pkg(pkg_name, fetch_path, use_checksum=True)
showard9d02fb52008-08-08 18:20:37 +0000240
241 # check to see if the install_dir exists and if it does
242 # then check to see if the .checksum file is the latest
243 install_dir_exists = False
244 try:
245 self._run_command("ls %s" % install_dir)
246 install_dir_exists = True
247 except (error.CmdError, error.AutoservRunError):
248 pass
249
250 if (install_dir_exists and
251 not self.untar_required(fetch_path, install_dir)):
252 return
253
254 # untar the package into install_dir and
255 # update the checksum in that directory
256 if not preserve_install_dir:
257 # Make sure we clean up the install_dir
258 self._run_command('rm -rf %s' % install_dir)
259 self._run_command('mkdir -p %s' % install_dir)
260
261 self.untar_pkg(fetch_path, install_dir)
262
263 except PackageFetchError, why:
264 raise PackageInstallError('Installation of %s(type:%s) failed'
265 ' : %s' % (name, pkg_type, why))
266 finally:
267 if self.do_locking:
268 fcntl.flock(lockfile, fcntl.LOCK_UN)
269 lockfile.close()
270
271
mbligh76d19f72008-10-15 16:24:43 +0000272 def fetch_pkg(self, pkg_name, dest_path, repo_url=None, use_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000273 '''
274 Fetch the package into dest_dir from repo_url. By default repo_url
275 is None and the package is looked in all the repostories specified.
276 Otherwise it fetches it from the specific repo_url.
277 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
278 dep-gcc.tar.bz2, kernel.1-1.rpm)
279 repo_url : the URL of the repository where the package is located.
280 dest_path : complete path of where the package will be fetched to.
281 use_checksum : This is set to False to fetch the packages.checksum file
282 so that the checksum comparison is bypassed for the
283 checksum file itself. This is used internally by the
284 packaging system. It should be ignored by externals
285 callers of this method who use it fetch custom packages.
286 '''
287
288 try:
289 self._run_command("ls %s" % os.path.dirname(dest_path))
290 except (error.CmdError, error.AutoservRunError):
291 raise PackageFetchError("Please provide a valid "
292 "destination: %s " % dest_path)
293
294 # See if the package was already fetched earlier, if so
295 # the checksums need to be compared and the package is now
296 # fetched only if they differ.
297 pkg_exists = False
298 try:
299 self._run_command("ls %s" % dest_path)
300 pkg_exists = True
301 except (error.CmdError, error.AutoservRunError):
302 pass
303
304 # if a repository location is explicitly provided, fetch the package
305 # from there and return
306 if repo_url:
307 repo_url_list = [repo_url]
308 elif len(self.repo_urls) > 0:
309 repo_url_list = self.repo_urls
310 else:
311 raise PackageFetchError("There are no repository urls specified")
312
313 error_msgs = {}
314 for location in repo_url_list:
315 try:
316 # Fetch the checksum if it not there
317 if not use_checksum:
318 self.fetch_pkg_file(pkg_name, dest_path, location)
319
320 # Fetch the package if a) the pkg does not exist or
321 # b) if the checksum differs for the existing package
322 elif (not pkg_exists or
323 not self.compare_checksum(dest_path, location)):
324 self.fetch_pkg_file(pkg_name, dest_path, location)
325 # Update the checksum of the package in the packages'
326 # checksum file
327 self.update_checksum(dest_path)
328 return
mbligh7a603672009-02-07 01:52:08 +0000329 except (PackageFetchError, error.AutoservRunError):
showard9d02fb52008-08-08 18:20:37 +0000330 # The package could not be found in this repo, continue looking
showarda290e982009-03-27 20:57:30 +0000331 logging.error('%s could not be fetched from %s', pkg_name,
332 location)
showard9d02fb52008-08-08 18:20:37 +0000333
334 # if we got here then that means the package is not found
335 # in any of the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000336 raise PackageFetchError("%s could not be fetched from any of"
337 " the repos %s : %s " % (pkg_name,
338 repo_url_list,
showard9d02fb52008-08-08 18:20:37 +0000339 error_msgs))
340
341
mbligh76d19f72008-10-15 16:24:43 +0000342 def fetch_pkg_file(self, filename, dest_path, source_url):
showard9d02fb52008-08-08 18:20:37 +0000343 """
344 Fetch the file from source_url into dest_path. The package repository
345 url is parsed and the appropriate retrieval method is determined.
346
347 """
348 if source_url.startswith('http://'):
mbligh76d19f72008-10-15 16:24:43 +0000349 self.fetch_file_http(filename, dest_path, source_url)
showard9d02fb52008-08-08 18:20:37 +0000350 else:
mbligh76d19f72008-10-15 16:24:43 +0000351 raise PackageFetchError("Invalid location %s" % source_url)
showard9d02fb52008-08-08 18:20:37 +0000352
353
mbligh76d19f72008-10-15 16:24:43 +0000354 def fetch_file_http(self, filename, dest_path, source_url):
showard9d02fb52008-08-08 18:20:37 +0000355 """
356 Fetch the package using http protocol. Raises a PackageFetchError.
357 """
showarda290e982009-03-27 20:57:30 +0000358 logging.info("Fetching %s from %s to %s", filename, source_url,
359 dest_path)
showard9d02fb52008-08-08 18:20:37 +0000360 # check to see if the source_url is reachable or not
361 self.run_http_test(source_url, os.path.dirname(dest_path))
362
mbligh76d19f72008-10-15 16:24:43 +0000363 pkg_path = os.path.join(source_url, filename)
showard9d02fb52008-08-08 18:20:37 +0000364 try:
mblighdc1e7aa2008-10-10 21:12:15 +0000365 self._run_command('wget -nv %s -O %s' % (pkg_path, dest_path))
showarda290e982009-03-27 20:57:30 +0000366 logging.debug("Successfully fetched %s from %s", filename,
367 source_url)
mbligh7a603672009-02-07 01:52:08 +0000368 except error.CmdError:
369 raise PackageFetchError("%s not found in %s" % (filename,
370 source_url))
showard9d02fb52008-08-08 18:20:37 +0000371
372
373 def run_http_test(self, source_url, dest_dir):
374 '''
375 Run a simple 30 sec wget on source_url
376 just to see if it can be reachable or not. This avoids the need
377 for waiting for a 10min timeout.
378 '''
379 dest_file_path = os.path.join(dest_dir, 'http_test_file')
380
381 BPM = BasePackageManager
382 error_msg = "HTTP test failed. Failed to contact"
383 # We should never get here unless the source_url starts with http://
384 assert(source_url.startswith('http://'))
385
386 # Get the http server name from the URL
387 server_name = urlparse.urlparse(source_url)[1]
mbligh76d19f72008-10-15 16:24:43 +0000388 http_cmd = 'wget -nv %s -O %s' % (server_name, dest_file_path)
mblighabe330e2008-12-09 23:37:52 +0000389
390 # Following repo_exception optimization is disabled for now.
391 # Checksum files are optional. The attempted download of a
392 # missing checksum file erroneously causes the repos to be marked
393 # dead, causing download of its custom kernels to fail.
394 # It also stays dead until Autotest is restarted.
395 if server_name in BPM._repo_exception and False: # <--- TEMP
showard9d02fb52008-08-08 18:20:37 +0000396 if BPM._repo_exception[server_name] == BPM.REPO_OK:
397 # This repository is fine. Simply return
398 return
399 else:
400 raise PackageFetchError("%s - %s : %s "
401 % (error_msg, server_name,
402 BPM._repo_exception[server_name]))
403 try:
404 try:
405 self._run_command(http_cmd,
406 _run_command_dargs={'timeout':30})
407 BPM._repo_exception[server_name] = BPM.REPO_OK
408 finally:
409 self._run_command('rm -f %s' % dest_file_path)
mbligh76d19f72008-10-15 16:24:43 +0000410 except Exception, e:
showard9d02fb52008-08-08 18:20:37 +0000411 BPM._repo_exception[server_name] = e
mbligh76d19f72008-10-15 16:24:43 +0000412 raise PackageFetchError("%s - %s: %s " % (error_msg, server_name,
413 e))
showard9d02fb52008-08-08 18:20:37 +0000414
415
mblighe1836812009-04-17 18:25:14 +0000416 def upload_pkg(self, pkg_path, upload_path=None, update_checksum=False):
mbligh1a519b92009-04-17 18:26:19 +0000417 from autotest_lib.server import subcommand
mblighe1836812009-04-17 18:25:14 +0000418 if upload_path:
419 upload_path_list = [upload_path]
420 self.upkeep(upload_path_list)
421 elif len(self.upload_paths) > 0:
422 self.upkeep()
423 upload_path_list = self.upload_paths
424 else:
425 raise PackageUploadError("Invalid Upload Path specified")
426
mblighcf57ae22009-04-17 20:13:01 +0000427 if update_checksum:
428 # get the packages' checksum file and update it with the current
429 # package's checksum
430 self.update_checksum(pkg_path)
431
mblighe1836812009-04-17 18:25:14 +0000432 commands = []
433 for path in upload_path_list:
434 commands.append(subcommand.subcommand(self.upload_pkg_parallel,
435 (pkg_path, path,
436 update_checksum)))
437
438 results = subcommand.parallel(commands, 300, return_results=True)
439 for result in results:
440 if result:
441 print str(result)
442
443
showard9d02fb52008-08-08 18:20:37 +0000444 # TODO(aganti): Fix the bug with the current checksum logic where
445 # packages' checksums that are not present consistently in all the
446 # repositories are not handled properly. This is a corner case though
447 # but the ideal solution is to make the checksum file repository specific
448 # and then maintain it.
mblighe1836812009-04-17 18:25:14 +0000449 def upload_pkg_parallel(self, pkg_path, upload_path, update_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000450 '''
451 Uploads to a specified upload_path or to all the repos.
452 Also uploads the checksum file to all the repos.
453 pkg_path : The complete path to the package file
454 upload_path : the absolute path where the files are copied to.
455 if set to 'None' assumes 'all' repos
456 update_checksum : If set to False, the checksum file is not
457 going to be updated which happens by default.
458 This is necessary for custom
459 packages (like custom kernels and custom tests)
460 that get uploaded which do not need to be part of
461 the checksum file and bloat it.
462 '''
mblighe1836812009-04-17 18:25:14 +0000463 self.repo_check(upload_path)
showard9d02fb52008-08-08 18:20:37 +0000464 # upload the package
mblighe1836812009-04-17 18:25:14 +0000465 if os.path.isdir(pkg_path):
466 self.upload_pkg_dir(pkg_path, upload_path)
467 else:
468 self.upload_pkg_file(pkg_path, upload_path)
showard9d02fb52008-08-08 18:20:37 +0000469 if update_checksum:
mblighcf57ae22009-04-17 20:13:01 +0000470 self.upload_pkg_file(self._get_checksum_file_path(),
471 upload_path)
showard9d02fb52008-08-08 18:20:37 +0000472
473
474 def upload_pkg_file(self, file_path, upload_path):
475 '''
476 Upload a single file. Depending on the upload path, the appropriate
477 method for that protocol is called. Currently this simply copies the
478 file to the target directory (but can be extended for other protocols)
479 This assumes that the web server is running on the same machine where
480 the method is being called from. The upload_path's files are
481 basically served by that web server.
482 '''
483 try:
mbligh93a9e292008-10-10 21:09:53 +0000484 if upload_path.startswith('ssh://'):
485 # parse ssh://user@host/usr/local/autotest/packages
mblighe1836812009-04-17 18:25:14 +0000486 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000487 try:
488 utils.run('scp %s %s:%s' % (file_path, hostline,
489 remote_path))
490 r_path = os.path.join(remote_path,
491 os.path.basename(file_path))
492 utils.run("ssh %s 'chmod 644 %s'" % (hostline, r_path))
493 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000494 logging.error("Error uploading to repository %s",
495 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000496 else:
497 shutil.copy(file_path, upload_path)
498 os.chmod(os.path.join(upload_path,
499 os.path.basename(file_path)), 0644)
showard9d02fb52008-08-08 18:20:37 +0000500 except (IOError, os.error), why:
showarda290e982009-03-27 20:57:30 +0000501 logging.error("Upload of %s to %s failed: %s", file_path,
502 upload_path, why)
showard9d02fb52008-08-08 18:20:37 +0000503
504
mbligh9fc77972008-10-02 20:32:09 +0000505 def upload_pkg_dir(self, dir_path, upload_path):
506 '''
507 Upload a full directory. Depending on the upload path, the appropriate
508 method for that protocol is called. Currently this copies the whole
509 tmp package directory to the target directory.
510 This assumes that the web server is running on the same machine where
511 the method is being called from. The upload_path's files are
512 basically served by that web server.
513 '''
mbligh93a9e292008-10-10 21:09:53 +0000514 local_path = os.path.join(dir_path, "*")
mbligh9fc77972008-10-02 20:32:09 +0000515 try:
mbligh93a9e292008-10-10 21:09:53 +0000516 if upload_path.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000517 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000518 try:
519 utils.run('scp %s %s:%s' % (local_path, hostline,
520 remote_path))
521 ssh_path = os.path.join(remote_path, "*")
522 utils.run("ssh %s 'chmod 644 %s'" % (hostline, ssh_path))
523 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000524 logging.error("Error uploading to repository: %s",
525 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000526 else:
527 utils.run("cp %s %s " % (local_path, upload_path))
528 up_path = os.path.join(upload_path, "*")
529 utils.run("chmod 644 %s" % up_path)
mbligh9fc77972008-10-02 20:32:09 +0000530 except (IOError, os.error), why:
531 raise PackageUploadError("Upload of %s to %s failed: %s"
532 % (dir_path, upload_path, why))
533
534
showard9d02fb52008-08-08 18:20:37 +0000535 def remove_pkg(self, pkg_name, remove_path=None, remove_checksum=False):
536 '''
537 Remove the package from the specified remove_path
538 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
539 dep-gcc.tar.bz2)
540 remove_path : the location to remove the package from.
541
542 '''
543 if remove_path:
544 remove_path_list = [remove_path]
545 elif len(self.upload_paths) > 0:
546 remove_path_list = self.upload_paths
547 else:
548 raise PackageRemoveError("Invalid path to remove the pkg from")
549
550 checksum_path = self._get_checksum_file_path()
551
552 if remove_checksum:
553 self.remove_checksum(pkg_name)
554
555 # remove the package and upload the checksum file to the repos
556 for path in remove_path_list:
557 self.remove_pkg_file(pkg_name, path)
558 self.upload_pkg_file(checksum_path, path)
559
560
mbligh76d19f72008-10-15 16:24:43 +0000561 def remove_pkg_file(self, filename, pkg_dir):
showard9d02fb52008-08-08 18:20:37 +0000562 '''
mbligh76d19f72008-10-15 16:24:43 +0000563 Remove the file named filename from pkg_dir
showard9d02fb52008-08-08 18:20:37 +0000564 '''
565 try:
566 # Remove the file
mbligh93a9e292008-10-10 21:09:53 +0000567 if pkg_dir.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000568 hostline, remote_path = parse_ssh_path(pkg_dir)
mbligh76d19f72008-10-15 16:24:43 +0000569 path = os.path.join(remote_path, filename)
mbligh93a9e292008-10-10 21:09:53 +0000570 utils.run("ssh %s 'rm -rf %s/%s'" % (hostline, remote_path,
571 path))
572 else:
mbligh76d19f72008-10-15 16:24:43 +0000573 os.remove(os.path.join(pkg_dir, filename))
showard9d02fb52008-08-08 18:20:37 +0000574 except (IOError, os.error), why:
575 raise PackageRemoveError("Could not remove %s from %s: %s "
mbligh76d19f72008-10-15 16:24:43 +0000576 % (filename, pkg_dir, why))
showard9d02fb52008-08-08 18:20:37 +0000577
578
mbligh76d19f72008-10-15 16:24:43 +0000579 def get_mirror_list(self):
mbligh1e3b0992008-10-14 16:29:54 +0000580 '''
mbligh76d19f72008-10-15 16:24:43 +0000581 Stub function for site specific mirrors.
mbligh1e3b0992008-10-14 16:29:54 +0000582
583 Returns:
584 Priority ordered list
585 '''
586 return self.repo_urls
587
588
showard9d02fb52008-08-08 18:20:37 +0000589 def _get_checksum_file_path(self):
590 '''
591 Return the complete path of the checksum file (assumed to be stored
592 in self.pkgmgr_dir
593 '''
594 return os.path.join(self.pkgmgr_dir, CHECKSUM_FILE)
595
596
597 def _get_checksum_dict(self):
598 '''
599 Fetch the checksum file if not already fetched. If the checksum file
600 cannot be fetched from the repos then a new file is created with
601 the current package's (specified in pkg_path) checksum value in it.
602 Populate the local checksum dictionary with the values read from
603 the checksum file.
604 The checksum file is assumed to be present in self.pkgmgr_dir
605 '''
606 checksum_path = self._get_checksum_file_path()
607 if not self._checksum_dict:
608 # Fetch the checksum file
609 try:
610 try:
611 self._run_command("ls %s" % checksum_path)
612 except (error.CmdError, error.AutoservRunError):
613 # The packages checksum file does not exist locally.
614 # See if it is present in the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000615 self.fetch_pkg(CHECKSUM_FILE, checksum_path)
showard9d02fb52008-08-08 18:20:37 +0000616 except PackageFetchError, e:
617 # This should not happen whilst fetching a package..if a
618 # package is present in the repository, the corresponding
619 # checksum file should also be automatically present. This
620 # case happens only when a package
621 # is being uploaded and if it is the first package to be
622 # uploaded to the repos (hence no checksum file created yet)
623 # Return an empty dictionary in that case
624 return {}
625
626 # Read the checksum file into memory
627 checksum_file_contents = self._run_command('cat '
628 + checksum_path).stdout
629
630 # Return {} if we have an empty checksum file present
631 if not checksum_file_contents.strip():
632 return {}
633
634 # Parse the checksum file contents into self._checksum_dict
635 for line in checksum_file_contents.splitlines():
636 checksum, package_name = line.split(None, 1)
637 self._checksum_dict[package_name] = checksum
638
639 return self._checksum_dict
640
641
642 def _save_checksum_dict(self, checksum_dict):
643 '''
644 Save the checksum dictionary onto the checksum file. Update the
645 local _checksum_dict variable with this new set of values.
646 checksum_dict : New checksum dictionary
647 checksum_dir : The directory in which to store the checksum file to.
648 '''
649 checksum_path = self._get_checksum_file_path()
650 self._checksum_dict = checksum_dict.copy()
651 checksum_contents = '\n'.join(checksum + ' ' + pkg_name
652 for pkg_name,checksum in
653 checksum_dict.iteritems())
654 # Write the checksum file back to disk
655 self._run_command('echo "%s" > %s' % (checksum_contents,
656 checksum_path))
657
658
659 def compute_checksum(self, pkg_path):
660 '''
661 Compute the MD5 checksum for the package file and return it.
662 pkg_path : The complete path for the package file
663 '''
664 md5sum_output = self._run_command("md5sum %s " % pkg_path).stdout
665 return md5sum_output.split()[0]
666
667
668 def update_checksum(self, pkg_path):
669 '''
670 Update the checksum of the package in the packages' checksum
671 file. This method is called whenever a package is fetched just
672 to be sure that the checksums in the local file are the latest.
673 pkg_path : The complete path to the package file.
674 '''
675 # Compute the new checksum
676 new_checksum = self.compute_checksum(pkg_path)
677 checksum_dict = self._get_checksum_dict()
678 checksum_dict[os.path.basename(pkg_path)] = new_checksum
679 self._save_checksum_dict(checksum_dict)
680
681
682 def remove_checksum(self, pkg_name):
683 '''
684 Remove the checksum of the package from the packages checksum file.
685 This method is called whenever a package is removed from the
686 repositories in order clean its corresponding checksum.
687 pkg_name : The name of the package to be removed
688 '''
689 checksum_dict = self._get_checksum_dict()
690 if pkg_name in checksum_dict:
691 del checksum_dict[pkg_name]
692 self._save_checksum_dict(checksum_dict)
693
694
695 def compare_checksum(self, pkg_path, repo_url):
696 '''
697 Calculate the checksum of the file specified in pkg_path and
698 compare it with the checksum in the checksum file
699 Return True if both match else return False.
700 pkg_path : The full path to the package file for which the
701 checksum is being compared
702 repo_url : The URL to fetch the checksum from
703 '''
704 checksum_dict = self._get_checksum_dict()
705 package_name = os.path.basename(pkg_path)
706 if not checksum_dict or package_name not in checksum_dict:
707 return False
708
709 repository_checksum = checksum_dict[package_name]
710 local_checksum = self.compute_checksum(pkg_path)
711 return (local_checksum == repository_checksum)
712
713
mblighdbfc4e32008-08-22 18:08:07 +0000714 def tar_package(self, pkg_name, src_dir, dest_dir, exclude_string=None):
showard9d02fb52008-08-08 18:20:37 +0000715 '''
716 Create a tar.bz2 file with the name 'pkg_name' say test-blah.tar.bz2.
mbligh9fc77972008-10-02 20:32:09 +0000717 Excludes the directories specified in exclude_string while tarring
showard9d02fb52008-08-08 18:20:37 +0000718 the source. Returns the tarball path.
719 '''
showard9d02fb52008-08-08 18:20:37 +0000720 tarball_path = os.path.join(dest_dir, pkg_name)
mblighd5a61a02009-05-21 01:44:30 +0000721 temp_path = tarball_path + '.tmp'
722 cmd = "tar -cvjf %s -C %s %s " % (temp_path, src_dir, exclude_string)
showard9d02fb52008-08-08 18:20:37 +0000723
mblighd5a61a02009-05-21 01:44:30 +0000724 try:
725 utils.system(cmd)
726 except:
727 os.unlink(temp_path)
728 raise
729
730 os.rename(temp_path, tarball_path)
showard9d02fb52008-08-08 18:20:37 +0000731 return tarball_path
732
733
734 def untar_required(self, tarball_path, dest_dir):
735 '''
736 Compare the checksum of the tarball_path with the .checksum file
737 in the dest_dir and return False if it matches. The untar
738 of the package happens only if the checksums do not match.
739 '''
740 checksum_path = os.path.join(dest_dir, '.checksum')
741 try:
742 existing_checksum = self._run_command('cat ' + checksum_path).stdout
743 except (error.CmdError, error.AutoservRunError):
744 # If the .checksum file is not present (generally, this should
745 # not be the case) then return True so that the untar happens
746 return True
747
748 new_checksum = self.compute_checksum(tarball_path)
749 return (new_checksum.strip() != existing_checksum.strip())
750
751
752 def untar_pkg(self, tarball_path, dest_dir):
753 '''
754 Untar the package present in the tarball_path and put a
755 ".checksum" file in the dest_dir containing the checksum
756 of the tarball. This method
757 assumes that the package to be untarred is of the form
758 <name>.tar.bz2
759 '''
mbligh96ad8512008-10-03 03:45:26 +0000760 self._run_command('tar xjf %s -C %s' % (tarball_path, dest_dir))
showard9d02fb52008-08-08 18:20:37 +0000761 # Put the .checksum file in the install_dir to note
762 # where the package came from
763 pkg_checksum = self.compute_checksum(tarball_path)
764 pkg_checksum_path = os.path.join(dest_dir,
765 '.checksum')
766 self._run_command('echo "%s" > %s '
767 % (pkg_checksum, pkg_checksum_path))
768
769
770 def get_tarball_name(self, name, pkg_type):
771 return "%s-%s.tar.bz2" % (pkg_type, name)
772
773
774 def is_url(self, url):
775 """Return true if path looks like a URL"""
776 return url.startswith('http://')
777
778
779 def get_package_name(self, url, pkg_type):
780 '''
781 Extract the group and test name for the url. This method is currently
782 used only for tests.
783 '''
784 if pkg_type == 'test':
mblighecbaec32008-10-25 13:37:42 +0000785 regex = '[^:]+://(.*)/([^/]*)$'
showard9d02fb52008-08-08 18:20:37 +0000786 return self._get_package_name(url, regex)
787 else:
788 return ('', url)
789
790
791 def _get_package_name(self, url, regex):
792 if not self.is_url(url):
793 if url.endswith('.tar.bz2'):
794 testname = url.replace('.tar.bz2', '')
795 testname = re.sub(r'(\d*)\.', '', testname)
796 return (testname, testname)
797 else:
798 return ('', url)
799
800 match = re.match(regex, url)
801 if not match:
802 return ('', url)
803 group, filename = match.groups()
804 # Generate the group prefix.
805 group = re.sub(r'\W', '_', group)
806 # Drop the extension to get the raw test name.
807 testname = re.sub(r'\.tar\.bz2', '', filename)
808 # Drop any random numbers at the end of the test name if any
809 testname = re.sub(r'\.(\d*)', '', testname)
810 return (group, testname)
811
812
mbligha7007722009-01-13 00:37:11 +0000813SitePackageManager = utils.import_site_class(
814 __file__, "autotest_lib.client.common_lib.site_packages",
815 "SitePackageManager", BasePackageManager)
showard9d02fb52008-08-08 18:20:37 +0000816
817class PackageManager(SitePackageManager):
818 pass