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()
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()
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