blob: 3f4ba344d896f0b9350faa87c2404b3fbf2abb49 [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
13from autotest_lib.server import subcommand
showard9d02fb52008-08-08 18:20:37 +000014
15
16class PackageUploadError(error.AutotestError):
17 'Raised when there is an error uploading the package'
18
19class PackageFetchError(error.AutotestError):
20 'Raised when there is an error fetching the package'
21
22class PackageRemoveError(error.AutotestError):
23 'Raised when there is an error removing the package'
24
25class PackageInstallError(error.AutotestError):
26 'Raised when there is an error installing the package'
27
mblighe1836812009-04-17 18:25:14 +000028class RepoDiskFull(error.AutotestError):
29 'Raised when the destination for packages is full'
30
31class RepoWriteError(error.AutotestError):
32 "Raised when packager cannot write to a repo's desitnation"
33
34class RepoUnknownError(error.AutotestError):
35 "Raised when packager cannot write to a repo's desitnation"
36
37class RepoError(error.AutotestError):
38 "Raised when a repo isn't working in some way"
39
showard9d02fb52008-08-08 18:20:37 +000040# the name of the checksum file that stores the packages' checksums
41CHECKSUM_FILE = "packages.checksum"
42
mblighe1836812009-04-17 18:25:14 +000043
44def parse_ssh_path(repo):
45 '''
46 Parse ssh://xx@xx/path/to/ and return a tuple with host_line and
47 remote path
48 '''
49
50 match = re.search('^ssh://(.*?)(/.*)$', repo)
51 if match:
52 return match.groups()
53 else:
54 raise PackageUploadError("Incorrect SSH path in global_config: %s"
55 % repo)
56
57
58def repo_run_command(repo, cmd, ignore_status=False):
59 """Run a command relative to the repos path"""
60 repo = repo.strip()
61 run_cmd = None
62 if repo.startswith('ssh://'):
63 username = None
64 hostline, remote_path = parse_ssh_path(repo)
65 if '@' in hostline:
66 username, host = hostline.split('@')
67 run_cmd = 'ssh %s@%s "cd %s && %s"' % (username, host,
68 remote_path, cmd)
69 else:
70 run_cmd = 'ssh %s "cd %s && %s"' % (host, remote_path, cmd)
71
72 else:
73 run_cmd = "cd %s && %s" % (repo, cmd)
74
75 if run_cmd:
76 return utils.run(run_cmd, ignore_status=ignore_status)
77
78
79def check_diskspace(repo, min_free=None):
80 if not min_free:
81 min_free = global_config.global_config.get_config_value('PACKAGES',
82 'minimum_free_space',
83 type=int)
84 try:
85 df = repo_run_command(repo, 'df -mP . | tail -1').stdout.split()
86 free_space_gb = int(df[3])/1000.0
87 except Exception, e:
88 raise RepoUnknownError('Unknown Repo Error: %s' % e)
89 if free_space_gb < min_free:
90 raise RepoDiskFull('Not enough disk space available')
91
92
93def check_write(repo):
94 try:
95 repo_testfile = '.repo_test_file'
96 repo_run_command(repo, 'touch %s' % repo_testfile).stdout.strip()
97 repo_run_command(repo, 'rm ' + repo_testfile)
98 except error.CmdError:
99 raise RepoWriteError('Unable to write to ' + repo)
100
101
102def trim_custom_directories(repo, older_than_days=40):
103 older_than_days = global_config.global_config.get_config_value('PACKAGES',
104 'custom_max_age',
105 type=int)
106 cmd = 'find . -type f -atime %s -exec rm -f {} \;' % older_than_days
107 repo_run_command(repo, cmd, ignore_status=True)
108
109
showard9d02fb52008-08-08 18:20:37 +0000110class BasePackageManager(object):
111 _repo_exception = {}
112 REPO_OK = object()
113
mbligh76d19f72008-10-15 16:24:43 +0000114 def __init__(self, pkgmgr_dir, hostname=None, repo_urls=None,
115 upload_paths=None, do_locking=True, run_function=utils.run,
116 run_function_args=[], run_function_dargs={}):
showard9d02fb52008-08-08 18:20:37 +0000117 '''
118 repo_urls: The list of the repository urls which is consulted
119 whilst fetching the package
120 upload_paths: The list of the upload of repositories to which
121 the package is uploaded to
122 pkgmgr_dir : A directory that can be used by the package manager
123 to dump stuff (like checksum files of the repositories
124 etc.).
125 do_locking : Enable locking when the packages are installed.
126
127 run_function is used to execute the commands throughout this file.
128 It defaults to utils.run() but a custom method (if provided) should
129 be of the same schema as utils.run. It should return a CmdResult
130 object and throw a CmdError exception. The reason for using a separate
131 function to run the commands is that the same code can be run to fetch
132 a package on the local machine or on a remote machine (in which case
133 ssh_host's run function is passed in for run_function).
134 '''
135 # In memory dictionary that stores the checksum's of packages
136 self._checksum_dict = {}
137
138 self.pkgmgr_dir = pkgmgr_dir
139 self.do_locking = do_locking
mbligh76d19f72008-10-15 16:24:43 +0000140 self.hostname = hostname
showard9d02fb52008-08-08 18:20:37 +0000141
142 # Process the repository URLs and the upload paths if specified
143 if not repo_urls:
144 self.repo_urls = []
145 else:
mbligh76d19f72008-10-15 16:24:43 +0000146 if hostname:
147 self.repo_urls = repo_urls
148 self.repo_urls = list(self.get_mirror_list())
149 else:
150 self.repo_urls = list(repo_urls)
showard9d02fb52008-08-08 18:20:37 +0000151 if not upload_paths:
152 self.upload_paths = []
153 else:
154 self.upload_paths = list(upload_paths)
155
mblighe1836812009-04-17 18:25:14 +0000156
showard9d02fb52008-08-08 18:20:37 +0000157 # Create an internal function that is a simple wrapper of
158 # run_function and takes in the args and dargs as arguments
159 def _run_command(command, _run_command_args=run_function_args,
160 _run_command_dargs={}):
161 '''
162 Special internal function that takes in a command as
163 argument and passes it on to run_function (if specified).
164 The _run_command_dargs are merged into run_function_dargs
165 with the former having more precedence than the latter.
166 '''
167 new_dargs = dict(run_function_dargs)
168 new_dargs.update(_run_command_dargs)
169
170 return run_function(command, *_run_command_args,
171 **new_dargs)
172
173 self._run_command = _run_command
174
mblighe1836812009-04-17 18:25:14 +0000175 def repo_check(self, repo):
176 '''
177 Check to make sure the repo is in a sane state:
178 ensure we have at least XX amount of free space
179 Make sure we can write to the repo
180 '''
181 try:
182 check_diskspace(repo)
183 check_write(repo)
184 except (RepoWriteError, RepoUnknownError, RepoDiskFull), e:
185 raise RepoError("ERROR: Repo %s: %s" % (repo, e))
186
187
188 def upkeep(self, custom_repos=None):
189 '''
190 Clean up custom upload/download areas
191 '''
192 if not custom_repos:
193 custom_repos = global_config.global_config.get_config_value('PACKAGES',
194 'custom_upload_location').split(',')
195 custom_download = global_config.global_config.get_config_value(
196 'PACKAGES', 'custom_download_location')
197 custom_repos += [custom_download]
198
199 results = subcommand.parallel_simple(trim_custom_directories,
200 custom_repos, log=False, )
201
showard9d02fb52008-08-08 18:20:37 +0000202
203 def install_pkg(self, name, pkg_type, fetch_dir, install_dir,
204 preserve_install_dir=False, repo_url=None):
205 '''
206 Remove install_dir if it already exists and then recreate it unless
207 preserve_install_dir is specified as True.
208 Fetch the package into the pkg_dir. Untar the package into install_dir
209 The assumption is that packages are of the form :
210 <pkg_type>.<pkg_name>.tar.bz2
211 name : name of the package
212 type : type of the package
213 fetch_dir : The directory into which the package tarball will be
214 fetched to.
215 install_dir : the directory where the package files will be untarred to
216 repo_url : the url of the repository to fetch the package from.
217 '''
218
219 # do_locking flag is on by default unless you disable it (typically
220 # in the cases where packages are directly installed from the server
221 # onto the client in which case fcntl stuff wont work as the code
222 # will run on the server in that case..
223 if self.do_locking:
224 lockfile_name = '.%s-%s-lock' % (name, pkg_type)
225 lockfile = open(os.path.join(self.pkgmgr_dir, lockfile_name), 'w')
226
227 try:
228 if self.do_locking:
229 fcntl.flock(lockfile, fcntl.LOCK_EX)
230
231 self._run_command('mkdir -p %s' % fetch_dir)
232
233 pkg_name = self.get_tarball_name(name, pkg_type)
234 fetch_path = os.path.join(fetch_dir, pkg_name)
235 try:
236 # Fetch the package into fetch_dir
mbligh76d19f72008-10-15 16:24:43 +0000237 self.fetch_pkg(pkg_name, fetch_path, use_checksum=True)
showard9d02fb52008-08-08 18:20:37 +0000238
239 # check to see if the install_dir exists and if it does
240 # then check to see if the .checksum file is the latest
241 install_dir_exists = False
242 try:
243 self._run_command("ls %s" % install_dir)
244 install_dir_exists = True
245 except (error.CmdError, error.AutoservRunError):
246 pass
247
248 if (install_dir_exists and
249 not self.untar_required(fetch_path, install_dir)):
250 return
251
252 # untar the package into install_dir and
253 # update the checksum in that directory
254 if not preserve_install_dir:
255 # Make sure we clean up the install_dir
256 self._run_command('rm -rf %s' % install_dir)
257 self._run_command('mkdir -p %s' % install_dir)
258
259 self.untar_pkg(fetch_path, install_dir)
260
261 except PackageFetchError, why:
262 raise PackageInstallError('Installation of %s(type:%s) failed'
263 ' : %s' % (name, pkg_type, why))
264 finally:
265 if self.do_locking:
266 fcntl.flock(lockfile, fcntl.LOCK_UN)
267 lockfile.close()
268
269
mbligh76d19f72008-10-15 16:24:43 +0000270 def fetch_pkg(self, pkg_name, dest_path, repo_url=None, use_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000271 '''
272 Fetch the package into dest_dir from repo_url. By default repo_url
273 is None and the package is looked in all the repostories specified.
274 Otherwise it fetches it from the specific repo_url.
275 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
276 dep-gcc.tar.bz2, kernel.1-1.rpm)
277 repo_url : the URL of the repository where the package is located.
278 dest_path : complete path of where the package will be fetched to.
279 use_checksum : This is set to False to fetch the packages.checksum file
280 so that the checksum comparison is bypassed for the
281 checksum file itself. This is used internally by the
282 packaging system. It should be ignored by externals
283 callers of this method who use it fetch custom packages.
284 '''
285
286 try:
287 self._run_command("ls %s" % os.path.dirname(dest_path))
288 except (error.CmdError, error.AutoservRunError):
289 raise PackageFetchError("Please provide a valid "
290 "destination: %s " % dest_path)
291
292 # See if the package was already fetched earlier, if so
293 # the checksums need to be compared and the package is now
294 # fetched only if they differ.
295 pkg_exists = False
296 try:
297 self._run_command("ls %s" % dest_path)
298 pkg_exists = True
299 except (error.CmdError, error.AutoservRunError):
300 pass
301
302 # if a repository location is explicitly provided, fetch the package
303 # from there and return
304 if repo_url:
305 repo_url_list = [repo_url]
306 elif len(self.repo_urls) > 0:
307 repo_url_list = self.repo_urls
308 else:
309 raise PackageFetchError("There are no repository urls specified")
310
311 error_msgs = {}
312 for location in repo_url_list:
313 try:
314 # Fetch the checksum if it not there
315 if not use_checksum:
316 self.fetch_pkg_file(pkg_name, dest_path, location)
317
318 # Fetch the package if a) the pkg does not exist or
319 # b) if the checksum differs for the existing package
320 elif (not pkg_exists or
321 not self.compare_checksum(dest_path, location)):
322 self.fetch_pkg_file(pkg_name, dest_path, location)
323 # Update the checksum of the package in the packages'
324 # checksum file
325 self.update_checksum(dest_path)
326 return
mbligh7a603672009-02-07 01:52:08 +0000327 except (PackageFetchError, error.AutoservRunError):
showard9d02fb52008-08-08 18:20:37 +0000328 # The package could not be found in this repo, continue looking
showarda290e982009-03-27 20:57:30 +0000329 logging.error('%s could not be fetched from %s', pkg_name,
330 location)
showard9d02fb52008-08-08 18:20:37 +0000331
332 # if we got here then that means the package is not found
333 # in any of the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000334 raise PackageFetchError("%s could not be fetched from any of"
335 " the repos %s : %s " % (pkg_name,
336 repo_url_list,
showard9d02fb52008-08-08 18:20:37 +0000337 error_msgs))
338
339
mbligh76d19f72008-10-15 16:24:43 +0000340 def fetch_pkg_file(self, filename, dest_path, source_url):
showard9d02fb52008-08-08 18:20:37 +0000341 """
342 Fetch the file from source_url into dest_path. The package repository
343 url is parsed and the appropriate retrieval method is determined.
344
345 """
346 if source_url.startswith('http://'):
mbligh76d19f72008-10-15 16:24:43 +0000347 self.fetch_file_http(filename, dest_path, source_url)
showard9d02fb52008-08-08 18:20:37 +0000348 else:
mbligh76d19f72008-10-15 16:24:43 +0000349 raise PackageFetchError("Invalid location %s" % source_url)
showard9d02fb52008-08-08 18:20:37 +0000350
351
mbligh76d19f72008-10-15 16:24:43 +0000352 def fetch_file_http(self, filename, dest_path, source_url):
showard9d02fb52008-08-08 18:20:37 +0000353 """
354 Fetch the package using http protocol. Raises a PackageFetchError.
355 """
showarda290e982009-03-27 20:57:30 +0000356 logging.info("Fetching %s from %s to %s", filename, source_url,
357 dest_path)
showard9d02fb52008-08-08 18:20:37 +0000358 # check to see if the source_url is reachable or not
359 self.run_http_test(source_url, os.path.dirname(dest_path))
360
mbligh76d19f72008-10-15 16:24:43 +0000361 pkg_path = os.path.join(source_url, filename)
showard9d02fb52008-08-08 18:20:37 +0000362 try:
mblighdc1e7aa2008-10-10 21:12:15 +0000363 self._run_command('wget -nv %s -O %s' % (pkg_path, dest_path))
showarda290e982009-03-27 20:57:30 +0000364 logging.debug("Successfully fetched %s from %s", filename,
365 source_url)
mbligh7a603672009-02-07 01:52:08 +0000366 except error.CmdError:
367 raise PackageFetchError("%s not found in %s" % (filename,
368 source_url))
showard9d02fb52008-08-08 18:20:37 +0000369
370
371 def run_http_test(self, source_url, dest_dir):
372 '''
373 Run a simple 30 sec wget on source_url
374 just to see if it can be reachable or not. This avoids the need
375 for waiting for a 10min timeout.
376 '''
377 dest_file_path = os.path.join(dest_dir, 'http_test_file')
378
379 BPM = BasePackageManager
380 error_msg = "HTTP test failed. Failed to contact"
381 # We should never get here unless the source_url starts with http://
382 assert(source_url.startswith('http://'))
383
384 # Get the http server name from the URL
385 server_name = urlparse.urlparse(source_url)[1]
mbligh76d19f72008-10-15 16:24:43 +0000386 http_cmd = 'wget -nv %s -O %s' % (server_name, dest_file_path)
mblighabe330e2008-12-09 23:37:52 +0000387
388 # Following repo_exception optimization is disabled for now.
389 # Checksum files are optional. The attempted download of a
390 # missing checksum file erroneously causes the repos to be marked
391 # dead, causing download of its custom kernels to fail.
392 # It also stays dead until Autotest is restarted.
393 if server_name in BPM._repo_exception and False: # <--- TEMP
showard9d02fb52008-08-08 18:20:37 +0000394 if BPM._repo_exception[server_name] == BPM.REPO_OK:
395 # This repository is fine. Simply return
396 return
397 else:
398 raise PackageFetchError("%s - %s : %s "
399 % (error_msg, server_name,
400 BPM._repo_exception[server_name]))
401 try:
402 try:
403 self._run_command(http_cmd,
404 _run_command_dargs={'timeout':30})
405 BPM._repo_exception[server_name] = BPM.REPO_OK
406 finally:
407 self._run_command('rm -f %s' % dest_file_path)
mbligh76d19f72008-10-15 16:24:43 +0000408 except Exception, e:
showard9d02fb52008-08-08 18:20:37 +0000409 BPM._repo_exception[server_name] = e
mbligh76d19f72008-10-15 16:24:43 +0000410 raise PackageFetchError("%s - %s: %s " % (error_msg, server_name,
411 e))
showard9d02fb52008-08-08 18:20:37 +0000412
413
mblighe1836812009-04-17 18:25:14 +0000414 def upload_pkg(self, pkg_path, upload_path=None, update_checksum=False):
415 if upload_path:
416 upload_path_list = [upload_path]
417 self.upkeep(upload_path_list)
418 elif len(self.upload_paths) > 0:
419 self.upkeep()
420 upload_path_list = self.upload_paths
421 else:
422 raise PackageUploadError("Invalid Upload Path specified")
423
424 commands = []
425 for path in upload_path_list:
426 commands.append(subcommand.subcommand(self.upload_pkg_parallel,
427 (pkg_path, path,
428 update_checksum)))
429
430 results = subcommand.parallel(commands, 300, return_results=True)
431 for result in results:
432 if result:
433 print str(result)
434
435
showard9d02fb52008-08-08 18:20:37 +0000436 # TODO(aganti): Fix the bug with the current checksum logic where
437 # packages' checksums that are not present consistently in all the
438 # repositories are not handled properly. This is a corner case though
439 # but the ideal solution is to make the checksum file repository specific
440 # and then maintain it.
mblighe1836812009-04-17 18:25:14 +0000441 def upload_pkg_parallel(self, pkg_path, upload_path, update_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000442 '''
443 Uploads to a specified upload_path or to all the repos.
444 Also uploads the checksum file to all the repos.
445 pkg_path : The complete path to the package file
446 upload_path : the absolute path where the files are copied to.
447 if set to 'None' assumes 'all' repos
448 update_checksum : If set to False, the checksum file is not
449 going to be updated which happens by default.
450 This is necessary for custom
451 packages (like custom kernels and custom tests)
452 that get uploaded which do not need to be part of
453 the checksum file and bloat it.
454 '''
mblighe1836812009-04-17 18:25:14 +0000455 self.repo_check(upload_path)
showard9d02fb52008-08-08 18:20:37 +0000456 if update_checksum:
457 # get the packages' checksum file and update it with the current
458 # package's checksum
459 checksum_path = self._get_checksum_file_path()
460 self.update_checksum(pkg_path)
461
showard9d02fb52008-08-08 18:20:37 +0000462 # upload the package
mblighe1836812009-04-17 18:25:14 +0000463 if os.path.isdir(pkg_path):
464 self.upload_pkg_dir(pkg_path, upload_path)
465 else:
466 self.upload_pkg_file(pkg_path, upload_path)
showard9d02fb52008-08-08 18:20:37 +0000467 if update_checksum:
mblighe1836812009-04-17 18:25:14 +0000468 self.upload_pkg_file(checksum_path, upload_path)
showard9d02fb52008-08-08 18:20:37 +0000469
470
471 def upload_pkg_file(self, file_path, upload_path):
472 '''
473 Upload a single file. Depending on the upload path, the appropriate
474 method for that protocol is called. Currently this simply copies the
475 file to the target directory (but can be extended for other protocols)
476 This assumes that the web server is running on the same machine where
477 the method is being called from. The upload_path's files are
478 basically served by that web server.
479 '''
480 try:
mbligh93a9e292008-10-10 21:09:53 +0000481 if upload_path.startswith('ssh://'):
482 # parse ssh://user@host/usr/local/autotest/packages
mblighe1836812009-04-17 18:25:14 +0000483 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000484 try:
485 utils.run('scp %s %s:%s' % (file_path, hostline,
486 remote_path))
487 r_path = os.path.join(remote_path,
488 os.path.basename(file_path))
489 utils.run("ssh %s 'chmod 644 %s'" % (hostline, r_path))
490 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000491 logging.error("Error uploading to repository %s",
492 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000493 else:
494 shutil.copy(file_path, upload_path)
495 os.chmod(os.path.join(upload_path,
496 os.path.basename(file_path)), 0644)
showard9d02fb52008-08-08 18:20:37 +0000497 except (IOError, os.error), why:
showarda290e982009-03-27 20:57:30 +0000498 logging.error("Upload of %s to %s failed: %s", file_path,
499 upload_path, why)
showard9d02fb52008-08-08 18:20:37 +0000500
501
mbligh9fc77972008-10-02 20:32:09 +0000502 def upload_pkg_dir(self, dir_path, upload_path):
503 '''
504 Upload a full directory. Depending on the upload path, the appropriate
505 method for that protocol is called. Currently this copies the whole
506 tmp package directory to the target directory.
507 This assumes that the web server is running on the same machine where
508 the method is being called from. The upload_path's files are
509 basically served by that web server.
510 '''
mbligh93a9e292008-10-10 21:09:53 +0000511 local_path = os.path.join(dir_path, "*")
mbligh9fc77972008-10-02 20:32:09 +0000512 try:
mbligh93a9e292008-10-10 21:09:53 +0000513 if upload_path.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000514 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000515 try:
516 utils.run('scp %s %s:%s' % (local_path, hostline,
517 remote_path))
518 ssh_path = os.path.join(remote_path, "*")
519 utils.run("ssh %s 'chmod 644 %s'" % (hostline, ssh_path))
520 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000521 logging.error("Error uploading to repository: %s",
522 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000523 else:
524 utils.run("cp %s %s " % (local_path, upload_path))
525 up_path = os.path.join(upload_path, "*")
526 utils.run("chmod 644 %s" % up_path)
mbligh9fc77972008-10-02 20:32:09 +0000527 except (IOError, os.error), why:
528 raise PackageUploadError("Upload of %s to %s failed: %s"
529 % (dir_path, upload_path, why))
530
531
showard9d02fb52008-08-08 18:20:37 +0000532 def remove_pkg(self, pkg_name, remove_path=None, remove_checksum=False):
533 '''
534 Remove the package from the specified remove_path
535 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
536 dep-gcc.tar.bz2)
537 remove_path : the location to remove the package from.
538
539 '''
540 if remove_path:
541 remove_path_list = [remove_path]
542 elif len(self.upload_paths) > 0:
543 remove_path_list = self.upload_paths
544 else:
545 raise PackageRemoveError("Invalid path to remove the pkg from")
546
547 checksum_path = self._get_checksum_file_path()
548
549 if remove_checksum:
550 self.remove_checksum(pkg_name)
551
552 # remove the package and upload the checksum file to the repos
553 for path in remove_path_list:
554 self.remove_pkg_file(pkg_name, path)
555 self.upload_pkg_file(checksum_path, path)
556
557
mbligh76d19f72008-10-15 16:24:43 +0000558 def remove_pkg_file(self, filename, pkg_dir):
showard9d02fb52008-08-08 18:20:37 +0000559 '''
mbligh76d19f72008-10-15 16:24:43 +0000560 Remove the file named filename from pkg_dir
showard9d02fb52008-08-08 18:20:37 +0000561 '''
562 try:
563 # Remove the file
mbligh93a9e292008-10-10 21:09:53 +0000564 if pkg_dir.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000565 hostline, remote_path = parse_ssh_path(pkg_dir)
mbligh76d19f72008-10-15 16:24:43 +0000566 path = os.path.join(remote_path, filename)
mbligh93a9e292008-10-10 21:09:53 +0000567 utils.run("ssh %s 'rm -rf %s/%s'" % (hostline, remote_path,
568 path))
569 else:
mbligh76d19f72008-10-15 16:24:43 +0000570 os.remove(os.path.join(pkg_dir, filename))
showard9d02fb52008-08-08 18:20:37 +0000571 except (IOError, os.error), why:
572 raise PackageRemoveError("Could not remove %s from %s: %s "
mbligh76d19f72008-10-15 16:24:43 +0000573 % (filename, pkg_dir, why))
showard9d02fb52008-08-08 18:20:37 +0000574
575
mbligh76d19f72008-10-15 16:24:43 +0000576 def get_mirror_list(self):
mbligh1e3b0992008-10-14 16:29:54 +0000577 '''
mbligh76d19f72008-10-15 16:24:43 +0000578 Stub function for site specific mirrors.
mbligh1e3b0992008-10-14 16:29:54 +0000579
580 Returns:
581 Priority ordered list
582 '''
583 return self.repo_urls
584
585
showard9d02fb52008-08-08 18:20:37 +0000586 def _get_checksum_file_path(self):
587 '''
588 Return the complete path of the checksum file (assumed to be stored
589 in self.pkgmgr_dir
590 '''
591 return os.path.join(self.pkgmgr_dir, CHECKSUM_FILE)
592
593
594 def _get_checksum_dict(self):
595 '''
596 Fetch the checksum file if not already fetched. If the checksum file
597 cannot be fetched from the repos then a new file is created with
598 the current package's (specified in pkg_path) checksum value in it.
599 Populate the local checksum dictionary with the values read from
600 the checksum file.
601 The checksum file is assumed to be present in self.pkgmgr_dir
602 '''
603 checksum_path = self._get_checksum_file_path()
604 if not self._checksum_dict:
605 # Fetch the checksum file
606 try:
607 try:
608 self._run_command("ls %s" % checksum_path)
609 except (error.CmdError, error.AutoservRunError):
610 # The packages checksum file does not exist locally.
611 # See if it is present in the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000612 self.fetch_pkg(CHECKSUM_FILE, checksum_path)
showard9d02fb52008-08-08 18:20:37 +0000613 except PackageFetchError, e:
614 # This should not happen whilst fetching a package..if a
615 # package is present in the repository, the corresponding
616 # checksum file should also be automatically present. This
617 # case happens only when a package
618 # is being uploaded and if it is the first package to be
619 # uploaded to the repos (hence no checksum file created yet)
620 # Return an empty dictionary in that case
621 return {}
622
623 # Read the checksum file into memory
624 checksum_file_contents = self._run_command('cat '
625 + checksum_path).stdout
626
627 # Return {} if we have an empty checksum file present
628 if not checksum_file_contents.strip():
629 return {}
630
631 # Parse the checksum file contents into self._checksum_dict
632 for line in checksum_file_contents.splitlines():
633 checksum, package_name = line.split(None, 1)
634 self._checksum_dict[package_name] = checksum
635
636 return self._checksum_dict
637
638
639 def _save_checksum_dict(self, checksum_dict):
640 '''
641 Save the checksum dictionary onto the checksum file. Update the
642 local _checksum_dict variable with this new set of values.
643 checksum_dict : New checksum dictionary
644 checksum_dir : The directory in which to store the checksum file to.
645 '''
646 checksum_path = self._get_checksum_file_path()
647 self._checksum_dict = checksum_dict.copy()
648 checksum_contents = '\n'.join(checksum + ' ' + pkg_name
649 for pkg_name,checksum in
650 checksum_dict.iteritems())
651 # Write the checksum file back to disk
652 self._run_command('echo "%s" > %s' % (checksum_contents,
653 checksum_path))
654
655
656 def compute_checksum(self, pkg_path):
657 '''
658 Compute the MD5 checksum for the package file and return it.
659 pkg_path : The complete path for the package file
660 '''
661 md5sum_output = self._run_command("md5sum %s " % pkg_path).stdout
662 return md5sum_output.split()[0]
663
664
665 def update_checksum(self, pkg_path):
666 '''
667 Update the checksum of the package in the packages' checksum
668 file. This method is called whenever a package is fetched just
669 to be sure that the checksums in the local file are the latest.
670 pkg_path : The complete path to the package file.
671 '''
672 # Compute the new checksum
673 new_checksum = self.compute_checksum(pkg_path)
674 checksum_dict = self._get_checksum_dict()
675 checksum_dict[os.path.basename(pkg_path)] = new_checksum
676 self._save_checksum_dict(checksum_dict)
677
678
679 def remove_checksum(self, pkg_name):
680 '''
681 Remove the checksum of the package from the packages checksum file.
682 This method is called whenever a package is removed from the
683 repositories in order clean its corresponding checksum.
684 pkg_name : The name of the package to be removed
685 '''
686 checksum_dict = self._get_checksum_dict()
687 if pkg_name in checksum_dict:
688 del checksum_dict[pkg_name]
689 self._save_checksum_dict(checksum_dict)
690
691
692 def compare_checksum(self, pkg_path, repo_url):
693 '''
694 Calculate the checksum of the file specified in pkg_path and
695 compare it with the checksum in the checksum file
696 Return True if both match else return False.
697 pkg_path : The full path to the package file for which the
698 checksum is being compared
699 repo_url : The URL to fetch the checksum from
700 '''
701 checksum_dict = self._get_checksum_dict()
702 package_name = os.path.basename(pkg_path)
703 if not checksum_dict or package_name not in checksum_dict:
704 return False
705
706 repository_checksum = checksum_dict[package_name]
707 local_checksum = self.compute_checksum(pkg_path)
708 return (local_checksum == repository_checksum)
709
710
mblighdbfc4e32008-08-22 18:08:07 +0000711 def tar_package(self, pkg_name, src_dir, dest_dir, exclude_string=None):
showard9d02fb52008-08-08 18:20:37 +0000712 '''
713 Create a tar.bz2 file with the name 'pkg_name' say test-blah.tar.bz2.
mbligh9fc77972008-10-02 20:32:09 +0000714 Excludes the directories specified in exclude_string while tarring
showard9d02fb52008-08-08 18:20:37 +0000715 the source. Returns the tarball path.
716 '''
showard9d02fb52008-08-08 18:20:37 +0000717 tarball_path = os.path.join(dest_dir, pkg_name)
mbligh9fc77972008-10-02 20:32:09 +0000718 cmd = "tar -cvjf %s -C %s %s " % (tarball_path, src_dir, exclude_string)
showard9d02fb52008-08-08 18:20:37 +0000719
mbligh9fc77972008-10-02 20:32:09 +0000720 utils.system(cmd)
showard9d02fb52008-08-08 18:20:37 +0000721 return tarball_path
722
723
724 def untar_required(self, tarball_path, dest_dir):
725 '''
726 Compare the checksum of the tarball_path with the .checksum file
727 in the dest_dir and return False if it matches. The untar
728 of the package happens only if the checksums do not match.
729 '''
730 checksum_path = os.path.join(dest_dir, '.checksum')
731 try:
732 existing_checksum = self._run_command('cat ' + checksum_path).stdout
733 except (error.CmdError, error.AutoservRunError):
734 # If the .checksum file is not present (generally, this should
735 # not be the case) then return True so that the untar happens
736 return True
737
738 new_checksum = self.compute_checksum(tarball_path)
739 return (new_checksum.strip() != existing_checksum.strip())
740
741
742 def untar_pkg(self, tarball_path, dest_dir):
743 '''
744 Untar the package present in the tarball_path and put a
745 ".checksum" file in the dest_dir containing the checksum
746 of the tarball. This method
747 assumes that the package to be untarred is of the form
748 <name>.tar.bz2
749 '''
mbligh96ad8512008-10-03 03:45:26 +0000750 self._run_command('tar xjf %s -C %s' % (tarball_path, dest_dir))
showard9d02fb52008-08-08 18:20:37 +0000751 # Put the .checksum file in the install_dir to note
752 # where the package came from
753 pkg_checksum = self.compute_checksum(tarball_path)
754 pkg_checksum_path = os.path.join(dest_dir,
755 '.checksum')
756 self._run_command('echo "%s" > %s '
757 % (pkg_checksum, pkg_checksum_path))
758
759
760 def get_tarball_name(self, name, pkg_type):
761 return "%s-%s.tar.bz2" % (pkg_type, name)
762
763
764 def is_url(self, url):
765 """Return true if path looks like a URL"""
766 return url.startswith('http://')
767
768
769 def get_package_name(self, url, pkg_type):
770 '''
771 Extract the group and test name for the url. This method is currently
772 used only for tests.
773 '''
774 if pkg_type == 'test':
mblighecbaec32008-10-25 13:37:42 +0000775 regex = '[^:]+://(.*)/([^/]*)$'
showard9d02fb52008-08-08 18:20:37 +0000776 return self._get_package_name(url, regex)
777 else:
778 return ('', url)
779
780
781 def _get_package_name(self, url, regex):
782 if not self.is_url(url):
783 if url.endswith('.tar.bz2'):
784 testname = url.replace('.tar.bz2', '')
785 testname = re.sub(r'(\d*)\.', '', testname)
786 return (testname, testname)
787 else:
788 return ('', url)
789
790 match = re.match(regex, url)
791 if not match:
792 return ('', url)
793 group, filename = match.groups()
794 # Generate the group prefix.
795 group = re.sub(r'\W', '_', group)
796 # Drop the extension to get the raw test name.
797 testname = re.sub(r'\.tar\.bz2', '', filename)
798 # Drop any random numbers at the end of the test name if any
799 testname = re.sub(r'\.(\d*)', '', testname)
800 return (group, testname)
801
802
mbligha7007722009-01-13 00:37:11 +0000803SitePackageManager = utils.import_site_class(
804 __file__, "autotest_lib.client.common_lib.site_packages",
805 "SitePackageManager", BasePackageManager)
showard9d02fb52008-08-08 18:20:37 +0000806
807class PackageManager(SitePackageManager):
808 pass