blob: ed99c78f5b904aa95176f9adccc3caa72b6f6684 [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)
168
169 return run_function(command, *_run_command_args,
170 **new_dargs)
171
172 self._run_command = _run_command
173
mblighe1836812009-04-17 18:25:14 +0000174 def repo_check(self, repo):
175 '''
176 Check to make sure the repo is in a sane state:
177 ensure we have at least XX amount of free space
178 Make sure we can write to the repo
179 '''
180 try:
181 check_diskspace(repo)
182 check_write(repo)
183 except (RepoWriteError, RepoUnknownError, RepoDiskFull), e:
184 raise RepoError("ERROR: Repo %s: %s" % (repo, e))
185
186
187 def upkeep(self, custom_repos=None):
188 '''
189 Clean up custom upload/download areas
190 '''
mbligh1a519b92009-04-17 18:26:19 +0000191 from autotest_lib.server import subcommand
mblighe1836812009-04-17 18:25:14 +0000192 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):
mbligh1a519b92009-04-17 18:26:19 +0000415 from autotest_lib.server import subcommand
mblighe1836812009-04-17 18:25:14 +0000416 if upload_path:
417 upload_path_list = [upload_path]
418 self.upkeep(upload_path_list)
419 elif len(self.upload_paths) > 0:
420 self.upkeep()
421 upload_path_list = self.upload_paths
422 else:
423 raise PackageUploadError("Invalid Upload Path specified")
424
mblighcf57ae22009-04-17 20:13:01 +0000425 if update_checksum:
426 # get the packages' checksum file and update it with the current
427 # package's checksum
428 self.update_checksum(pkg_path)
429
mblighe1836812009-04-17 18:25:14 +0000430 commands = []
431 for path in upload_path_list:
432 commands.append(subcommand.subcommand(self.upload_pkg_parallel,
433 (pkg_path, path,
434 update_checksum)))
435
436 results = subcommand.parallel(commands, 300, return_results=True)
437 for result in results:
438 if result:
439 print str(result)
440
441
showard9d02fb52008-08-08 18:20:37 +0000442 # TODO(aganti): Fix the bug with the current checksum logic where
443 # packages' checksums that are not present consistently in all the
444 # repositories are not handled properly. This is a corner case though
445 # but the ideal solution is to make the checksum file repository specific
446 # and then maintain it.
mblighe1836812009-04-17 18:25:14 +0000447 def upload_pkg_parallel(self, pkg_path, upload_path, update_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000448 '''
449 Uploads to a specified upload_path or to all the repos.
450 Also uploads the checksum file to all the repos.
451 pkg_path : The complete path to the package file
452 upload_path : the absolute path where the files are copied to.
453 if set to 'None' assumes 'all' repos
454 update_checksum : If set to False, the checksum file is not
455 going to be updated which happens by default.
456 This is necessary for custom
457 packages (like custom kernels and custom tests)
458 that get uploaded which do not need to be part of
459 the checksum file and bloat it.
460 '''
mblighe1836812009-04-17 18:25:14 +0000461 self.repo_check(upload_path)
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:
mblighcf57ae22009-04-17 20:13:01 +0000468 self.upload_pkg_file(self._get_checksum_file_path(),
469 upload_path)
showard9d02fb52008-08-08 18:20:37 +0000470
471
472 def upload_pkg_file(self, file_path, upload_path):
473 '''
474 Upload a single file. Depending on the upload path, the appropriate
475 method for that protocol is called. Currently this simply copies the
476 file to the target directory (but can be extended for other protocols)
477 This assumes that the web server is running on the same machine where
478 the method is being called from. The upload_path's files are
479 basically served by that web server.
480 '''
481 try:
mbligh93a9e292008-10-10 21:09:53 +0000482 if upload_path.startswith('ssh://'):
483 # parse ssh://user@host/usr/local/autotest/packages
mblighe1836812009-04-17 18:25:14 +0000484 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000485 try:
486 utils.run('scp %s %s:%s' % (file_path, hostline,
487 remote_path))
488 r_path = os.path.join(remote_path,
489 os.path.basename(file_path))
490 utils.run("ssh %s 'chmod 644 %s'" % (hostline, r_path))
491 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000492 logging.error("Error uploading to repository %s",
493 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000494 else:
495 shutil.copy(file_path, upload_path)
496 os.chmod(os.path.join(upload_path,
497 os.path.basename(file_path)), 0644)
showard9d02fb52008-08-08 18:20:37 +0000498 except (IOError, os.error), why:
showarda290e982009-03-27 20:57:30 +0000499 logging.error("Upload of %s to %s failed: %s", file_path,
500 upload_path, why)
showard9d02fb52008-08-08 18:20:37 +0000501
502
mbligh9fc77972008-10-02 20:32:09 +0000503 def upload_pkg_dir(self, dir_path, upload_path):
504 '''
505 Upload a full directory. Depending on the upload path, the appropriate
506 method for that protocol is called. Currently this copies the whole
507 tmp package directory to the target directory.
508 This assumes that the web server is running on the same machine where
509 the method is being called from. The upload_path's files are
510 basically served by that web server.
511 '''
mbligh93a9e292008-10-10 21:09:53 +0000512 local_path = os.path.join(dir_path, "*")
mbligh9fc77972008-10-02 20:32:09 +0000513 try:
mbligh93a9e292008-10-10 21:09:53 +0000514 if upload_path.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000515 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000516 try:
517 utils.run('scp %s %s:%s' % (local_path, hostline,
518 remote_path))
519 ssh_path = os.path.join(remote_path, "*")
520 utils.run("ssh %s 'chmod 644 %s'" % (hostline, ssh_path))
521 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000522 logging.error("Error uploading to repository: %s",
523 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000524 else:
525 utils.run("cp %s %s " % (local_path, upload_path))
526 up_path = os.path.join(upload_path, "*")
527 utils.run("chmod 644 %s" % up_path)
mbligh9fc77972008-10-02 20:32:09 +0000528 except (IOError, os.error), why:
529 raise PackageUploadError("Upload of %s to %s failed: %s"
530 % (dir_path, upload_path, why))
531
532
showard9d02fb52008-08-08 18:20:37 +0000533 def remove_pkg(self, pkg_name, remove_path=None, remove_checksum=False):
534 '''
535 Remove the package from the specified remove_path
536 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
537 dep-gcc.tar.bz2)
538 remove_path : the location to remove the package from.
539
540 '''
541 if remove_path:
542 remove_path_list = [remove_path]
543 elif len(self.upload_paths) > 0:
544 remove_path_list = self.upload_paths
545 else:
546 raise PackageRemoveError("Invalid path to remove the pkg from")
547
548 checksum_path = self._get_checksum_file_path()
549
550 if remove_checksum:
551 self.remove_checksum(pkg_name)
552
553 # remove the package and upload the checksum file to the repos
554 for path in remove_path_list:
555 self.remove_pkg_file(pkg_name, path)
556 self.upload_pkg_file(checksum_path, path)
557
558
mbligh76d19f72008-10-15 16:24:43 +0000559 def remove_pkg_file(self, filename, pkg_dir):
showard9d02fb52008-08-08 18:20:37 +0000560 '''
mbligh76d19f72008-10-15 16:24:43 +0000561 Remove the file named filename from pkg_dir
showard9d02fb52008-08-08 18:20:37 +0000562 '''
563 try:
564 # Remove the file
mbligh93a9e292008-10-10 21:09:53 +0000565 if pkg_dir.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000566 hostline, remote_path = parse_ssh_path(pkg_dir)
mbligh76d19f72008-10-15 16:24:43 +0000567 path = os.path.join(remote_path, filename)
mbligh93a9e292008-10-10 21:09:53 +0000568 utils.run("ssh %s 'rm -rf %s/%s'" % (hostline, remote_path,
569 path))
570 else:
mbligh76d19f72008-10-15 16:24:43 +0000571 os.remove(os.path.join(pkg_dir, filename))
showard9d02fb52008-08-08 18:20:37 +0000572 except (IOError, os.error), why:
573 raise PackageRemoveError("Could not remove %s from %s: %s "
mbligh76d19f72008-10-15 16:24:43 +0000574 % (filename, pkg_dir, why))
showard9d02fb52008-08-08 18:20:37 +0000575
576
mbligh76d19f72008-10-15 16:24:43 +0000577 def get_mirror_list(self):
mbligh1e3b0992008-10-14 16:29:54 +0000578 '''
mbligh76d19f72008-10-15 16:24:43 +0000579 Stub function for site specific mirrors.
mbligh1e3b0992008-10-14 16:29:54 +0000580
581 Returns:
582 Priority ordered list
583 '''
584 return self.repo_urls
585
586
showard9d02fb52008-08-08 18:20:37 +0000587 def _get_checksum_file_path(self):
588 '''
589 Return the complete path of the checksum file (assumed to be stored
590 in self.pkgmgr_dir
591 '''
592 return os.path.join(self.pkgmgr_dir, CHECKSUM_FILE)
593
594
595 def _get_checksum_dict(self):
596 '''
597 Fetch the checksum file if not already fetched. If the checksum file
598 cannot be fetched from the repos then a new file is created with
599 the current package's (specified in pkg_path) checksum value in it.
600 Populate the local checksum dictionary with the values read from
601 the checksum file.
602 The checksum file is assumed to be present in self.pkgmgr_dir
603 '''
604 checksum_path = self._get_checksum_file_path()
605 if not self._checksum_dict:
606 # Fetch the checksum file
607 try:
608 try:
609 self._run_command("ls %s" % checksum_path)
610 except (error.CmdError, error.AutoservRunError):
611 # The packages checksum file does not exist locally.
612 # See if it is present in the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000613 self.fetch_pkg(CHECKSUM_FILE, checksum_path)
showard9d02fb52008-08-08 18:20:37 +0000614 except PackageFetchError, e:
615 # This should not happen whilst fetching a package..if a
616 # package is present in the repository, the corresponding
617 # checksum file should also be automatically present. This
618 # case happens only when a package
619 # is being uploaded and if it is the first package to be
620 # uploaded to the repos (hence no checksum file created yet)
621 # Return an empty dictionary in that case
622 return {}
623
624 # Read the checksum file into memory
625 checksum_file_contents = self._run_command('cat '
626 + checksum_path).stdout
627
628 # Return {} if we have an empty checksum file present
629 if not checksum_file_contents.strip():
630 return {}
631
632 # Parse the checksum file contents into self._checksum_dict
633 for line in checksum_file_contents.splitlines():
634 checksum, package_name = line.split(None, 1)
635 self._checksum_dict[package_name] = checksum
636
637 return self._checksum_dict
638
639
640 def _save_checksum_dict(self, checksum_dict):
641 '''
642 Save the checksum dictionary onto the checksum file. Update the
643 local _checksum_dict variable with this new set of values.
644 checksum_dict : New checksum dictionary
645 checksum_dir : The directory in which to store the checksum file to.
646 '''
647 checksum_path = self._get_checksum_file_path()
648 self._checksum_dict = checksum_dict.copy()
649 checksum_contents = '\n'.join(checksum + ' ' + pkg_name
650 for pkg_name,checksum in
651 checksum_dict.iteritems())
652 # Write the checksum file back to disk
653 self._run_command('echo "%s" > %s' % (checksum_contents,
654 checksum_path))
655
656
657 def compute_checksum(self, pkg_path):
658 '''
659 Compute the MD5 checksum for the package file and return it.
660 pkg_path : The complete path for the package file
661 '''
662 md5sum_output = self._run_command("md5sum %s " % pkg_path).stdout
663 return md5sum_output.split()[0]
664
665
666 def update_checksum(self, pkg_path):
667 '''
668 Update the checksum of the package in the packages' checksum
669 file. This method is called whenever a package is fetched just
670 to be sure that the checksums in the local file are the latest.
671 pkg_path : The complete path to the package file.
672 '''
673 # Compute the new checksum
674 new_checksum = self.compute_checksum(pkg_path)
675 checksum_dict = self._get_checksum_dict()
676 checksum_dict[os.path.basename(pkg_path)] = new_checksum
677 self._save_checksum_dict(checksum_dict)
678
679
680 def remove_checksum(self, pkg_name):
681 '''
682 Remove the checksum of the package from the packages checksum file.
683 This method is called whenever a package is removed from the
684 repositories in order clean its corresponding checksum.
685 pkg_name : The name of the package to be removed
686 '''
687 checksum_dict = self._get_checksum_dict()
688 if pkg_name in checksum_dict:
689 del checksum_dict[pkg_name]
690 self._save_checksum_dict(checksum_dict)
691
692
693 def compare_checksum(self, pkg_path, repo_url):
694 '''
695 Calculate the checksum of the file specified in pkg_path and
696 compare it with the checksum in the checksum file
697 Return True if both match else return False.
698 pkg_path : The full path to the package file for which the
699 checksum is being compared
700 repo_url : The URL to fetch the checksum from
701 '''
702 checksum_dict = self._get_checksum_dict()
703 package_name = os.path.basename(pkg_path)
704 if not checksum_dict or package_name not in checksum_dict:
705 return False
706
707 repository_checksum = checksum_dict[package_name]
708 local_checksum = self.compute_checksum(pkg_path)
709 return (local_checksum == repository_checksum)
710
711
mblighdbfc4e32008-08-22 18:08:07 +0000712 def tar_package(self, pkg_name, src_dir, dest_dir, exclude_string=None):
showard9d02fb52008-08-08 18:20:37 +0000713 '''
714 Create a tar.bz2 file with the name 'pkg_name' say test-blah.tar.bz2.
mbligh9fc77972008-10-02 20:32:09 +0000715 Excludes the directories specified in exclude_string while tarring
showard9d02fb52008-08-08 18:20:37 +0000716 the source. Returns the tarball path.
717 '''
showard9d02fb52008-08-08 18:20:37 +0000718 tarball_path = os.path.join(dest_dir, pkg_name)
mbligh9fc77972008-10-02 20:32:09 +0000719 cmd = "tar -cvjf %s -C %s %s " % (tarball_path, src_dir, exclude_string)
showard9d02fb52008-08-08 18:20:37 +0000720
mbligh9fc77972008-10-02 20:32:09 +0000721 utils.system(cmd)
showard9d02fb52008-08-08 18:20:37 +0000722 return tarball_path
723
724
725 def untar_required(self, tarball_path, dest_dir):
726 '''
727 Compare the checksum of the tarball_path with the .checksum file
728 in the dest_dir and return False if it matches. The untar
729 of the package happens only if the checksums do not match.
730 '''
731 checksum_path = os.path.join(dest_dir, '.checksum')
732 try:
733 existing_checksum = self._run_command('cat ' + checksum_path).stdout
734 except (error.CmdError, error.AutoservRunError):
735 # If the .checksum file is not present (generally, this should
736 # not be the case) then return True so that the untar happens
737 return True
738
739 new_checksum = self.compute_checksum(tarball_path)
740 return (new_checksum.strip() != existing_checksum.strip())
741
742
743 def untar_pkg(self, tarball_path, dest_dir):
744 '''
745 Untar the package present in the tarball_path and put a
746 ".checksum" file in the dest_dir containing the checksum
747 of the tarball. This method
748 assumes that the package to be untarred is of the form
749 <name>.tar.bz2
750 '''
mbligh96ad8512008-10-03 03:45:26 +0000751 self._run_command('tar xjf %s -C %s' % (tarball_path, dest_dir))
showard9d02fb52008-08-08 18:20:37 +0000752 # Put the .checksum file in the install_dir to note
753 # where the package came from
754 pkg_checksum = self.compute_checksum(tarball_path)
755 pkg_checksum_path = os.path.join(dest_dir,
756 '.checksum')
757 self._run_command('echo "%s" > %s '
758 % (pkg_checksum, pkg_checksum_path))
759
760
761 def get_tarball_name(self, name, pkg_type):
762 return "%s-%s.tar.bz2" % (pkg_type, name)
763
764
765 def is_url(self, url):
766 """Return true if path looks like a URL"""
767 return url.startswith('http://')
768
769
770 def get_package_name(self, url, pkg_type):
771 '''
772 Extract the group and test name for the url. This method is currently
773 used only for tests.
774 '''
775 if pkg_type == 'test':
mblighecbaec32008-10-25 13:37:42 +0000776 regex = '[^:]+://(.*)/([^/]*)$'
showard9d02fb52008-08-08 18:20:37 +0000777 return self._get_package_name(url, regex)
778 else:
779 return ('', url)
780
781
782 def _get_package_name(self, url, regex):
783 if not self.is_url(url):
784 if url.endswith('.tar.bz2'):
785 testname = url.replace('.tar.bz2', '')
786 testname = re.sub(r'(\d*)\.', '', testname)
787 return (testname, testname)
788 else:
789 return ('', url)
790
791 match = re.match(regex, url)
792 if not match:
793 return ('', url)
794 group, filename = match.groups()
795 # Generate the group prefix.
796 group = re.sub(r'\W', '_', group)
797 # Drop the extension to get the raw test name.
798 testname = re.sub(r'\.tar\.bz2', '', filename)
799 # Drop any random numbers at the end of the test name if any
800 testname = re.sub(r'\.(\d*)', '', testname)
801 return (group, testname)
802
803
mbligha7007722009-01-13 00:37:11 +0000804SitePackageManager = utils.import_site_class(
805 __file__, "autotest_lib.client.common_lib.site_packages",
806 "SitePackageManager", BasePackageManager)
showard9d02fb52008-08-08 18:20:37 +0000807
808class PackageManager(SitePackageManager):
809 pass