def init_auth_system(self, auth_params): self.assertIsNone( self.auth_sys) # allowed to be called only once per test params_path = os.path.join(self.tmp_dir, 'auth_params.json') with open(params_path, 'w') as f: json.dump(auth_params._asdict(), f) self.auth_sys = bot_auth.AuthSystem(params_path) return self.auth_sys.start()
def test_no_auth_works(self): # If 'task_service_account' is empty, doesn't launch local HTTP server. auth_params_path = self.write_auth_params({ 'swarming_http_headers': { 'Authorization': 'Bearer bot-own-token', }, 'task_service_account': 'none', }) with bot_auth.AuthSystem(auth_params_path) as auth_sys: self.assertIsNone(auth_sys.local_auth_context)
def test_bot_auth_works(self): # If 'task_service_account' is 'bot', local HTTP server returns bot tokens. auth_params_path = self.write_auth_params({ 'swarming_http_headers': { 'Authorization': 'Bearer bot-own-token', }, 'task_service_account': 'bot', }) with bot_auth.AuthSystem(auth_params_path) as auth_sys: self.assertEqual( {'Authorization': 'Bearer bot-own-token'}, auth_sys.bot_headers) self.assertEqual( ['rpc_port', 'secret'], sorted(auth_sys.local_auth_context)) # Try to actually use the local RPC service to grab a token. resp = call_rpc(auth_sys.local_auth_context, ['A', 'B', 'C']) self.assertEqual([u'access_token', u'expiry'], sorted(resp)) self.assertEqual(u'bot-own-token', resp['access_token']) self.assertGreater(resp['expiry'], time.time())
def load_and_run( in_file, swarming_server, is_grpc, cost_usd_hour, start, out_file, run_isolated_flags, bot_file, auth_params_file): """Loads the task's metadata, prepares auth environment and executes the task. This may throw all sorts of exceptions in case of failure. It's up to the caller to trap them. These shall be considered 'internal_failure' instead of 'failure' from a TaskRunResult standpoint. """ auth_system = None local_auth_context = None task_result = None work_dir = os.path.dirname(out_file) def handler(sig, _): logging.info('Got signal %s', sig) raise ExitSignal(sig) try: with subprocess42.set_signal_handler([SIG_BREAK_OR_TERM], handler): # The work directory is guaranteed to exist since it was created by # bot_main.py and contains the manifest. Temporary files will be # downloaded there. It's bot_main.py that will delete the directory # afterward. Tests are not run from there. if not os.path.isdir(work_dir): raise InternalError('%s expected to exist' % work_dir) # Raises InternalError on errors. task_details = TaskDetails.load(in_file) # This will start a thread that occasionally reads bot authentication # headers from 'auth_params_file'. It will also optionally launch local # HTTP server that serves OAuth tokens to the task processes. We put # location of this service into a file referenced by LUCI_CONTEXT env var # below. if auth_params_file: try: auth_system = bot_auth.AuthSystem(auth_params_file) local_auth_context = auth_system.start() except bot_auth.AuthSystemError as e: raise InternalError('Failed to init auth: %s' % e) # Override LUCI_CONTEXT['local_auth']. If the task is not using auth, # do NOT inherit existing local_auth (if its there). Kick it out by # passing None. context_edits = { 'local_auth': local_auth_context } # Extend existing LUCI_CONTEXT['swarming'], if any. if task_details.secret_bytes is not None: swarming = luci_context.read('swarming') or {} swarming['secret_bytes'] = task_details.secret_bytes context_edits['swarming'] = swarming # Returns bot authentication headers dict or raises InternalError. def headers_cb(): try: if auth_system: return auth_system.get_bot_headers() return (None, None) # A timeout of "None" means "don't use auth" except bot_auth.AuthSystemError as e: raise InternalError('Failed to grab bot auth headers: %s' % e) # Make a client that can send request to Swarming using bot auth headers. grpc_proxy = '' if is_grpc: grpc_proxy = swarming_server swarming_server = '' # The hostname and work dir provided here don't really matter, since the # task runner is always called with a specific versioned URL. remote = remote_client.createRemoteClient( swarming_server, headers_cb, os_utilities.get_hostname_short(), work_dir, grpc_proxy) remote.initialize() # Let AuthSystem know it can now send RPCs to Swarming (to grab OAuth # tokens). There's a circular dependency here! AuthSystem will be # indirectly relying on its own 'get_bot_headers' method to authenticate # RPCs it sends through the provided client. if auth_system: auth_system.set_remote_client(remote) # Auth environment is up, start the command. task_result is dumped to # disk in 'finally' block. with luci_context.stage(_tmpdir=work_dir, **context_edits) as ctx_file: task_result = run_command( remote, task_details, work_dir, cost_usd_hour, start, run_isolated_flags, bot_file, ctx_file) except (ExitSignal, InternalError, remote_client.InternalError) as e: # This normally means run_command() didn't get the chance to run, as it # itself traps exceptions and will report accordingly. In this case, we want # the parent process to send the message instead. if not task_result: task_result = { u'exit_code': -1, u'hard_timeout': False, u'io_timeout': False, u'must_signal_internal_failure': str(e.message or 'unknown error'), u'version': OUT_VERSION, } finally: # We've found tests to delete the working directory work_dir when quitting, # causing an exception here. Try to recreate the directory if necessary. if not os.path.isdir(work_dir): os.mkdir(work_dir) if auth_system: auth_system.stop() with open(out_file, 'wb') as f: json.dump(task_result, f)
def load_and_run(in_file, swarming_server, cost_usd_hour, start, out_file, min_free_space, bot_file, auth_params_file): """Loads the task's metadata, prepares auth environment and executes the task. This may throw all sorts of exceptions in case of failure. It's up to the caller to trap them. These shall be considered 'internal_failure' instead of 'failure' from a TaskRunResult standpoint. """ auth_system = None task_result = None work_dir = os.path.dirname(out_file) def handler(sig, _): logging.info('Got signal %s', sig) raise ExitSignal(sig) try: with subprocess42.set_signal_handler([SIG_BREAK_OR_TERM], handler): # The work directory is guaranteed to exist since it was created by # bot_main.py and contains the manifest. Temporary files will be # downloaded there. It's bot_main.py that will delete the directory # afterward. Tests are not run from there. if not os.path.isdir(work_dir): raise InternalError('%s expected to exist' % work_dir) # Raises InternalError on errors. task_details = TaskDetails.load(in_file) # This will start a thread that occasionally reads bot authentication # headers from 'auth_params_file'. It will also optionally launch local # HTTP server that serves OAuth tokens to the task processes. We put # location of this service into a file referenced by LUCI_CONTEXT env var # below. if auth_params_file: try: auth_system = bot_auth.AuthSystem(auth_params_file) auth_system.start() except bot_auth.AuthSystemError as e: raise InternalError('Failed to init auth: %s' % e) context_edits = {} # If the task is using service accounts, add local_auth details to # LUCI_CONTEXT. if auth_system and auth_system.local_auth_context: context_edits['local_auth'] = auth_system.local_auth_context # Returns bot authentication headers dict or raises InternalError. def headers_cb(): try: if auth_system: # The second parameter is the time until which the remote client # should cache the headers. Since auth_system is doing the # caching, we're just sending "0", which is to say the Epoch # (Jan 1 1970), which effectively means "never cache." return (auth_system.bot_headers, 0) return (None, None ) # A timeout of "None" means "don't use auth" except bot_auth.AuthSystemError as e: raise InternalError('Failed to grab bot auth headers: %s' % e) # Auth environment is up, start the command. task_result is dumped to # disk in 'finally' block. remote = remote_client.createRemoteClient(swarming_server, headers_cb) with luci_context.write(_tmpdir=work_dir, **context_edits): task_result = run_command(remote, task_details, work_dir, cost_usd_hour, start, min_free_space, bot_file) except (ExitSignal, InternalError) as e: # This normally means run_command() didn't get the chance to run, as it # itself traps exceptions and will report accordingly. In this case, we want # the parent process to send the message instead. if not task_result: task_result = { u'exit_code': -1, u'hard_timeout': False, u'io_timeout': False, u'must_signal_internal_failure': str(e.message or 'unknown error'), u'version': OUT_VERSION, } finally: # We've found tests to delete the working directory work_dir when quitting, # causing an exception here. Try to recreate the directory if necessary. if not os.path.isdir(work_dir): os.mkdir(work_dir) if auth_system: auth_system.stop() with open(out_file, 'wb') as f: json.dump(task_result, f)