Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(943)

Unified Diff: swarm_client/run_isolated.py

Issue 69143004: Delete swarm_client. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « swarm_client/isolateserver.py ('k') | swarm_client/swarming.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: swarm_client/run_isolated.py
===================================================================
--- swarm_client/run_isolated.py (revision 235167)
+++ swarm_client/run_isolated.py (working copy)
@@ -1,613 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Reads a .isolated, creates a tree of hardlinks and runs the test.
-
-Keeps a local cache.
-"""
-
-__version__ = '0.2'
-
-import ctypes
-import logging
-import optparse
-import os
-import re
-import shutil
-import stat
-import subprocess
-import sys
-import tempfile
-import time
-
-from third_party.depot_tools import fix_encoding
-
-from utils import lru
-from utils import threading_utils
-from utils import tools
-from utils import zip_package
-
-import isolateserver
-
-
-# Absolute path to this file (can be None if running from zip on Mac).
-THIS_FILE_PATH = os.path.abspath(__file__) if __file__ else None
-
-# Directory that contains this file (might be inside zip package).
-BASE_DIR = os.path.dirname(THIS_FILE_PATH) if __file__ else None
-
-# Directory that contains currently running script file.
-if zip_package.get_main_script_path():
- MAIN_DIR = os.path.dirname(
- os.path.abspath(zip_package.get_main_script_path()))
-else:
- # This happens when 'import run_isolated' is executed at the python
- # interactive prompt, in that case __file__ is undefined.
- MAIN_DIR = None
-
-# Types of action accepted by link_file().
-HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY = range(1, 5)
-
-# The name of the log file to use.
-RUN_ISOLATED_LOG_FILE = 'run_isolated.log'
-
-# The name of the log to use for the run_test_cases.py command
-RUN_TEST_CASES_LOG = 'run_test_cases.log'
-
-
-# Used by get_flavor().
-FLAVOR_MAPPING = {
- 'cygwin': 'win',
- 'win32': 'win',
- 'darwin': 'mac',
- 'sunos5': 'solaris',
- 'freebsd7': 'freebsd',
- 'freebsd8': 'freebsd',
-}
-
-
-def get_as_zip_package(executable=True):
- """Returns ZipPackage with this module and all its dependencies.
-
- If |executable| is True will store run_isolated.py as __main__.py so that
- zip package is directly executable be python.
- """
- # Building a zip package when running from another zip package is
- # unsupported and probably unneeded.
- assert not zip_package.is_zipped_module(sys.modules[__name__])
- assert THIS_FILE_PATH
- assert BASE_DIR
- package = zip_package.ZipPackage(root=BASE_DIR)
- package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None)
- package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py'))
- package.add_directory(os.path.join(BASE_DIR, 'third_party'))
- package.add_directory(os.path.join(BASE_DIR, 'utils'))
- return package
-
-
-def get_flavor():
- """Returns the system default flavor. Copied from gyp/pylib/gyp/common.py."""
- return FLAVOR_MAPPING.get(sys.platform, 'linux')
-
-
-def os_link(source, link_name):
- """Add support for os.link() on Windows."""
- if sys.platform == 'win32':
- if not ctypes.windll.kernel32.CreateHardLinkW(
- unicode(link_name), unicode(source), 0):
- raise OSError()
- else:
- os.link(source, link_name)
-
-
-def readable_copy(outfile, infile):
- """Makes a copy of the file that is readable by everyone."""
- shutil.copy2(infile, outfile)
- read_enabled_mode = (os.stat(outfile).st_mode | stat.S_IRUSR |
- stat.S_IRGRP | stat.S_IROTH)
- os.chmod(outfile, read_enabled_mode)
-
-
-def link_file(outfile, infile, action):
- """Links a file. The type of link depends on |action|."""
- logging.debug('Mapping %s to %s' % (infile, outfile))
- if action not in (HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY):
- raise ValueError('Unknown mapping action %s' % action)
- if not os.path.isfile(infile):
- raise isolateserver.MappingError('%s is missing' % infile)
- if os.path.isfile(outfile):
- raise isolateserver.MappingError(
- '%s already exist; insize:%d; outsize:%d' %
- (outfile, os.stat(infile).st_size, os.stat(outfile).st_size))
-
- if action == COPY:
- readable_copy(outfile, infile)
- elif action == SYMLINK and sys.platform != 'win32':
- # On windows, symlink are converted to hardlink and fails over to copy.
- os.symlink(infile, outfile) # pylint: disable=E1101
- else:
- try:
- os_link(infile, outfile)
- except OSError as e:
- if action == HARDLINK:
- raise isolateserver.MappingError(
- 'Failed to hardlink %s to %s: %s' % (infile, outfile, e))
- # Probably a different file system.
- logging.warning(
- 'Failed to hardlink, failing back to copy %s to %s' % (
- infile, outfile))
- readable_copy(outfile, infile)
-
-
-def _set_write_bit(path, read_only):
- """Sets or resets the executable bit on a file or directory."""
- mode = os.lstat(path).st_mode
- if read_only:
- mode = mode & 0500
- else:
- mode = mode | 0200
- if hasattr(os, 'lchmod'):
- os.lchmod(path, mode) # pylint: disable=E1101
- else:
- if stat.S_ISLNK(mode):
- # Skip symlink without lchmod() support.
- logging.debug('Can\'t change +w bit on symlink %s' % path)
- return
-
- # TODO(maruel): Implement proper DACL modification on Windows.
- os.chmod(path, mode)
-
-
-def make_writable(root, read_only):
- """Toggle the writable bit on a directory tree."""
- assert os.path.isabs(root), root
- for dirpath, dirnames, filenames in os.walk(root, topdown=True):
- for filename in filenames:
- _set_write_bit(os.path.join(dirpath, filename), read_only)
-
- for dirname in dirnames:
- _set_write_bit(os.path.join(dirpath, dirname), read_only)
-
-
-def rmtree(root):
- """Wrapper around shutil.rmtree() to retry automatically on Windows."""
- make_writable(root, False)
- if sys.platform == 'win32':
- for i in range(3):
- try:
- shutil.rmtree(root)
- break
- except WindowsError: # pylint: disable=E0602
- delay = (i+1)*2
- print >> sys.stderr, (
- 'The test has subprocess outliving it. Sleep %d seconds.' % delay)
- time.sleep(delay)
- else:
- shutil.rmtree(root)
-
-
-def try_remove(filepath):
- """Removes a file without crashing even if it doesn't exist."""
- try:
- os.remove(filepath)
- except OSError:
- pass
-
-
-def is_same_filesystem(path1, path2):
- """Returns True if both paths are on the same filesystem.
-
- This is required to enable the use of hardlinks.
- """
- assert os.path.isabs(path1), path1
- assert os.path.isabs(path2), path2
- if sys.platform == 'win32':
- # If the drive letter mismatches, assume it's a separate partition.
- # TODO(maruel): It should look at the underlying drive, a drive letter could
- # be a mount point to a directory on another drive.
- assert re.match(r'^[a-zA-Z]\:\\.*', path1), path1
- assert re.match(r'^[a-zA-Z]\:\\.*', path2), path2
- if path1[0].lower() != path2[0].lower():
- return False
- return os.stat(path1).st_dev == os.stat(path2).st_dev
-
-
-def get_free_space(path):
- """Returns the number of free bytes."""
- if sys.platform == 'win32':
- free_bytes = ctypes.c_ulonglong(0)
- ctypes.windll.kernel32.GetDiskFreeSpaceExW(
- ctypes.c_wchar_p(path), None, None, ctypes.pointer(free_bytes))
- return free_bytes.value
- # For OSes other than Windows.
- f = os.statvfs(path) # pylint: disable=E1101
- return f.f_bfree * f.f_frsize
-
-
-def make_temp_dir(prefix, root_dir):
- """Returns a temporary directory on the same file system as root_dir."""
- base_temp_dir = None
- if not is_same_filesystem(root_dir, tempfile.gettempdir()):
- base_temp_dir = os.path.dirname(root_dir)
- return tempfile.mkdtemp(prefix=prefix, dir=base_temp_dir)
-
-
-class CachePolicies(object):
- def __init__(self, max_cache_size, min_free_space, max_items):
- """
- Arguments:
- - max_cache_size: Trim if the cache gets larger than this value. If 0, the
- cache is effectively a leak.
- - min_free_space: Trim if disk free space becomes lower than this value. If
- 0, it unconditionally fill the disk.
- - max_items: Maximum number of items to keep in the cache. If 0, do not
- enforce a limit.
- """
- self.max_cache_size = max_cache_size
- self.min_free_space = min_free_space
- self.max_items = max_items
-
-
-class DiskCache(isolateserver.LocalCache):
- """Stateful LRU cache in a flat hash table in a directory.
-
- Saves its state as json file.
- """
- STATE_FILE = 'state.json'
-
- def __init__(self, cache_dir, policies, algo):
- """
- Arguments:
- cache_dir: directory where to place the cache.
- policies: cache retention policies.
- algo: hashing algorithm used.
- """
- super(DiskCache, self).__init__()
- self.algo = algo
- self.cache_dir = cache_dir
- self.policies = policies
- self.state_file = os.path.join(cache_dir, self.STATE_FILE)
-
- # All protected methods (starting with '_') except _path should be called
- # with this lock locked.
- self._lock = threading_utils.LockWithAssert()
- self._lru = lru.LRUDict()
-
- # Profiling values.
- self._added = []
- self._removed = []
- self._free_disk = 0
-
- with tools.Profiler('Setup'):
- with self._lock:
- self._load()
-
- def __enter__(self):
- return self
-
- def __exit__(self, _exc_type, _exec_value, _traceback):
- with tools.Profiler('CleanupTrimming'):
- with self._lock:
- self._trim()
-
- logging.info(
- '%5d (%8dkb) added',
- len(self._added), sum(self._added) / 1024)
- logging.info(
- '%5d (%8dkb) current',
- len(self._lru),
- sum(self._lru.itervalues()) / 1024)
- logging.info(
- '%5d (%8dkb) removed',
- len(self._removed), sum(self._removed) / 1024)
- logging.info(
- ' %8dkb free',
- self._free_disk / 1024)
- return False
-
- def cached_set(self):
- with self._lock:
- return self._lru.keys_set()
-
- def touch(self, digest, size):
- # Verify an actual file is valid. Note that is doesn't compute the hash so
- # it could still be corrupted. Do it outside the lock.
- if not isolateserver.is_valid_file(self._path(digest), size):
- return False
-
- # Update it's LRU position.
- with self._lock:
- if digest not in self._lru:
- return False
- self._lru.touch(digest)
- return True
-
- def evict(self, digest):
- with self._lock:
- self._lru.pop(digest)
- self._delete_file(digest, isolateserver.UNKNOWN_FILE_SIZE)
-
- def read(self, digest):
- with open(self._path(digest), 'rb') as f:
- return f.read()
-
- def write(self, digest, content):
- path = self._path(digest)
- try:
- size = isolateserver.file_write(path, content)
- except:
- # There are two possible places were an exception can occur:
- # 1) Inside |content| generator in case of network or unzipping errors.
- # 2) Inside file_write itself in case of disk IO errors.
- # In any case delete an incomplete file and propagate the exception to
- # caller, it will be logged there.
- try_remove(path)
- raise
- with self._lock:
- self._add(digest, size)
-
- def link(self, digest, dest, file_mode=None):
- link_file(dest, self._path(digest), HARDLINK)
- if file_mode is not None:
- os.chmod(dest, file_mode)
-
- def _load(self):
- """Loads state of the cache from json file."""
- self._lock.assert_locked()
-
- if not os.path.isdir(self.cache_dir):
- os.makedirs(self.cache_dir)
-
- # Load state of the cache.
- if os.path.isfile(self.state_file):
- try:
- self._lru = lru.LRUDict.load(self.state_file)
- except ValueError as err:
- logging.error('Failed to load cache state: %s' % (err,))
- # Don't want to keep broken state file.
- os.remove(self.state_file)
-
- # Ensure that all files listed in the state still exist and add new ones.
- previous = self._lru.keys_set()
- unknown = []
- for filename in os.listdir(self.cache_dir):
- if filename == self.STATE_FILE:
- continue
- if filename in previous:
- previous.remove(filename)
- continue
- # An untracked file.
- if not isolateserver.is_valid_hash(filename, self.algo):
- logging.warning('Removing unknown file %s from cache', filename)
- try_remove(self._path(filename))
- continue
- # File that's not referenced in 'state.json'.
- # TODO(vadimsh): Verify its SHA1 matches file name.
- logging.warning('Adding unknown file %s to cache', filename)
- unknown.append(filename)
-
- if unknown:
- # Add as oldest files. They will be deleted eventually if not accessed.
- self._add_oldest_list(unknown)
- logging.warning('Added back %d unknown files', len(unknown))
-
- if previous:
- # Filter out entries that were not found.
- logging.warning('Removed %d lost files', len(previous))
- for filename in previous:
- self._lru.pop(filename)
- self._trim()
-
- def _save(self):
- """Saves the LRU ordering."""
- self._lock.assert_locked()
- self._lru.save(self.state_file)
-
- def _trim(self):
- """Trims anything we don't know, make sure enough free space exists."""
- self._lock.assert_locked()
-
- # Ensure maximum cache size.
- if self.policies.max_cache_size:
- total_size = sum(self._lru.itervalues())
- while total_size > self.policies.max_cache_size:
- total_size -= self._remove_lru_file()
-
- # Ensure maximum number of items in the cache.
- if self.policies.max_items and len(self._lru) > self.policies.max_items:
- for _ in xrange(len(self._lru) - self.policies.max_items):
- self._remove_lru_file()
-
- # Ensure enough free space.
- self._free_disk = get_free_space(self.cache_dir)
- trimmed_due_to_space = False
- while (
- self.policies.min_free_space and
- self._lru and
- self._free_disk < self.policies.min_free_space):
- trimmed_due_to_space = True
- self._remove_lru_file()
- self._free_disk = get_free_space(self.cache_dir)
- if trimmed_due_to_space:
- total = sum(self._lru.itervalues())
- logging.warning(
- 'Trimmed due to not enough free disk space: %.1fkb free, %.1fkb '
- 'cache (%.1f%% of its maximum capacity)',
- self._free_disk / 1024.,
- total / 1024.,
- 100. * self.policies.max_cache_size / float(total),
- )
- self._save()
-
- def _path(self, digest):
- """Returns the path to one item."""
- return os.path.join(self.cache_dir, digest)
-
- def _remove_lru_file(self):
- """Removes the last recently used file and returns its size."""
- self._lock.assert_locked()
- digest, size = self._lru.pop_oldest()
- self._delete_file(digest, size)
- return size
-
- def _add(self, digest, size=isolateserver.UNKNOWN_FILE_SIZE):
- """Adds an item into LRU cache marking it as a newest one."""
- self._lock.assert_locked()
- if size == isolateserver.UNKNOWN_FILE_SIZE:
- size = os.stat(self._path(digest)).st_size
- self._added.append(size)
- self._lru.add(digest, size)
-
- def _add_oldest_list(self, digests):
- """Adds a bunch of items into LRU cache marking them as oldest ones."""
- self._lock.assert_locked()
- pairs = []
- for digest in digests:
- size = os.stat(self._path(digest)).st_size
- self._added.append(size)
- pairs.append((digest, size))
- self._lru.batch_insert_oldest(pairs)
-
- def _delete_file(self, digest, size=isolateserver.UNKNOWN_FILE_SIZE):
- """Deletes cache file from the file system."""
- self._lock.assert_locked()
- try:
- if size == isolateserver.UNKNOWN_FILE_SIZE:
- size = os.stat(self._path(digest)).st_size
- os.remove(self._path(digest))
- self._removed.append(size)
- except OSError as e:
- logging.error('Error attempting to delete a file %s:\n%s' % (digest, e))
-
-
-def run_tha_test(isolated_hash, storage, cache, algo, outdir):
- """Downloads the dependencies in the cache, hardlinks them into a |outdir|
- and runs the executable.
- """
- try:
- try:
- settings = isolateserver.fetch_isolated(
- isolated_hash=isolated_hash,
- storage=storage,
- cache=cache,
- algo=algo,
- outdir=outdir,
- os_flavor=get_flavor(),
- require_command=True)
- except isolateserver.ConfigError as e:
- tools.report_error(e)
- return 1
-
- if settings.read_only:
- logging.info('Making files read only')
- make_writable(outdir, True)
- cwd = os.path.normpath(os.path.join(outdir, settings.relative_cwd))
- logging.info('Running %s, cwd=%s' % (settings.command, cwd))
-
- # TODO(csharp): This should be specified somewhere else.
- # TODO(vadimsh): Pass it via 'env_vars' in manifest.
- # Add a rotating log file if one doesn't already exist.
- env = os.environ.copy()
- if MAIN_DIR:
- env.setdefault('RUN_TEST_CASES_LOG_FILE',
- os.path.join(MAIN_DIR, RUN_TEST_CASES_LOG))
- try:
- with tools.Profiler('RunTest'):
- return subprocess.call(settings.command, cwd=cwd, env=env)
- except OSError:
- tools.report_error('Failed to run %s; cwd=%s' % (settings.command, cwd))
- return 1
- finally:
- if outdir:
- rmtree(outdir)
-
-
-def main():
- tools.disable_buffering()
- parser = tools.OptionParserWithLogging(
- usage='%prog <options>',
- version=__version__,
- log_file=RUN_ISOLATED_LOG_FILE)
-
- group = optparse.OptionGroup(parser, 'Data source')
- group.add_option(
- '-s', '--isolated',
- metavar='FILE',
- help='File/url describing what to map or run')
- group.add_option(
- '-H', '--hash',
- help='Hash of the .isolated to grab from the hash table')
- group.add_option(
- '-I', '--isolate-server',
- metavar='URL', default='',
- help='Isolate server to use')
- group.add_option(
- '-n', '--namespace',
- default='default-gzip',
- help='namespace to use when using isolateserver, default: %default')
- parser.add_option_group(group)
-
- group = optparse.OptionGroup(parser, 'Cache management')
- group.add_option(
- '--cache',
- default='cache',
- metavar='DIR',
- help='Cache directory, default=%default')
- group.add_option(
- '--max-cache-size',
- type='int',
- metavar='NNN',
- default=20*1024*1024*1024,
- help='Trim if the cache gets larger than this value, default=%default')
- group.add_option(
- '--min-free-space',
- type='int',
- metavar='NNN',
- default=2*1024*1024*1024,
- help='Trim if disk free space becomes lower than this value, '
- 'default=%default')
- group.add_option(
- '--max-items',
- type='int',
- metavar='NNN',
- default=100000,
- help='Trim if more than this number of items are in the cache '
- 'default=%default')
- parser.add_option_group(group)
-
- options, args = parser.parse_args()
-
- if bool(options.isolated) == bool(options.hash):
- logging.debug('One and only one of --isolated or --hash is required.')
- parser.error('One and only one of --isolated or --hash is required.')
- if args:
- logging.debug('Unsupported args %s' % ' '.join(args))
- parser.error('Unsupported args %s' % ' '.join(args))
- if not options.isolate_server:
- parser.error('--isolate-server is required.')
-
- options.cache = os.path.abspath(options.cache)
- policies = CachePolicies(
- options.max_cache_size, options.min_free_space, options.max_items)
- storage = isolateserver.get_storage(options.isolate_server, options.namespace)
- algo = isolateserver.get_hash_algo(options.namespace)
-
- try:
- # |options.cache| may not exist until DiskCache() instance is created.
- cache = DiskCache(options.cache, policies, algo)
- outdir = make_temp_dir('run_tha_test', options.cache)
- return run_tha_test(
- options.isolated or options.hash, storage, cache, algo, outdir)
- except Exception as e:
- # Make sure any exception is logged.
- tools.report_error(e)
- logging.exception(e)
- return 1
-
-
-if __name__ == '__main__':
- # Ensure that we are always running with the correct encoding.
- fix_encoding.fix_encoding()
- sys.exit(main())
« no previous file with comments | « swarm_client/isolateserver.py ('k') | swarm_client/swarming.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698