blob: b9c0d8031600fb992d1cb330eb6af674c29b2fee [file] [log] [blame]
showard9d02fb52008-08-08 18:20:37 +00001"""
2This module defines the BasePackageManager Class which provides an
3implementation of the packaging system API providing methods to fetch,
4upload and remove packages. Site specific extensions to any of these methods
5should inherit this class.
6"""
7
8import re, os, sys, traceback, subprocess, shutil, time, traceback, urlparse
showarda290e982009-03-27 20:57:30 +00009import fcntl, logging
mblighe1836812009-04-17 18:25:14 +000010from autotest_lib.client.common_lib import error, utils, global_config
showard9d02fb52008-08-08 18:20:37 +000011
12
13class PackageUploadError(error.AutotestError):
14 'Raised when there is an error uploading the package'
15
16class PackageFetchError(error.AutotestError):
17 'Raised when there is an error fetching the package'
18
19class PackageRemoveError(error.AutotestError):
20 'Raised when there is an error removing the package'
21
22class PackageInstallError(error.AutotestError):
23 'Raised when there is an error installing the package'
24
mblighe1836812009-04-17 18:25:14 +000025class RepoDiskFull(error.AutotestError):
26 'Raised when the destination for packages is full'
27
28class RepoWriteError(error.AutotestError):
29 "Raised when packager cannot write to a repo's desitnation"
30
31class RepoUnknownError(error.AutotestError):
32 "Raised when packager cannot write to a repo's desitnation"
33
34class RepoError(error.AutotestError):
35 "Raised when a repo isn't working in some way"
36
showard9d02fb52008-08-08 18:20:37 +000037# the name of the checksum file that stores the packages' checksums
38CHECKSUM_FILE = "packages.checksum"
39
mblighe1836812009-04-17 18:25:14 +000040
41def parse_ssh_path(repo):
42 '''
43 Parse ssh://xx@xx/path/to/ and return a tuple with host_line and
44 remote path
45 '''
46
47 match = re.search('^ssh://(.*?)(/.*)$', repo)
48 if match:
49 return match.groups()
50 else:
51 raise PackageUploadError("Incorrect SSH path in global_config: %s"
52 % repo)
53
54
55def repo_run_command(repo, cmd, ignore_status=False):
56 """Run a command relative to the repos path"""
57 repo = repo.strip()
58 run_cmd = None
59 if repo.startswith('ssh://'):
60 username = None
61 hostline, remote_path = parse_ssh_path(repo)
62 if '@' in hostline:
63 username, host = hostline.split('@')
64 run_cmd = 'ssh %s@%s "cd %s && %s"' % (username, host,
65 remote_path, cmd)
66 else:
67 run_cmd = 'ssh %s "cd %s && %s"' % (host, remote_path, cmd)
68
69 else:
70 run_cmd = "cd %s && %s" % (repo, cmd)
71
72 if run_cmd:
73 return utils.run(run_cmd, ignore_status=ignore_status)
74
75
76def check_diskspace(repo, min_free=None):
77 if not min_free:
78 min_free = global_config.global_config.get_config_value('PACKAGES',
79 'minimum_free_space',
80 type=int)
81 try:
82 df = repo_run_command(repo, 'df -mP . | tail -1').stdout.split()
83 free_space_gb = int(df[3])/1000.0
84 except Exception, e:
85 raise RepoUnknownError('Unknown Repo Error: %s' % e)
86 if free_space_gb < min_free:
87 raise RepoDiskFull('Not enough disk space available')
88
89
90def check_write(repo):
91 try:
92 repo_testfile = '.repo_test_file'
93 repo_run_command(repo, 'touch %s' % repo_testfile).stdout.strip()
94 repo_run_command(repo, 'rm ' + repo_testfile)
95 except error.CmdError:
96 raise RepoWriteError('Unable to write to ' + repo)
97
98
99def trim_custom_directories(repo, older_than_days=40):
100 older_than_days = global_config.global_config.get_config_value('PACKAGES',
101 'custom_max_age',
102 type=int)
103 cmd = 'find . -type f -atime %s -exec rm -f {} \;' % older_than_days
104 repo_run_command(repo, cmd, ignore_status=True)
105
106
showard9d02fb52008-08-08 18:20:37 +0000107class BasePackageManager(object):
108 _repo_exception = {}
109 REPO_OK = object()
jadmanski19426ea2009-07-28 20:19:40 +0000110 _wget_cmd_pattern = 'wget --connect-timeout=15 -nv %s -O %s'
showard9d02fb52008-08-08 18:20:37 +0000111
mbligh76d19f72008-10-15 16:24:43 +0000112 def __init__(self, pkgmgr_dir, hostname=None, repo_urls=None,
113 upload_paths=None, do_locking=True, run_function=utils.run,
114 run_function_args=[], run_function_dargs={}):
showard9d02fb52008-08-08 18:20:37 +0000115 '''
116 repo_urls: The list of the repository urls which is consulted
117 whilst fetching the package
118 upload_paths: The list of the upload of repositories to which
119 the package is uploaded to
120 pkgmgr_dir : A directory that can be used by the package manager
121 to dump stuff (like checksum files of the repositories
122 etc.).
123 do_locking : Enable locking when the packages are installed.
124
125 run_function is used to execute the commands throughout this file.
126 It defaults to utils.run() but a custom method (if provided) should
127 be of the same schema as utils.run. It should return a CmdResult
128 object and throw a CmdError exception. The reason for using a separate
129 function to run the commands is that the same code can be run to fetch
130 a package on the local machine or on a remote machine (in which case
131 ssh_host's run function is passed in for run_function).
132 '''
133 # In memory dictionary that stores the checksum's of packages
134 self._checksum_dict = {}
135
136 self.pkgmgr_dir = pkgmgr_dir
137 self.do_locking = do_locking
mbligh76d19f72008-10-15 16:24:43 +0000138 self.hostname = hostname
showard9d02fb52008-08-08 18:20:37 +0000139
140 # Process the repository URLs and the upload paths if specified
141 if not repo_urls:
142 self.repo_urls = []
143 else:
mbligh76d19f72008-10-15 16:24:43 +0000144 if hostname:
145 self.repo_urls = repo_urls
146 self.repo_urls = list(self.get_mirror_list())
147 else:
148 self.repo_urls = list(repo_urls)
showard9d02fb52008-08-08 18:20:37 +0000149 if not upload_paths:
150 self.upload_paths = []
151 else:
152 self.upload_paths = list(upload_paths)
153
mblighe1836812009-04-17 18:25:14 +0000154
showard9d02fb52008-08-08 18:20:37 +0000155 # Create an internal function that is a simple wrapper of
156 # run_function and takes in the args and dargs as arguments
157 def _run_command(command, _run_command_args=run_function_args,
158 _run_command_dargs={}):
159 '''
160 Special internal function that takes in a command as
161 argument and passes it on to run_function (if specified).
162 The _run_command_dargs are merged into run_function_dargs
163 with the former having more precedence than the latter.
164 '''
165 new_dargs = dict(run_function_dargs)
166 new_dargs.update(_run_command_dargs)
showard108d73e2009-06-22 18:14:41 +0000167 # avoid polluting logs with extremely verbose packaging output
168 new_dargs.update({'stdout_tee' : None})
showard9d02fb52008-08-08 18:20:37 +0000169
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 '''
mbligh1a519b92009-04-17 18:26:19 +0000192 from autotest_lib.server import subcommand
mblighe1836812009-04-17 18:25:14 +0000193 if not custom_repos:
194 custom_repos = global_config.global_config.get_config_value('PACKAGES',
195 'custom_upload_location').split(',')
196 custom_download = global_config.global_config.get_config_value(
197 'PACKAGES', 'custom_download_location')
198 custom_repos += [custom_download]
199
200 results = subcommand.parallel_simple(trim_custom_directories,
201 custom_repos, log=False, )
202
showard9d02fb52008-08-08 18:20:37 +0000203
204 def install_pkg(self, name, pkg_type, fetch_dir, install_dir,
205 preserve_install_dir=False, repo_url=None):
206 '''
207 Remove install_dir if it already exists and then recreate it unless
208 preserve_install_dir is specified as True.
209 Fetch the package into the pkg_dir. Untar the package into install_dir
210 The assumption is that packages are of the form :
211 <pkg_type>.<pkg_name>.tar.bz2
212 name : name of the package
213 type : type of the package
214 fetch_dir : The directory into which the package tarball will be
215 fetched to.
216 install_dir : the directory where the package files will be untarred to
217 repo_url : the url of the repository to fetch the package from.
218 '''
219
220 # do_locking flag is on by default unless you disable it (typically
221 # in the cases where packages are directly installed from the server
222 # onto the client in which case fcntl stuff wont work as the code
223 # will run on the server in that case..
224 if self.do_locking:
225 lockfile_name = '.%s-%s-lock' % (name, pkg_type)
226 lockfile = open(os.path.join(self.pkgmgr_dir, lockfile_name), 'w')
227
228 try:
229 if self.do_locking:
230 fcntl.flock(lockfile, fcntl.LOCK_EX)
231
232 self._run_command('mkdir -p %s' % fetch_dir)
233
234 pkg_name = self.get_tarball_name(name, pkg_type)
235 fetch_path = os.path.join(fetch_dir, pkg_name)
236 try:
237 # Fetch the package into fetch_dir
mbligh76d19f72008-10-15 16:24:43 +0000238 self.fetch_pkg(pkg_name, fetch_path, use_checksum=True)
showard9d02fb52008-08-08 18:20:37 +0000239
240 # check to see if the install_dir exists and if it does
241 # then check to see if the .checksum file is the latest
242 install_dir_exists = False
243 try:
244 self._run_command("ls %s" % install_dir)
245 install_dir_exists = True
246 except (error.CmdError, error.AutoservRunError):
247 pass
248
249 if (install_dir_exists and
250 not self.untar_required(fetch_path, install_dir)):
251 return
252
253 # untar the package into install_dir and
254 # update the checksum in that directory
255 if not preserve_install_dir:
256 # Make sure we clean up the install_dir
257 self._run_command('rm -rf %s' % install_dir)
258 self._run_command('mkdir -p %s' % install_dir)
259
260 self.untar_pkg(fetch_path, install_dir)
261
262 except PackageFetchError, why:
263 raise PackageInstallError('Installation of %s(type:%s) failed'
264 ' : %s' % (name, pkg_type, why))
265 finally:
266 if self.do_locking:
267 fcntl.flock(lockfile, fcntl.LOCK_UN)
268 lockfile.close()
269
270
mbligh76d19f72008-10-15 16:24:43 +0000271 def fetch_pkg(self, pkg_name, dest_path, repo_url=None, use_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000272 '''
273 Fetch the package into dest_dir from repo_url. By default repo_url
274 is None and the package is looked in all the repostories specified.
275 Otherwise it fetches it from the specific repo_url.
276 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
277 dep-gcc.tar.bz2, kernel.1-1.rpm)
278 repo_url : the URL of the repository where the package is located.
279 dest_path : complete path of where the package will be fetched to.
280 use_checksum : This is set to False to fetch the packages.checksum file
281 so that the checksum comparison is bypassed for the
282 checksum file itself. This is used internally by the
283 packaging system. It should be ignored by externals
284 callers of this method who use it fetch custom packages.
285 '''
286
287 try:
288 self._run_command("ls %s" % os.path.dirname(dest_path))
289 except (error.CmdError, error.AutoservRunError):
290 raise PackageFetchError("Please provide a valid "
291 "destination: %s " % dest_path)
292
293 # See if the package was already fetched earlier, if so
294 # the checksums need to be compared and the package is now
295 # fetched only if they differ.
296 pkg_exists = False
297 try:
298 self._run_command("ls %s" % dest_path)
299 pkg_exists = True
300 except (error.CmdError, error.AutoservRunError):
301 pass
302
303 # if a repository location is explicitly provided, fetch the package
304 # from there and return
305 if repo_url:
306 repo_url_list = [repo_url]
307 elif len(self.repo_urls) > 0:
308 repo_url_list = self.repo_urls
309 else:
310 raise PackageFetchError("There are no repository urls specified")
311
312 error_msgs = {}
313 for location in repo_url_list:
314 try:
315 # Fetch the checksum if it not there
316 if not use_checksum:
317 self.fetch_pkg_file(pkg_name, dest_path, location)
318
319 # Fetch the package if a) the pkg does not exist or
320 # b) if the checksum differs for the existing package
321 elif (not pkg_exists or
322 not self.compare_checksum(dest_path, location)):
323 self.fetch_pkg_file(pkg_name, dest_path, location)
324 # Update the checksum of the package in the packages'
325 # checksum file
326 self.update_checksum(dest_path)
327 return
mbligh7a603672009-02-07 01:52:08 +0000328 except (PackageFetchError, error.AutoservRunError):
showard9d02fb52008-08-08 18:20:37 +0000329 # The package could not be found in this repo, continue looking
jadmanski19426ea2009-07-28 20:19:40 +0000330 logging.error('%s could not be fetched from %s', pkg_name,
showarda290e982009-03-27 20:57:30 +0000331 location)
showard9d02fb52008-08-08 18:20:37 +0000332
333 # if we got here then that means the package is not found
334 # in any of the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000335 raise PackageFetchError("%s could not be fetched from any of"
336 " the repos %s : %s " % (pkg_name,
337 repo_url_list,
showard9d02fb52008-08-08 18:20:37 +0000338 error_msgs))
339
340
mbligh76d19f72008-10-15 16:24:43 +0000341 def fetch_pkg_file(self, filename, dest_path, source_url):
showard9d02fb52008-08-08 18:20:37 +0000342 """
343 Fetch the file from source_url into dest_path. The package repository
344 url is parsed and the appropriate retrieval method is determined.
345
346 """
347 if source_url.startswith('http://'):
mbligh76d19f72008-10-15 16:24:43 +0000348 self.fetch_file_http(filename, dest_path, source_url)
showard9d02fb52008-08-08 18:20:37 +0000349 else:
mbligh76d19f72008-10-15 16:24:43 +0000350 raise PackageFetchError("Invalid location %s" % source_url)
showard9d02fb52008-08-08 18:20:37 +0000351
352
mbligh76d19f72008-10-15 16:24:43 +0000353 def fetch_file_http(self, filename, dest_path, source_url):
showard9d02fb52008-08-08 18:20:37 +0000354 """
355 Fetch the package using http protocol. Raises a PackageFetchError.
356 """
jadmanski19426ea2009-07-28 20:19:40 +0000357 logging.info("Fetching %s from %s to %s", filename, source_url,
showarda290e982009-03-27 20:57:30 +0000358 dest_path)
showard9d02fb52008-08-08 18:20:37 +0000359 # check to see if the source_url is reachable or not
360 self.run_http_test(source_url, os.path.dirname(dest_path))
361
mbligh76d19f72008-10-15 16:24:43 +0000362 pkg_path = os.path.join(source_url, filename)
showard9d02fb52008-08-08 18:20:37 +0000363 try:
jadmanskic1335992009-07-28 20:20:41 +0000364 cmd = self._wget_cmd_pattern % (pkg_path, dest_path)
365 result = self._run_command(cmd)
366
367 file_exists = self._run_command('ls %s' % dest_path,
368 _run_command_dargs={'ignore_status': True}).exit_status == 0
369 if not file_exists:
370 # wget failed but it did not reflect that in the exit status
371 logging.error('wget failed: %s', result)
372 raise error.CmdError(cmd, result)
373
showarda290e982009-03-27 20:57:30 +0000374 logging.debug("Successfully fetched %s from %s", filename,
375 source_url)
mbligh7a603672009-02-07 01:52:08 +0000376 except error.CmdError:
377 raise PackageFetchError("%s not found in %s" % (filename,
378 source_url))
showard9d02fb52008-08-08 18:20:37 +0000379
380
381 def run_http_test(self, source_url, dest_dir):
382 '''
383 Run a simple 30 sec wget on source_url
384 just to see if it can be reachable or not. This avoids the need
385 for waiting for a 10min timeout.
386 '''
387 dest_file_path = os.path.join(dest_dir, 'http_test_file')
388
389 BPM = BasePackageManager
390 error_msg = "HTTP test failed. Failed to contact"
391 # We should never get here unless the source_url starts with http://
392 assert(source_url.startswith('http://'))
393
394 # Get the http server name from the URL
395 server_name = urlparse.urlparse(source_url)[1]
jadmanski19426ea2009-07-28 20:19:40 +0000396 http_cmd = self._wget_cmd_pattern % (server_name, dest_file_path)
mblighabe330e2008-12-09 23:37:52 +0000397
398 # Following repo_exception optimization is disabled for now.
jadmanski19426ea2009-07-28 20:19:40 +0000399 # Checksum files are optional. The attempted download of a
400 # missing checksum file erroneously causes the repos to be marked
401 # dead, causing download of its custom kernels to fail.
mblighabe330e2008-12-09 23:37:52 +0000402 # It also stays dead until Autotest is restarted.
jadmanski19426ea2009-07-28 20:19:40 +0000403 if server_name in BPM._repo_exception and False: # <--- TEMP
showard9d02fb52008-08-08 18:20:37 +0000404 if BPM._repo_exception[server_name] == BPM.REPO_OK:
405 # This repository is fine. Simply return
406 return
407 else:
408 raise PackageFetchError("%s - %s : %s "
409 % (error_msg, server_name,
410 BPM._repo_exception[server_name]))
411 try:
412 try:
413 self._run_command(http_cmd,
414 _run_command_dargs={'timeout':30})
415 BPM._repo_exception[server_name] = BPM.REPO_OK
416 finally:
417 self._run_command('rm -f %s' % dest_file_path)
mbligh76d19f72008-10-15 16:24:43 +0000418 except Exception, e:
showard9d02fb52008-08-08 18:20:37 +0000419 BPM._repo_exception[server_name] = e
mbligh76d19f72008-10-15 16:24:43 +0000420 raise PackageFetchError("%s - %s: %s " % (error_msg, server_name,
421 e))
showard9d02fb52008-08-08 18:20:37 +0000422
423
mblighe1836812009-04-17 18:25:14 +0000424 def upload_pkg(self, pkg_path, upload_path=None, update_checksum=False):
mbligh1a519b92009-04-17 18:26:19 +0000425 from autotest_lib.server import subcommand
mblighe1836812009-04-17 18:25:14 +0000426 if upload_path:
427 upload_path_list = [upload_path]
428 self.upkeep(upload_path_list)
429 elif len(self.upload_paths) > 0:
430 self.upkeep()
431 upload_path_list = self.upload_paths
432 else:
433 raise PackageUploadError("Invalid Upload Path specified")
434
mblighcf57ae22009-04-17 20:13:01 +0000435 if update_checksum:
436 # get the packages' checksum file and update it with the current
437 # package's checksum
438 self.update_checksum(pkg_path)
439
mblighe1836812009-04-17 18:25:14 +0000440 commands = []
441 for path in upload_path_list:
442 commands.append(subcommand.subcommand(self.upload_pkg_parallel,
443 (pkg_path, path,
444 update_checksum)))
445
446 results = subcommand.parallel(commands, 300, return_results=True)
447 for result in results:
448 if result:
449 print str(result)
450
451
showard9d02fb52008-08-08 18:20:37 +0000452 # TODO(aganti): Fix the bug with the current checksum logic where
453 # packages' checksums that are not present consistently in all the
454 # repositories are not handled properly. This is a corner case though
455 # but the ideal solution is to make the checksum file repository specific
456 # and then maintain it.
mblighe1836812009-04-17 18:25:14 +0000457 def upload_pkg_parallel(self, pkg_path, upload_path, update_checksum=False):
showard9d02fb52008-08-08 18:20:37 +0000458 '''
459 Uploads to a specified upload_path or to all the repos.
460 Also uploads the checksum file to all the repos.
461 pkg_path : The complete path to the package file
462 upload_path : the absolute path where the files are copied to.
463 if set to 'None' assumes 'all' repos
464 update_checksum : If set to False, the checksum file is not
465 going to be updated which happens by default.
466 This is necessary for custom
467 packages (like custom kernels and custom tests)
468 that get uploaded which do not need to be part of
469 the checksum file and bloat it.
470 '''
mblighe1836812009-04-17 18:25:14 +0000471 self.repo_check(upload_path)
showard9d02fb52008-08-08 18:20:37 +0000472 # upload the package
mblighe1836812009-04-17 18:25:14 +0000473 if os.path.isdir(pkg_path):
474 self.upload_pkg_dir(pkg_path, upload_path)
475 else:
476 self.upload_pkg_file(pkg_path, upload_path)
showard9d02fb52008-08-08 18:20:37 +0000477 if update_checksum:
mblighcf57ae22009-04-17 20:13:01 +0000478 self.upload_pkg_file(self._get_checksum_file_path(),
479 upload_path)
showard9d02fb52008-08-08 18:20:37 +0000480
481
482 def upload_pkg_file(self, file_path, upload_path):
483 '''
484 Upload a single file. Depending on the upload path, the appropriate
485 method for that protocol is called. Currently this simply copies the
486 file to the target directory (but can be extended for other protocols)
487 This assumes that the web server is running on the same machine where
488 the method is being called from. The upload_path's files are
489 basically served by that web server.
490 '''
491 try:
mbligh93a9e292008-10-10 21:09:53 +0000492 if upload_path.startswith('ssh://'):
493 # parse ssh://user@host/usr/local/autotest/packages
mblighe1836812009-04-17 18:25:14 +0000494 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000495 try:
496 utils.run('scp %s %s:%s' % (file_path, hostline,
497 remote_path))
498 r_path = os.path.join(remote_path,
499 os.path.basename(file_path))
500 utils.run("ssh %s 'chmod 644 %s'" % (hostline, r_path))
501 except error.CmdError:
jadmanski19426ea2009-07-28 20:19:40 +0000502 logging.error("Error uploading to repository %s",
showarda290e982009-03-27 20:57:30 +0000503 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000504 else:
505 shutil.copy(file_path, upload_path)
506 os.chmod(os.path.join(upload_path,
507 os.path.basename(file_path)), 0644)
showard9d02fb52008-08-08 18:20:37 +0000508 except (IOError, os.error), why:
jadmanski19426ea2009-07-28 20:19:40 +0000509 logging.error("Upload of %s to %s failed: %s", file_path,
showarda290e982009-03-27 20:57:30 +0000510 upload_path, why)
showard9d02fb52008-08-08 18:20:37 +0000511
512
mbligh9fc77972008-10-02 20:32:09 +0000513 def upload_pkg_dir(self, dir_path, upload_path):
514 '''
515 Upload a full directory. Depending on the upload path, the appropriate
516 method for that protocol is called. Currently this copies the whole
517 tmp package directory to the target directory.
518 This assumes that the web server is running on the same machine where
519 the method is being called from. The upload_path's files are
520 basically served by that web server.
521 '''
mbligh93a9e292008-10-10 21:09:53 +0000522 local_path = os.path.join(dir_path, "*")
mbligh9fc77972008-10-02 20:32:09 +0000523 try:
mbligh93a9e292008-10-10 21:09:53 +0000524 if upload_path.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000525 hostline, remote_path = parse_ssh_path(upload_path)
mbligh1e3b0992008-10-14 16:29:54 +0000526 try:
527 utils.run('scp %s %s:%s' % (local_path, hostline,
528 remote_path))
529 ssh_path = os.path.join(remote_path, "*")
530 utils.run("ssh %s 'chmod 644 %s'" % (hostline, ssh_path))
531 except error.CmdError:
showarda290e982009-03-27 20:57:30 +0000532 logging.error("Error uploading to repository: %s",
533 upload_path)
mbligh93a9e292008-10-10 21:09:53 +0000534 else:
535 utils.run("cp %s %s " % (local_path, upload_path))
536 up_path = os.path.join(upload_path, "*")
537 utils.run("chmod 644 %s" % up_path)
mbligh9fc77972008-10-02 20:32:09 +0000538 except (IOError, os.error), why:
539 raise PackageUploadError("Upload of %s to %s failed: %s"
540 % (dir_path, upload_path, why))
541
542
showard9d02fb52008-08-08 18:20:37 +0000543 def remove_pkg(self, pkg_name, remove_path=None, remove_checksum=False):
544 '''
545 Remove the package from the specified remove_path
546 pkg_name : name of the package (ex: test-sleeptest.tar.bz2,
547 dep-gcc.tar.bz2)
548 remove_path : the location to remove the package from.
549
550 '''
551 if remove_path:
552 remove_path_list = [remove_path]
553 elif len(self.upload_paths) > 0:
554 remove_path_list = self.upload_paths
555 else:
556 raise PackageRemoveError("Invalid path to remove the pkg from")
557
558 checksum_path = self._get_checksum_file_path()
559
560 if remove_checksum:
561 self.remove_checksum(pkg_name)
562
563 # remove the package and upload the checksum file to the repos
564 for path in remove_path_list:
565 self.remove_pkg_file(pkg_name, path)
566 self.upload_pkg_file(checksum_path, path)
567
568
mbligh76d19f72008-10-15 16:24:43 +0000569 def remove_pkg_file(self, filename, pkg_dir):
showard9d02fb52008-08-08 18:20:37 +0000570 '''
mbligh76d19f72008-10-15 16:24:43 +0000571 Remove the file named filename from pkg_dir
showard9d02fb52008-08-08 18:20:37 +0000572 '''
573 try:
574 # Remove the file
mbligh93a9e292008-10-10 21:09:53 +0000575 if pkg_dir.startswith('ssh://'):
mblighe1836812009-04-17 18:25:14 +0000576 hostline, remote_path = parse_ssh_path(pkg_dir)
mbligh76d19f72008-10-15 16:24:43 +0000577 path = os.path.join(remote_path, filename)
mbligh93a9e292008-10-10 21:09:53 +0000578 utils.run("ssh %s 'rm -rf %s/%s'" % (hostline, remote_path,
579 path))
580 else:
mbligh76d19f72008-10-15 16:24:43 +0000581 os.remove(os.path.join(pkg_dir, filename))
showard9d02fb52008-08-08 18:20:37 +0000582 except (IOError, os.error), why:
583 raise PackageRemoveError("Could not remove %s from %s: %s "
mbligh76d19f72008-10-15 16:24:43 +0000584 % (filename, pkg_dir, why))
showard9d02fb52008-08-08 18:20:37 +0000585
586
mbligh76d19f72008-10-15 16:24:43 +0000587 def get_mirror_list(self):
mbligh1e3b0992008-10-14 16:29:54 +0000588 '''
mbligh76d19f72008-10-15 16:24:43 +0000589 Stub function for site specific mirrors.
mbligh1e3b0992008-10-14 16:29:54 +0000590
591 Returns:
592 Priority ordered list
593 '''
594 return self.repo_urls
595
596
showard9d02fb52008-08-08 18:20:37 +0000597 def _get_checksum_file_path(self):
598 '''
599 Return the complete path of the checksum file (assumed to be stored
600 in self.pkgmgr_dir
601 '''
602 return os.path.join(self.pkgmgr_dir, CHECKSUM_FILE)
603
604
605 def _get_checksum_dict(self):
606 '''
607 Fetch the checksum file if not already fetched. If the checksum file
608 cannot be fetched from the repos then a new file is created with
609 the current package's (specified in pkg_path) checksum value in it.
610 Populate the local checksum dictionary with the values read from
611 the checksum file.
612 The checksum file is assumed to be present in self.pkgmgr_dir
613 '''
614 checksum_path = self._get_checksum_file_path()
615 if not self._checksum_dict:
616 # Fetch the checksum file
617 try:
618 try:
619 self._run_command("ls %s" % checksum_path)
620 except (error.CmdError, error.AutoservRunError):
621 # The packages checksum file does not exist locally.
622 # See if it is present in the repositories.
mbligh76d19f72008-10-15 16:24:43 +0000623 self.fetch_pkg(CHECKSUM_FILE, checksum_path)
showard9d02fb52008-08-08 18:20:37 +0000624 except PackageFetchError, e:
625 # This should not happen whilst fetching a package..if a
626 # package is present in the repository, the corresponding
627 # checksum file should also be automatically present. This
628 # case happens only when a package
629 # is being uploaded and if it is the first package to be
630 # uploaded to the repos (hence no checksum file created yet)
631 # Return an empty dictionary in that case
632 return {}
633
634 # Read the checksum file into memory
635 checksum_file_contents = self._run_command('cat '
636 + checksum_path).stdout
637
638 # Return {} if we have an empty checksum file present
639 if not checksum_file_contents.strip():
640 return {}
641
642 # Parse the checksum file contents into self._checksum_dict
643 for line in checksum_file_contents.splitlines():
644 checksum, package_name = line.split(None, 1)
645 self._checksum_dict[package_name] = checksum
646
647 return self._checksum_dict
648
649
650 def _save_checksum_dict(self, checksum_dict):
651 '''
652 Save the checksum dictionary onto the checksum file. Update the
653 local _checksum_dict variable with this new set of values.
654 checksum_dict : New checksum dictionary
655 checksum_dir : The directory in which to store the checksum file to.
656 '''
657 checksum_path = self._get_checksum_file_path()
658 self._checksum_dict = checksum_dict.copy()
659 checksum_contents = '\n'.join(checksum + ' ' + pkg_name
660 for pkg_name,checksum in
661 checksum_dict.iteritems())
662 # Write the checksum file back to disk
663 self._run_command('echo "%s" > %s' % (checksum_contents,
664 checksum_path))
665
666
667 def compute_checksum(self, pkg_path):
668 '''
669 Compute the MD5 checksum for the package file and return it.
670 pkg_path : The complete path for the package file
671 '''
672 md5sum_output = self._run_command("md5sum %s " % pkg_path).stdout
673 return md5sum_output.split()[0]
674
675
676 def update_checksum(self, pkg_path):
677 '''
678 Update the checksum of the package in the packages' checksum
679 file. This method is called whenever a package is fetched just
680 to be sure that the checksums in the local file are the latest.
681 pkg_path : The complete path to the package file.
682 '''
683 # Compute the new checksum
684 new_checksum = self.compute_checksum(pkg_path)
685 checksum_dict = self._get_checksum_dict()
686 checksum_dict[os.path.basename(pkg_path)] = new_checksum
687 self._save_checksum_dict(checksum_dict)
688
689
690 def remove_checksum(self, pkg_name):
691 '''
692 Remove the checksum of the package from the packages checksum file.
693 This method is called whenever a package is removed from the
694 repositories in order clean its corresponding checksum.
695 pkg_name : The name of the package to be removed
696 '''
697 checksum_dict = self._get_checksum_dict()
698 if pkg_name in checksum_dict:
699 del checksum_dict[pkg_name]
700 self._save_checksum_dict(checksum_dict)
701
702
703 def compare_checksum(self, pkg_path, repo_url):
704 '''
705 Calculate the checksum of the file specified in pkg_path and
706 compare it with the checksum in the checksum file
707 Return True if both match else return False.
708 pkg_path : The full path to the package file for which the
709 checksum is being compared
710 repo_url : The URL to fetch the checksum from
711 '''
712 checksum_dict = self._get_checksum_dict()
713 package_name = os.path.basename(pkg_path)
714 if not checksum_dict or package_name not in checksum_dict:
715 return False
716
717 repository_checksum = checksum_dict[package_name]
718 local_checksum = self.compute_checksum(pkg_path)
719 return (local_checksum == repository_checksum)
720
721
mblighdbfc4e32008-08-22 18:08:07 +0000722 def tar_package(self, pkg_name, src_dir, dest_dir, exclude_string=None):
showard9d02fb52008-08-08 18:20:37 +0000723 '''
724 Create a tar.bz2 file with the name 'pkg_name' say test-blah.tar.bz2.
mbligh9fc77972008-10-02 20:32:09 +0000725 Excludes the directories specified in exclude_string while tarring
showard9d02fb52008-08-08 18:20:37 +0000726 the source. Returns the tarball path.
727 '''
showard9d02fb52008-08-08 18:20:37 +0000728 tarball_path = os.path.join(dest_dir, pkg_name)
mblighd5a61a02009-05-21 01:44:30 +0000729 temp_path = tarball_path + '.tmp'
730 cmd = "tar -cvjf %s -C %s %s " % (temp_path, src_dir, exclude_string)
showard9d02fb52008-08-08 18:20:37 +0000731
mblighd5a61a02009-05-21 01:44:30 +0000732 try:
733 utils.system(cmd)
734 except:
735 os.unlink(temp_path)
736 raise
737
738 os.rename(temp_path, tarball_path)
showard9d02fb52008-08-08 18:20:37 +0000739 return tarball_path
740
741
742 def untar_required(self, tarball_path, dest_dir):
743 '''
744 Compare the checksum of the tarball_path with the .checksum file
745 in the dest_dir and return False if it matches. The untar
746 of the package happens only if the checksums do not match.
747 '''
748 checksum_path = os.path.join(dest_dir, '.checksum')
749 try:
750 existing_checksum = self._run_command('cat ' + checksum_path).stdout
751 except (error.CmdError, error.AutoservRunError):
752 # If the .checksum file is not present (generally, this should
753 # not be the case) then return True so that the untar happens
754 return True
755
756 new_checksum = self.compute_checksum(tarball_path)
757 return (new_checksum.strip() != existing_checksum.strip())
758
759
760 def untar_pkg(self, tarball_path, dest_dir):
761 '''
762 Untar the package present in the tarball_path and put a
763 ".checksum" file in the dest_dir containing the checksum
764 of the tarball. This method
765 assumes that the package to be untarred is of the form
766 <name>.tar.bz2
767 '''
mbligh96ad8512008-10-03 03:45:26 +0000768 self._run_command('tar xjf %s -C %s' % (tarball_path, dest_dir))
showard9d02fb52008-08-08 18:20:37 +0000769 # Put the .checksum file in the install_dir to note
770 # where the package came from
771 pkg_checksum = self.compute_checksum(tarball_path)
772 pkg_checksum_path = os.path.join(dest_dir,
773 '.checksum')
774 self._run_command('echo "%s" > %s '
775 % (pkg_checksum, pkg_checksum_path))
776
777
778 def get_tarball_name(self, name, pkg_type):
779 return "%s-%s.tar.bz2" % (pkg_type, name)
780
781
782 def is_url(self, url):
783 """Return true if path looks like a URL"""
784 return url.startswith('http://')
785
786
787 def get_package_name(self, url, pkg_type):
788 '''
789 Extract the group and test name for the url. This method is currently
790 used only for tests.
791 '''
792 if pkg_type == 'test':
mblighecbaec32008-10-25 13:37:42 +0000793 regex = '[^:]+://(.*)/([^/]*)$'
showard9d02fb52008-08-08 18:20:37 +0000794 return self._get_package_name(url, regex)
795 else:
796 return ('', url)
797
798
799 def _get_package_name(self, url, regex):
800 if not self.is_url(url):
801 if url.endswith('.tar.bz2'):
802 testname = url.replace('.tar.bz2', '')
803 testname = re.sub(r'(\d*)\.', '', testname)
804 return (testname, testname)
805 else:
806 return ('', url)
807
808 match = re.match(regex, url)
809 if not match:
810 return ('', url)
811 group, filename = match.groups()
812 # Generate the group prefix.
813 group = re.sub(r'\W', '_', group)
814 # Drop the extension to get the raw test name.
815 testname = re.sub(r'\.tar\.bz2', '', filename)
816 # Drop any random numbers at the end of the test name if any
817 testname = re.sub(r'\.(\d*)', '', testname)
818 return (group, testname)
819
820
mbligha7007722009-01-13 00:37:11 +0000821SitePackageManager = utils.import_site_class(
822 __file__, "autotest_lib.client.common_lib.site_packages",
823 "SitePackageManager", BasePackageManager)
showard9d02fb52008-08-08 18:20:37 +0000824
825class PackageManager(SitePackageManager):
826 pass