def test_works(self):
        with open(self.path, 'w') as f:
            json.dump({'A': 'a'}, f)

        r = file_reader.FileReaderThread(self.path, 0.1)
        r.start()
        try:
            self.assertEqual(r.last_value, {'A': 'a'})

            # Change the file. Expect possible conflict on Windows.
            attempt = 0
            bytes_json = json.dumps({'B': 'b'}).encode('utf-8')
            while True:
                try:
                    file_path.atomic_replace(self.path, bytes_json)
                    break
                except OSError:
                    attempt += 1
                    if attempt == 20:
                        self.fail('Cannot replace the file, giving up')
                    time.sleep(0.05)

            # Give some reasonable time for the reader thread to pick up the change.
            # This test will flake if for whatever reason OS thread scheduler is
            # lagging for more than 2 seconds.
            time.sleep(2)

            self.assertEqual(r.last_value, {'B': 'b'})
        finally:
            r.stop()
Esempio n. 2
0
    def start(self):
        """Grabs initial bot auth headers and starts all auth related threads.

    Raises:
      AuthSystemError on fatal errors.
    """
        assert not self._auth_params_reader, 'already running'
        try:
            # Read headers more often than bot_main writes them (which is 60 sec), to
            # reduce maximum possible latency between header updates and reads. Use
            # interval that isn't a divisor of 60 to avoid reads and writes happening
            # at the same moment in time.
            reader = file_reader.FileReaderThread(self._auth_params_file,
                                                  interval_sec=53)
            reader.start()
        except file_reader.FatalReadError as e:
            raise AuthSystemError('Cannot start FileReaderThread: %s' % e)

        # Initial validation.
        try:
            params = process_auth_params_json(reader.last_value)
        except ValueError as e:
            reader.stop()
            raise AuthSystemError('Cannot parse bot_auth_params.json: %s' % e)

        # If using task auth, launch local HTTP server that serves tokens (let OS
        # assign the port).
        server = None
        local_auth_context = None
        if params.task_service_account != 'none':
            try:
                server = auth_server.LocalAuthServer()
                local_auth_context = server.start(token_provider=self)
            except Exception as exc:
                reader.stop()  # cleanup
                raise AuthSystemError(
                    'Failed to start local auth server - %s' % exc)

        # Good to go.
        with self._lock:
            self._auth_params_reader = reader
            self._local_server = server
            self._local_auth_context = local_auth_context
    def test_works_with_file_refresher(self):
        val = 0

        w = file_refresher.FileRefresherThread(self.path, lambda: {'val': val},
                                               0.1)
        r = file_reader.FileReaderThread(self.path, 0.1)

        try:
            w.start()
            r.start()
            self.assertEqual(r.last_value, {'val': 0})

            val = 123

            # Give some reasonable time for the reader thread to pick up the change.
            # This test will flake if for whatever reason OS thread scheduler is
            # lagging for more than 2 seconds.
            time.sleep(2)

            self.assertEqual(r.last_value, {'val': 123})

        finally:
            w.stop()
            r.stop()
 def test_start_throws_on_error(self):
     r = file_reader.FileReaderThread(self.path + "_no_such_file",
                                      max_attempts=2)
     with self.assertRaises(file_reader.FatalReadError):
         r.start()
Esempio n. 5
0
  def start(self):
    """Grabs initial bot auth headers and starts all auth related threads.

    If the task is configured to use service accounts (based on data in
    'auth_params_file'), launches the local auth service and returns a dict that
    contains its parameters. It can be placed into LUCI_CONTEXT['local_auth']
    slot.

    Sets default service account (to be used by Swarming internal processes,
    like run_isolated.py) to 'system' (or unsets it if the bot has no associated
    service account). run_isolated.py eventually switches the default account to
    'task' before launching the actual user-supplied code.

    If task is not using service accounts, returns None (meaning, there's no
    need to setup LUCI_CONTEXT['local_auth'] at all).

    Format of the returned dict:
    {
      'rpc_port': <int with port number>,
      'secret': <str with a random string to send with RPCs>,
      'accounts': [{'id': <str>}, ...],
      'default_account_id': <str> or None
    }

    Raises:
      AuthSystemError on fatal errors.
    """
    assert not self._auth_params_reader, 'already running'
    try:
      # Read headers more often than bot_main writes them (which is 15 sec), to
      # reduce maximum possible latency between header updates and reads.
      #
      # TODO(vadimsh): Replace this with real IPC, like local sockets.
      reader = file_reader.FileReaderThread(
          self._auth_params_file, interval_sec=10)
      reader.start()
    except file_reader.FatalReadError as e:
      raise AuthSystemError('Cannot start FileReaderThread: %s' % e)

    # Initial validation.
    try:
      params = process_auth_params_json(reader.last_value)
    except ValueError as e:
      reader.stop()
      raise AuthSystemError('Cannot parse bot_auth_params.json: %s' % e)

    logging.info('Using following service accounts:')
    logging.info('  system: %s', params.system_service_account)
    logging.info('  task:   %s', params.task_service_account)

    bot_email = '-'
    if params.bot_service_account != 'none':
      logging.info('The bot itself runs as %s', params.bot_service_account)
      bot_email = params.bot_service_account

    available_accounts = []
    def add_account(account_id, email):
      if email == 'bot':
        email = bot_email
      available_accounts.append(auth_server.Account(id=account_id, email=email))

    # Expose all defined accounts (if any) to subprocesses via LUCI_CONTEXT.
    #
    # Use 'system' logical account as default for internal Swarming processes.
    # It is specified by 'system_service_account' field in bots.cfg. Swarming
    # will eventually switch to 'task' logical account before launching
    # user-supplied code. 'task' account is specified in the task definition.
    # This happens in run_isolated.py.
    #
    # If 'system_service_account' is not defined, then do not set default
    # account at all! It means internal Swarming processes will use
    # non-authenticated calls (which is precisely the meaning of un-set
    # system account).
    default_account_id = None
    if params.system_service_account != 'none':
      default_account_id = 'system'
      add_account('system', params.system_service_account)
    if params.task_service_account != 'none':
      add_account('task', params.task_service_account)

    # If using service accounts, launch local HTTP server that serves tokens
    # (let OS assign the port).
    server = None
    local_auth_context = None
    if available_accounts:
      server = auth_server.LocalAuthServer()
      local_auth_context = server.start(
          token_provider=self,
          accounts=available_accounts,
          default_account_id=default_account_id)

    # Good to go.
    with self._lock:
      self._auth_params_reader = reader
      self._local_server = server
    return local_auth_context