Beispiel #1
0
  def _archive_smoke(self, size):
    self.server.store_hash_instead()
    files = {}
    for i in range(5):
      name = '512mb_%d.%s' % (i, isolateserver.ALREADY_COMPRESSED_TYPES[0])
      logging.info('Writing %s', name)
      p = os.path.join(self.tempdir, name)
      h = hashlib.sha1()
      data = os.urandom(1024)
      with open(p, 'wb') as f:
        # Write 512MiB.
        for _ in range(size / len(data)):
          f.write(data)
          h.update(data)
      os.chmod(p, 0o600)
      files[p] = h.hexdigest()

    server_ref = isolate_storage.ServerRef(self.server.url, 'default')
    with isolateserver.get_storage(server_ref) as storage:
      logging.info('Archiving')
      results, cold, hot = isolateserver.archive_files_to_storage(
          storage, list(files), None)
      logging.info('Done')

    expected = {'default': {h: h for h in files.values()}}
    self.assertEqual(expected, self.server.contents)
    self.assertEqual(files, dict(results))
    # Everything is cold.
    f = os.path.join(self.tempdir, '512mb_3.7z')
    self.assertEqual(
        sorted(files.items()), sorted((f.path, f.digest) for f in cold))
    self.assertEqual([], [(f.path, f.digest) for f in hot])
Beispiel #2
0
def delete_and_upload(storage, out_dir, leak_temp_dir):
    """Deletes the temporary run directory and uploads results back.

  Returns:
    tuple(outputs_ref, success, stats)
    - outputs_ref: a dict referring to the results archived back to the isolated
          server, if applicable.
    - success: False if something occurred that means that the task must
          forcibly be considered a failure, e.g. zombie processes were left
          behind.
    - stats: uploading stats.
  """

    # Upload out_dir and generate a .isolated file out of this directory. It is
    # only done if files were written in the directory.
    outputs_ref = None
    cold = []
    hot = []
    start = time.time()

    if fs.isdir(out_dir) and fs.listdir(out_dir):
        with tools.Profiler('ArchiveOutput'):
            try:
                results, f_cold, f_hot = isolateserver.archive_files_to_storage(
                    storage, [out_dir], None)
                outputs_ref = {
                    'isolated': results[0][0],
                    'isolatedserver': storage.location,
                    'namespace': storage.namespace,
                }
                cold = sorted(i.size for i in f_cold)
                hot = sorted(i.size for i in f_hot)
            except isolateserver.Aborted:
                # This happens when a signal SIGTERM was received while uploading data.
                # There is 2 causes:
                # - The task was too slow and was about to be killed anyway due to
                #   exceeding the hard timeout.
                # - The amount of data uploaded back is very large and took too much
                #   time to archive.
                sys.stderr.write('Received SIGTERM while uploading')
                # Re-raise, so it will be treated as an internal failure.
                raise

    success = False
    try:
        if (not leak_temp_dir and fs.isdir(out_dir)
                and not file_path.rmtree(out_dir)):
            logging.error('Had difficulties removing out_dir %s', out_dir)
        else:
            success = True
    except OSError as e:
        # When this happens, it means there's a process error.
        logging.exception('Had difficulties removing out_dir %s: %s', out_dir,
                          e)
    stats = {
        'duration': time.time() - start,
        'items_cold': base64.b64encode(large.pack(cold)),
        'items_hot': base64.b64encode(large.pack(hot)),
    }
    return outputs_ref, success, stats
Beispiel #3
0
  def test_archive_files_to_storage(self):
    # Mocked
    files_content = {}
    def add(p, c):
      with open(os.path.join(self.tempdir, p), 'wb') as f:
        f.write(c)
      files_content[p] = c
    add(u'a', 'a'*100)
    add(u'b', 'b'*200)
    os.mkdir(os.path.join(self.tempdir, 'sub'))
    add(os.path.join(u'sub', u'c'), 'c'*300)
    add(os.path.join(u'sub', u'a_copy'), 'a'*100)

    files_hash = {
      p: hashlib.sha1(c).hexdigest() for p, c in files_content.items()
    }
    # 'a' and 'sub/c' are missing.
    missing = {
      files_hash[u'a']: u'a',
      files_hash[os.path.join(u'sub', u'c')]: os.path.join(u'sub', u'c'),
    }
    server_ref = isolate_storage.ServerRef(
        'http://localhost:1', 'some-namespace')
    storage_api = MockedStorageApi(server_ref, missing)
    storage = isolateserver.Storage(storage_api)
    with storage:
      results, cold, hot = isolateserver.archive_files_to_storage(
          storage, [os.path.join(self.tempdir, p) for p in files_content], None)
    self.assertEqual(
        {os.path.join(self.tempdir, f): h for f, h in files_hash.items()},
        dict(results))

    expected = [
      (os.path.join(self.tempdir, u'a'), files_hash['a']),
      (os.path.join(self.tempdir, u'sub', u'c'),
        files_hash[os.path.join(u'sub', u'c')]),
      (os.path.join(self.tempdir, u'sub', u'a_copy'),
        files_hash[os.path.join(u'sub', u'a_copy')]),
    ]
    self.assertEqual(expected, [(f.path, f.digest) for f in cold])
    self.assertEqual(
        [(os.path.join(self.tempdir, u'b'), files_hash['b'])],
        [(f.path, f.digest) for f in hot])
    # 'contains' checked for existence of all files.
    self.assertEqualIgnoringOrder(
        set(files_hash.values()),
        [i.digest for i in sum(storage_api.contains_calls, [])])
    # Pushed only missing files.
    self.assertEqualIgnoringOrder(
        list(missing),
        [call[0].digest for call in storage_api.push_calls])
    # Pushing with correct data, size and push state.
    for pushed_item, _push_state, pushed_content in storage_api.push_calls:
      filename = missing[pushed_item.digest]
      self.assertEqual(os.path.join(self.tempdir, filename), pushed_item.path)
      self.assertEqual(files_content[filename], pushed_content)
Beispiel #4
0
def delete_and_upload(storage, out_dir, leak_temp_dir):
  """Deletes the temporary run directory and uploads results back.

  Returns:
    tuple(outputs_ref, success, cold, hot)
    - outputs_ref: a dict referring to the results archived back to the isolated
          server, if applicable.
    - success: False if something occurred that means that the task must
          forcibly be considered a failure, e.g. zombie processes were left
          behind.
    - cold: list of size of cold items, they had to be uploaded.
    - hot: list of size of hot items, they didn't have to be uploaded.
  """

  # Upload out_dir and generate a .isolated file out of this directory. It is
  # only done if files were written in the directory.
  outputs_ref = None
  cold = []
  hot = []
  if fs.isdir(out_dir) and fs.listdir(out_dir):
    with tools.Profiler('ArchiveOutput'):
      try:
        results, f_cold, f_hot = isolateserver.archive_files_to_storage(
            storage, [out_dir], None)
        outputs_ref = {
          'isolated': results[0][0],
          'isolatedserver': storage.location,
          'namespace': storage.namespace,
        }
        cold = sorted(i.size for i in f_cold)
        hot = sorted(i.size for i in f_hot)
      except isolateserver.Aborted:
        # This happens when a signal SIGTERM was received while uploading data.
        # There is 2 causes:
        # - The task was too slow and was about to be killed anyway due to
        #   exceeding the hard timeout.
        # - The amount of data uploaded back is very large and took too much
        #   time to archive.
        sys.stderr.write('Received SIGTERM while uploading')
        # Re-raise, so it will be treated as an internal failure.
        raise
  try:
    if (not leak_temp_dir and fs.isdir(out_dir) and
        not file_path.rmtree(out_dir)):
      logging.error('Had difficulties removing out_dir %s', out_dir)
      return outputs_ref, False, cold, hot
  except OSError as e:
    # When this happens, it means there's a process error.
    logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e)
    return outputs_ref, False, cold, hot
  return outputs_ref, True, cold, hot
Beispiel #5
0
 def test_archive_files_to_storage_tar(self):
   # Create 5 files, which is the minimum to create a tarball.
   for i in range(5):
     with open(os.path.join(self.tempdir, six.text_type(i)), 'wb') as f:
       f.write('fooo%d' % i)
   server_ref = isolate_storage.ServerRef('http://localhost:1', 'default')
   storage_api = MockedStorageApi(server_ref, {})
   storage = isolateserver.Storage(storage_api)
   results, cold, hot = isolateserver.archive_files_to_storage(
       storage, [self.tempdir], None)
   self.assertEqual([self.tempdir], results.keys())
   self.assertEqual([], cold)
   # 5 files, the isolated file.
   self.assertEqual(6, len(hot))
def delete_and_upload(storage, out_dir, leak_temp_dir):
    """Deletes the temporary run directory and uploads results back.

  Returns:
    tuple(outputs_ref, success)
    - outputs_ref is a dict referring to the results archived back to the
      isolated server, if applicable.
    - success is False if something occurred that means that the task must
      forcibly be considered a failure, e.g. zombie processes were left behind.
  """

    # Upload out_dir and generate a .isolated file out of this directory. It is
    # only done if files were written in the directory.
    outputs_ref = None
    if os.path.isdir(out_dir) and os.listdir(out_dir):
        with tools.Profiler("ArchiveOutput"):
            try:
                results = isolateserver.archive_files_to_storage(storage, [out_dir], None)
                outputs_ref = {
                    "isolated": results[0][0],
                    "isolatedserver": storage.location,
                    "namespace": storage.namespace,
                }
            except isolateserver.Aborted:
                # This happens when a signal SIGTERM was received while uploading data.
                # There is 2 causes:
                # - The task was too slow and was about to be killed anyway due to
                #   exceeding the hard timeout.
                # - The amount of data uploaded back is very large and took too much
                #   time to archive.
                sys.stderr.write("Received SIGTERM while uploading")
                # Re-raise, so it will be treated as an internal failure.
                raise
    try:
        if not leak_temp_dir and os.path.isdir(out_dir) and not file_path.rmtree(out_dir):
            logging.error("Had difficulties removing out_dir %s", out_dir)
            return outputs_ref, False
    except OSError as e:
        # When this happens, it means there's a process error.
        logging.error("Had difficulties removing out_dir %s: %s", out_dir, e)
        return outputs_ref, False
    return outputs_ref, True
Beispiel #7
0
 def test_archive_files_to_storage_symlink(self):
   link_path = os.path.join(self.tempdir, u'link')
   with open(os.path.join(self.tempdir, u'foo'), 'wb') as f:
     f.write('fooo')
   fs.symlink('foo', link_path)
   server_ref = isolate_storage.ServerRef('http://localhost:1', 'default')
   storage_api = MockedStorageApi(server_ref, {})
   storage = isolateserver.Storage(storage_api)
   results, cold, hot = isolateserver.archive_files_to_storage(
       storage, [self.tempdir], None)
   self.assertEqual([self.tempdir], results.keys())
   self.assertEqual([], cold)
   # isolated, symlink, foo file.
   self.assertEqual(3, len(hot))
   self.assertEqual(os.path.join(self.tempdir, u'foo'), hot[0].path)
   self.assertEqual(4, hot[0].size)
   # TODO(maruel): The symlink is reported as its destination. We should fix
   # this because it double counts the stats.
   self.assertEqual(os.path.join(self.tempdir, u'foo'), hot[1].path)
   self.assertEqual(4, hot[1].size)
   # The isolated file is pure in-memory.
   self.assertIsInstance(hot[2], isolateserver.BufferItem)
def run_tha_test(isolated_hash, storage, cache, algo, extra_args):
  """Downloads the dependencies in the cache, hardlinks them into a temporary
  directory and runs the executable from there.

  A temporary directory is created to hold the output files. The content inside
  this directory will be uploaded back to |storage| packaged as a .isolated
  file.

  Arguments:
    isolated_hash: the sha-1 of the .isolated file that must be retrieved to
                   recreate the tree of files to run the target executable.
    storage: an isolateserver.Storage object to retrieve remote objects. This
             object has a reference to an isolateserver.StorageApi, which does
             the actual I/O.
    cache: an isolateserver.LocalCache to keep from retrieving the same objects
           constantly by caching the objects retrieved. Can be on-disk or
           in-memory.
    algo: an hashlib class to hash content. Usually hashlib.sha1.
    extra_args: optional arguments to add to the command stated in the .isolate
                file.
  """
  run_dir = make_temp_dir('run_tha_test', cache.cache_dir)
  out_dir = unicode(tempfile.mkdtemp(prefix='run_tha_test'))
  result = 0
  try:
    try:
      settings = isolateserver.fetch_isolated(
          isolated_hash=isolated_hash,
          storage=storage,
          cache=cache,
          algo=algo,
          outdir=run_dir,
          os_flavor=get_flavor(),
          require_command=True)
    except isolateserver.ConfigError as e:
      tools.report_error(e)
      result = 1
      return result

    change_tree_read_only(run_dir, settings.read_only)
    cwd = os.path.normpath(os.path.join(run_dir, settings.relative_cwd))
    command = settings.command + extra_args

    # subprocess.call doesn't consider 'cwd' when searching for executable.
    # Yet isolate can specify command relative to 'cwd'. Convert it to absolute
    # path if necessary.
    if not os.path.isabs(command[0]):
      command[0] = os.path.abspath(os.path.join(cwd, command[0]))
    command = process_command(command, out_dir)
    logging.info('Running %s, cwd=%s' % (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'):
        result = subprocess.call(command, cwd=cwd, env=env)
    except OSError as e:
      tools.report_error('Failed to run %s; cwd=%s: %s' % (command, cwd, e))
      result = 1

    # Upload out_dir and generate a .isolated file out of this directory. It is
    # only done if files were written in the directory.
    if os.listdir(out_dir):
      with tools.Profiler('ArchiveOutput'):
        results = isolateserver.archive_files_to_storage(
            storage, algo, [out_dir], None)
      # TODO(maruel): Implement side-channel to publish this information.
      print('run_isolated output: %s' % results[0][0])

  finally:
    try:
      rmtree(out_dir)
    finally:
      try:
        rmtree(run_dir)
      except OSError:
        logging.warning('Leaking %s', run_dir)
        # Swallow the exception so it doesn't generate an infrastructure error.
        #
        # It usually happens on Windows when a child process is not properly
        # terminated, usually because of a test case starting child processes
        # that time out. This causes files to be locked and it becomes
        # impossible to delete them.
        #
        # Only report an infrastructure error if the test didn't fail. This is
        # because a swarming bot will likely not reboot. This situation will
        # cause accumulation of temporary hardlink trees.
        if not result:
          raise
  return result
Beispiel #9
0
def run_tha_test(isolated_hash, storage, cache, leak_temp_dir, extra_args):
  """Downloads the dependencies in the cache, hardlinks them into a temporary
  directory and runs the executable from there.

  A temporary directory is created to hold the output files. The content inside
  this directory will be uploaded back to |storage| packaged as a .isolated
  file.

  Arguments:
    isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
                   recreate the tree of files to run the target executable.
    storage: an isolateserver.Storage object to retrieve remote objects. This
             object has a reference to an isolateserver.StorageApi, which does
             the actual I/O.
    cache: an isolateserver.LocalCache to keep from retrieving the same objects
           constantly by caching the objects retrieved. Can be on-disk or
           in-memory.
    leak_temp_dir: if true, the temporary directory will be deliberately leaked
                   for later examination.
    extra_args: optional arguments to add to the command stated in the .isolate
                file.
  """
  tmp_root = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
  run_dir = make_temp_dir(u'run_tha_test', tmp_root)
  out_dir = unicode(make_temp_dir(u'isolated_out', tmp_root))
  result = 0
  try:
    try:
      bundle = isolateserver.fetch_isolated(
          isolated_hash=isolated_hash,
          storage=storage,
          cache=cache,
          outdir=run_dir,
          require_command=True)
    except isolated_format.IsolatedError:
      on_error.report(None)
      return 1

    change_tree_read_only(run_dir, bundle.read_only)
    cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd))
    command = bundle.command + extra_args

    file_path.ensure_command_has_abs_path(command, cwd)
    command = process_command(command, out_dir)
    logging.info('Running %s, cwd=%s' % (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))
    sys.stdout.flush()
    with tools.Profiler('RunTest'):
      try:
        with subprocess42.Popen_with_handler(command, cwd=cwd, env=env) as p:
          p.communicate()
          result = p.returncode
      except OSError:
        on_error.report('Failed to run %s; cwd=%s' % (command, cwd))
        result = 1
    logging.info(
        'Command finished with exit code %d (%s)',
        result, hex(0xffffffff & result))
  finally:
    try:
      if leak_temp_dir:
        logging.warning('Deliberately leaking %s for later examination',
                        run_dir)
      else:
        try:
          if not file_path.rmtree(run_dir):
            print >> sys.stderr, (
                'Failed to delete the temporary directory, forcibly failing\n'
                'the task because of it. No zombie process can outlive a\n'
                'successful task run and still be marked as successful.\n'
                'Fix your stuff.')
            result = result or 1
        except OSError as exc:
          logging.error('Leaking run_dir %s: %s', run_dir, exc)
          result = 1

      # HACK(vadimsh): On Windows rmtree(run_dir) call above has
      # a synchronization effect: it finishes only when all task child processes
      # terminate (since a running process locks *.exe file). Examine out_dir
      # only after that call completes (since child processes may
      # write to out_dir too and we need to wait for them to finish).

      # Upload out_dir and generate a .isolated file out of this directory.
      # It is only done if files were written in the directory.
      if os.path.isdir(out_dir) and os.listdir(out_dir):
        with tools.Profiler('ArchiveOutput'):
          try:
            results = isolateserver.archive_files_to_storage(
                storage, [out_dir], None)
          except isolateserver.Aborted:
            # This happens when a signal SIGTERM was received while uploading
            # data. There is 2 causes:
            # - The task was too slow and was about to be killed anyway due to
            #   exceeding the hard timeout.
            # - The amount of data uploaded back is very large and took too much
            #   time to archive.
            #
            # There's 3 options to handle this:
            # - Ignore the upload failure as a silent failure. This can be
            #   detected client side by the fact no result file exists.
            # - Return as if the task failed. This is not factually correct.
            # - Return an internal failure. Sadly, it's impossible at this level
            #   at the moment.
            #
            # For now, silently drop the upload.
            #
            # In any case, the process only has a very short grace period so it
            # needs to exit right away.
            sys.stderr.write('Received SIGTERM while uploading')
            results = None

        if results:
          # TODO(maruel): Implement side-channel to publish this information.
          output_data = {
            'hash': results[0][0],
            'namespace': storage.namespace,
            'storage': storage.location,
          }
          sys.stdout.flush()
          print(
              '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
              tools.format_json(output_data, dense=True))

    finally:
      try:
        if os.path.isdir(out_dir) and not file_path.rmtree(out_dir):
          logging.error('Had difficulties removing out_dir %s', out_dir)
          result = result or 1
      except OSError as exc:
        # Only report on non-Windows or on Windows when the process had
        # succeeded. Due to the way file sharing works on Windows, it's sadly
        # expected that file deletion may fail when a test failed.
        logging.error('Failed to remove out_dir %s: %s', out_dir, exc)
        if sys.platform != 'win32' or not result:
          on_error.report(None)
        result = 1

  return result
Beispiel #10
0
def run_tha_test(isolated_hash, storage, cache, leak_temp_dir, extra_args):
  """Downloads the dependencies in the cache, hardlinks them into a temporary
  directory and runs the executable from there.

  A temporary directory is created to hold the output files. The content inside
  this directory will be uploaded back to |storage| packaged as a .isolated
  file.

  Arguments:
    isolated_hash: the SHA-1 of the .isolated file that must be retrieved to
                   recreate the tree of files to run the target executable.
    storage: an isolateserver.Storage object to retrieve remote objects. This
             object has a reference to an isolateserver.StorageApi, which does
             the actual I/O.
    cache: an isolateserver.LocalCache to keep from retrieving the same objects
           constantly by caching the objects retrieved. Can be on-disk or
           in-memory.
    leak_temp_dir: if true, the temporary directory will be deliberately leaked
                   for later examination.
    extra_args: optional arguments to add to the command stated in the .isolate
                file.
  """
  run_dir = make_temp_dir(u'run_tha_test', cache.cache_dir)
  out_dir = unicode(make_temp_dir(u'isolated_out', cache.cache_dir))
  result = 0
  try:
    try:
      bundle = isolateserver.fetch_isolated(
          isolated_hash=isolated_hash,
          storage=storage,
          cache=cache,
          outdir=run_dir,
          require_command=True)
    except isolated_format.IsolatedError:
      on_error.report(None)
      return 1

    change_tree_read_only(run_dir, bundle.read_only)
    cwd = os.path.normpath(os.path.join(run_dir, bundle.relative_cwd))
    command = bundle.command + extra_args

    file_path.ensure_command_has_abs_path(command, cwd)
    command = process_command(command, out_dir)
    logging.info('Running %s, cwd=%s' % (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))
    sys.stdout.flush()
    with tools.Profiler('RunTest'):
      try:
        with subprocess42.Popen_with_handler(command, cwd=cwd, env=env) as p:
          p.communicate()
          result = p.returncode
      except OSError:
        on_error.report('Failed to run %s; cwd=%s' % (command, cwd))
        result = 1
    logging.info(
        'Command finished with exit code %d (%s)',
        result, hex(0xffffffff & result))
  finally:
    try:
      if leak_temp_dir:
        logging.warning('Deliberately leaking %s for later examination',
                        run_dir)
      else:
        try:
          if not file_path.rmtree(run_dir):
            print >> sys.stderr, (
                'Failed to delete the temporary directory, forcibly failing\n'
                'the task because of it. No zombie process can outlive a\n'
                'successful task run and still be marked as successful.\n'
                'Fix your stuff.')
            result = result or 1
        except OSError:
          logging.warning('Leaking %s', run_dir)
          result = 1

      # HACK(vadimsh): On Windows rmtree(run_dir) call above has
      # a synchronization effect: it finishes only when all task child processes
      # terminate (since a running process locks *.exe file). Examine out_dir
      # only after that call completes (since child processes may
      # write to out_dir too and we need to wait for them to finish).

      # Upload out_dir and generate a .isolated file out of this directory.
      # It is only done if files were written in the directory.
      if os.path.isdir(out_dir) and os.listdir(out_dir):
        with tools.Profiler('ArchiveOutput'):
          results = isolateserver.archive_files_to_storage(
              storage, [out_dir], None)
        # TODO(maruel): Implement side-channel to publish this information.
        output_data = {
          'hash': results[0][0],
          'namespace': storage.namespace,
          'storage': storage.location,
        }
        sys.stdout.flush()
        print(
            '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
            tools.format_json(output_data, dense=True))

    finally:
      try:
        if os.path.isdir(out_dir) and not file_path.rmtree(out_dir):
          result = result or 1
      except OSError:
        # The error was already printed out. Report it but that's it. Only
        # report on non-Windows or on Windows when the process had succeeded.
        # Due to the way file sharing works on Windows, it's sadly expected that
        # file deletion may fail when a test failed.
        if sys.platform != 'win32' or not result:
          on_error.report(None)
        result = 1

  return result
def run_tha_test(isolated_hash, storage, cache, extra_args):
  """Downloads the dependencies in the cache, hardlinks them into a temporary
  directory and runs the executable from there.

  A temporary directory is created to hold the output files. The content inside
  this directory will be uploaded back to |storage| packaged as a .isolated
  file.

  Arguments:
    isolated_hash: the sha-1 of the .isolated file that must be retrieved to
                   recreate the tree of files to run the target executable.
    storage: an isolateserver.Storage object to retrieve remote objects. This
             object has a reference to an isolateserver.StorageApi, which does
             the actual I/O.
    cache: an isolateserver.LocalCache to keep from retrieving the same objects
           constantly by caching the objects retrieved. Can be on-disk or
           in-memory.
    extra_args: optional arguments to add to the command stated in the .isolate
                file.
  """
  run_dir = make_temp_dir('run_tha_test', cache.cache_dir)
  out_dir = unicode(make_temp_dir('isolated_out', cache.cache_dir))
  result = 0
  try:
    try:
      settings = isolateserver.fetch_isolated(
          isolated_hash=isolated_hash,
          storage=storage,
          cache=cache,
          outdir=run_dir,
          require_command=True)
    except isolateserver.ConfigError:
      on_error.report(None)
      return 1

    change_tree_read_only(run_dir, settings.read_only)
    cwd = os.path.normpath(os.path.join(run_dir, settings.relative_cwd))
    command = settings.command + extra_args

    # subprocess.call doesn't consider 'cwd' when searching for executable.
    # Yet isolate can specify command relative to 'cwd'. Convert it to absolute
    # path if necessary.
    if not os.path.isabs(command[0]):
      command[0] = os.path.abspath(os.path.join(cwd, command[0]))
    command = process_command(command, out_dir)
    logging.info('Running %s, cwd=%s' % (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:
      sys.stdout.flush()
      with tools.Profiler('RunTest'):
        result = subprocess.call(command, cwd=cwd, env=env)
        logging.info(
            'Command finished with exit code %d (%s)',
            result, hex(0xffffffff & result))
    except OSError:
      on_error.report('Failed to run %s; cwd=%s' % (command, cwd))
      result = 1

  finally:
    try:
      try:
        if not rmtree(run_dir):
          print >> sys.stderr, (
              'Failed to delete the temporary directory, forcibly failing the\n'
              'task because of it. No zombie process can outlive a successful\n'
              'task run and still be marked as successful. Fix your stuff.')
          result = result or 1
      except OSError:
        logging.warning('Leaking %s', run_dir)
        result = 1

      # HACK(vadimsh): On Windows rmtree(run_dir) call above has
      # a synchronization effect: it finishes only when all task child processes
      # terminate (since a running process locks *.exe file). Examine out_dir
      # only after that call completes (since child processes may
      # write to out_dir too and we need to wait for them to finish).

      # Upload out_dir and generate a .isolated file out of this directory.
      # It is only done if files were written in the directory.
      if os.listdir(out_dir):
        with tools.Profiler('ArchiveOutput'):
          results = isolateserver.archive_files_to_storage(
              storage, [out_dir], None)
        # TODO(maruel): Implement side-channel to publish this information.
        output_data = {
          'hash': results[0][0],
          'namespace': storage.namespace,
          'storage': storage.location,
        }
        sys.stdout.flush()
        print(
            '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
            tools.format_json(output_data, dense=True))

    finally:
      try:
        if os.path.isdir(out_dir) and not rmtree(out_dir):
          result = result or 1
      except OSError:
        # The error was already printed out. Report it but that's it.
        on_error.report(None)
        result = 1

  return result
Beispiel #12
0
def run_tha_test(isolated_hash, storage, cache, extra_args):
    """Downloads the dependencies in the cache, hardlinks them into a temporary
  directory and runs the executable from there.

  A temporary directory is created to hold the output files. The content inside
  this directory will be uploaded back to |storage| packaged as a .isolated
  file.

  Arguments:
    isolated_hash: the sha-1 of the .isolated file that must be retrieved to
                   recreate the tree of files to run the target executable.
    storage: an isolateserver.Storage object to retrieve remote objects. This
             object has a reference to an isolateserver.StorageApi, which does
             the actual I/O.
    cache: an isolateserver.LocalCache to keep from retrieving the same objects
           constantly by caching the objects retrieved. Can be on-disk or
           in-memory.
    extra_args: optional arguments to add to the command stated in the .isolate
                file.
  """
    run_dir = make_temp_dir("run_tha_test", cache.cache_dir)
    out_dir = unicode(make_temp_dir("isolated_out", cache.cache_dir))
    result = 0
    try:
        try:
            settings = isolateserver.fetch_isolated(
                isolated_hash=isolated_hash, storage=storage, cache=cache, outdir=run_dir, require_command=True
            )
        except isolateserver.ConfigError as e:
            tools.report_error(e)
            return 1

        change_tree_read_only(run_dir, settings.read_only)
        cwd = os.path.normpath(os.path.join(run_dir, settings.relative_cwd))
        command = settings.command + extra_args

        # subprocess.call doesn't consider 'cwd' when searching for executable.
        # Yet isolate can specify command relative to 'cwd'. Convert it to absolute
        # path if necessary.
        if not os.path.isabs(command[0]):
            command[0] = os.path.abspath(os.path.join(cwd, command[0]))
        command = process_command(command, out_dir)
        logging.info("Running %s, cwd=%s" % (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:
            sys.stdout.flush()
            with tools.Profiler("RunTest"):
                result = subprocess.call(command, cwd=cwd, env=env)
                logging.info("Command finished with exit code %d (%s)", result, hex(0xFFFFFFFF & result))
        except OSError as e:
            tools.report_error("Failed to run %s; cwd=%s: %s" % (command, cwd, e))
            result = 1

    finally:
        try:
            try:
                rmtree(run_dir)
            except OSError:
                logging.warning("Leaking %s", run_dir)
                # Swallow the exception so it doesn't generate an infrastructure error.
                #
                # It usually happens on Windows when a child process is not properly
                # terminated, usually because of a test case starting child processes
                # that time out. This causes files to be locked and it becomes
                # impossible to delete them.
                #
                # Only report an infrastructure error if the test didn't fail. This is
                # because a swarming bot will likely not reboot. This situation will
                # cause accumulation of temporary hardlink trees.
                if not result:
                    raise

            # HACK(vadimsh): On Windows rmtree(run_dir) call above has
            # a synchronization effect: it finishes only when all task child processes
            # terminate (since a running process locks *.exe file). Examine out_dir
            # only after that call completes (since child processes may
            # write to out_dir too and we need to wait for them to finish).

            # Upload out_dir and generate a .isolated file out of this directory.
            # It is only done if files were written in the directory.
            if os.listdir(out_dir):
                with tools.Profiler("ArchiveOutput"):
                    results = isolateserver.archive_files_to_storage(storage, [out_dir], None)
                # TODO(maruel): Implement side-channel to publish this information.
                output_data = {"hash": results[0][0], "namespace": storage.namespace, "storage": storage.location}
                sys.stdout.flush()
                print("[run_isolated_out_hack]%s[/run_isolated_out_hack]" % tools.format_json(output_data, dense=True))

        finally:
            rmtree(out_dir)

    return result
def run_tha_test(isolated_hash, storage, cache, extra_args):
  """Downloads the dependencies in the cache, hardlinks them into a temporary
  directory and runs the executable from there.

  A temporary directory is created to hold the output files. The content inside
  this directory will be uploaded back to |storage| packaged as a .isolated
  file.

  Arguments:
    isolated_hash: the sha-1 of the .isolated file that must be retrieved to
                   recreate the tree of files to run the target executable.
    storage: an isolateserver.Storage object to retrieve remote objects. This
             object has a reference to an isolateserver.StorageApi, which does
             the actual I/O.
    cache: an isolateserver.LocalCache to keep from retrieving the same objects
           constantly by caching the objects retrieved. Can be on-disk or
           in-memory.
    extra_args: optional arguments to add to the command stated in the .isolate
                file.
  """
  run_dir = make_temp_dir('run_tha_test', cache.cache_dir)
  out_dir = unicode(tempfile.mkdtemp(prefix='run_tha_test'))
  result = 0
  try:
    try:
      settings = isolateserver.fetch_isolated(
          isolated_hash=isolated_hash,
          storage=storage,
          cache=cache,
          outdir=run_dir,
          require_command=True)
    except isolateserver.ConfigError as e:
      tools.report_error(e)
      result = 1
      return result

    change_tree_read_only(run_dir, settings.read_only)
    cwd = os.path.normpath(os.path.join(run_dir, settings.relative_cwd))
    command = settings.command + extra_args

    # subprocess.call doesn't consider 'cwd' when searching for executable.
    # Yet isolate can specify command relative to 'cwd'. Convert it to absolute
    # path if necessary.
    if not os.path.isabs(command[0]):
      command[0] = os.path.abspath(os.path.join(cwd, command[0]))
    command = process_command(command, out_dir)
    logging.info('Running %s, cwd=%s' % (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'):
        result = subprocess.call(command, cwd=cwd, env=env)
    except OSError as e:
      tools.report_error('Failed to run %s; cwd=%s: %s' % (command, cwd, e))
      result = 1

    # Upload out_dir and generate a .isolated file out of this directory. It is
    # only done if files were written in the directory.
    if os.listdir(out_dir):
      with tools.Profiler('ArchiveOutput'):
        results = isolateserver.archive_files_to_storage(
            storage, [out_dir], None)
      # TODO(maruel): Implement side-channel to publish this information.
      output_data = {
        'hash': results[0][0],
        'namespace': storage.namespace,
        'storage': storage.location,
      }
      sys.stdout.flush()
      sys.stderr.flush()
      print(
          '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
          tools.format_json(output_data, dense=True))

  finally:
    try:
      rmtree(out_dir)
    finally:
      try:
        rmtree(run_dir)
      except OSError:
        logging.warning('Leaking %s', run_dir)
        # Swallow the exception so it doesn't generate an infrastructure error.
        #
        # It usually happens on Windows when a child process is not properly
        # terminated, usually because of a test case starting child processes
        # that time out. This causes files to be locked and it becomes
        # impossible to delete them.
        #
        # Only report an infrastructure error if the test didn't fail. This is
        # because a swarming bot will likely not reboot. This situation will
        # cause accumulation of temporary hardlink trees.
        if not result:
          raise
  return result