Repository: aurora Updated Branches: refs/heads/master 181f38207 -> 9a2bbded5
Don't retry API requests that fail with auth errors. Bugs closed: AURORA-1248 Reviewed at https://reviews.apache.org/r/33705/ Project: http://git-wip-us.apache.org/repos/asf/aurora/repo Commit: http://git-wip-us.apache.org/repos/asf/aurora/commit/9a2bbded Tree: http://git-wip-us.apache.org/repos/asf/aurora/tree/9a2bbded Diff: http://git-wip-us.apache.org/repos/asf/aurora/diff/9a2bbded Branch: refs/heads/master Commit: 9a2bbded5aeee78c74984d8aca84324ba66b0779 Parents: 181f382 Author: Bill Farner <[email protected]> Authored: Mon May 4 10:57:28 2015 -0700 Committer: Bill Farner <[email protected]> Committed: Mon May 4 10:57:28 2015 -0700 ---------------------------------------------------------------------- .../aurora/client/api/scheduler_client.py | 8 ++- .../python/apache/aurora/client/cli/__init__.py | 1 + .../python/apache/aurora/client/cli/context.py | 44 ++++++++++-- .../python/apache/aurora/common/transport.py | 18 +++-- .../aurora/client/api/test_scheduler_client.py | 76 +++++++++++--------- .../apache/aurora/client/cli/test_context.py | 31 +++++++- .../apache/aurora/common/test_transport.py | 28 ++++++-- .../sh/org/apache/aurora/e2e/test_end_to_end.sh | 26 ++++--- 8 files changed, 165 insertions(+), 67 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/main/python/apache/aurora/client/api/scheduler_client.py ---------------------------------------------------------------------- diff --git a/src/main/python/apache/aurora/client/api/scheduler_client.py b/src/main/python/apache/aurora/client/api/scheduler_client.py index 3f9c691..2b8047c 100644 --- a/src/main/python/apache/aurora/client/api/scheduler_client.py +++ b/src/main/python/apache/aurora/client/api/scheduler_client.py @@ -214,7 +214,7 @@ class SchedulerProxy(object): class Error(Exception): pass class TimeoutError(Error): pass class TransientError(Error): pass - class AuthenticationError(Error): pass + class AuthError(Error): pass class APIVersionError(Error): pass class ThriftInternalError(Error): pass @@ -258,7 +258,7 @@ class SchedulerProxy(object): try: return self._session_key_factory(self.cluster.auth_mechanism) except SessionKeyError as e: - raise self.AuthenticationError('Unable to create session key %s' % e) + raise self.AuthError('Unable to create session key %s' % e) def _construct_scheduler(self): """ @@ -281,7 +281,7 @@ class SchedulerProxy(object): # turn any auth module exception into an auth error. log.debug('Warning: got an unknown exception during authentication:') log.debug(traceback.format_exc()) - raise self.AuthenticationError('Error connecting to scheduler: %s' % e) + raise self.AuthError('Error connecting to scheduler: %s' % e) if not self._client: raise self.TimeoutError('Timed out trying to connect to scheduler at %s' % self.cluster.name) @@ -313,6 +313,8 @@ class SchedulerProxy(object): raise self.APIVersionError("Client Version: %s, Server Version: %s" % (THRIFT_API_VERSION, resp.serverInfo.thriftAPIVersion)) return resp + except TRequestsTransport.AuthError as e: + raise self.AuthError(e) except (TTransport.TTransportException, self.TimeoutError, self.TransientError) as e: if not self._terminating.is_set(): log.warning('Connection error with scheduler: %s, reconnecting...' % e) http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/main/python/apache/aurora/client/cli/__init__.py ---------------------------------------------------------------------- diff --git a/src/main/python/apache/aurora/client/cli/__init__.py b/src/main/python/apache/aurora/client/cli/__init__.py index 10d9432..1208846 100644 --- a/src/main/python/apache/aurora/client/cli/__init__.py +++ b/src/main/python/apache/aurora/client/cli/__init__.py @@ -55,6 +55,7 @@ EXIT_NETWORK_ERROR = 7 EXIT_TIMEOUT = 9 EXIT_API_ERROR = 10 EXIT_UNKNOWN_ERROR = 20 +EXIT_AUTH_ERROR = 30 try: http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/main/python/apache/aurora/client/cli/context.py ---------------------------------------------------------------------- diff --git a/src/main/python/apache/aurora/client/cli/context.py b/src/main/python/apache/aurora/client/cli/context.py index adbffc4..146960e 100644 --- a/src/main/python/apache/aurora/client/cli/context.py +++ b/src/main/python/apache/aurora/client/cli/context.py @@ -14,14 +14,16 @@ from __future__ import print_function +import functools import logging from fnmatch import fnmatch -from apache.aurora.client.api import AuroraClientAPI +from apache.aurora.client.api import AuroraClientAPI, SchedulerProxy from apache.aurora.client.base import AURORA_V2_USER_AGENT_NAME, combine_messages from apache.aurora.client.cli import ( Context, EXIT_API_ERROR, + EXIT_AUTH_ERROR, EXIT_COMMAND_FAILURE, EXIT_INVALID_CONFIGURATION, EXIT_INVALID_PARAMETER @@ -31,10 +33,42 @@ from apache.aurora.client.hooks.hooked_api import HookedAuroraClientAPI from apache.aurora.common.aurora_job_key import AuroraJobKey from apache.aurora.common.clusters import CLUSTERS +from gen.apache.aurora.api import AuroraAdmin from gen.apache.aurora.api.constants import ACTIVE_STATES from gen.apache.aurora.api.ttypes import ResponseCode +class AuthErrorHandlingScheduler(object): + """A decorator that can be applied on a AuroraClientAPI instance to add handling of + auth-related errors, terminating the client.""" + + def __init__(self, delegate): + self._delegate = delegate + + def __getattr__(self, method_name): + try: + method = getattr(AuroraAdmin.Client, method_name) + except AttributeError: + # Don't interfere with the non-public API. + return getattr(self._delegate, method_name) + if not callable(method): + return method + + @functools.wraps(method) + def method_wrapper(*args, **kwargs): + try: + return getattr(self._delegate, method_name)(*args, **kwargs) + except SchedulerProxy.AuthError as e: + raise Context.CommandError(EXIT_AUTH_ERROR, str(e)) + + return method_wrapper + + +def add_auth_error_handler(api): + api._scheduler_proxy = AuthErrorHandlingScheduler(api._scheduler_proxy) + return api + + class AuroraCommandContext(Context): LOCK_ERROR_MSG = """Error: job is locked by an incomplete update. @@ -50,21 +84,21 @@ class AuroraCommandContext(Context): self.apis = {} self.unhooked_apis = {} - def get_api(self, cluster, enable_hooks=True): + def get_api(self, cluster, enable_hooks=True, clusters=CLUSTERS): """Gets an API object for a specified cluster Keeps the API handle cached, so that only one handle for each cluster will be created in a session. """ - if cluster not in CLUSTERS: + if cluster not in clusters: raise self.CommandError(EXIT_INVALID_CONFIGURATION, "Unknown cluster: %s" % cluster) apis = self.apis if enable_hooks else self.unhooked_apis base_class = HookedAuroraClientAPI if enable_hooks else AuroraClientAPI if cluster not in apis: - api = base_class(CLUSTERS[cluster], AURORA_V2_USER_AGENT_NAME, verbose=True) + api = base_class(clusters[cluster], AURORA_V2_USER_AGENT_NAME, verbose=True) apis[cluster] = api - return apis[cluster] + return add_auth_error_handler(apis[cluster]) def get_job_config(self, jobkey, config_file): """Loads a job configuration from a config file.""" http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/main/python/apache/aurora/common/transport.py ---------------------------------------------------------------------- diff --git a/src/main/python/apache/aurora/common/transport.py b/src/main/python/apache/aurora/common/transport.py index eefe8f7..909021a 100644 --- a/src/main/python/apache/aurora/common/transport.py +++ b/src/main/python/apache/aurora/common/transport.py @@ -37,6 +37,10 @@ def default_requests_session_factory(): class TRequestsTransport(TTransportBase): """A Thrift HTTP client based upon the requests module.""" + class AuthError(Exception): + """Indicates that a request failed due to an authentication or authorization problem. """ + pass + def __init__( self, uri, @@ -57,7 +61,7 @@ class TRequestsTransport(TTransportBase): :keyword auth: The requests authentication context :type auth: requests.auth.AuthBase :keyword session_factory: A callable that returns a requests session - :type session_factory: requests.Session + :type session_factory: () -> requests.Session :keyword user_agent: The value to use for the User-Agent header :type user_agent: str """ @@ -119,7 +123,6 @@ class TRequestsTransport(TTransportBase): self._session.headers['Content-Length'] = str(len(data)) self._session.headers['Host'] = self.__urlparse.hostname - response = None try: response = self._session.post( self.__uri, @@ -127,17 +130,18 @@ class TRequestsTransport(TTransportBase): timeout=self.__timeout, auth=self.__auth) response.raise_for_status() + self.__rbuf = BytesIO(response.content) except request_exceptions.Timeout: raise TTransportException( type=TTransportException.TIMED_OUT, message='Timed out talking to %s' % self.__uri) except request_exceptions.RequestException as e: - if response: - log.debug('Error connecting, logging response headers:.') - for field_name, field_value in response.headers.items(): + if e.response is not None: + log.debug('Request failed, response headers:') + for field_name, field_value in e.response.headers.items(): log.debug(' %s: %s' % (field_name, field_value)) + if e.response.status_code in (401, 403): + raise self.AuthError(e) raise TTransportException( type=TTransportException.UNKNOWN, message='Unknown error talking to %s: %s' % (self.__uri, e)) - - self.__rbuf = BytesIO(response.content) http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/test/python/apache/aurora/client/api/test_scheduler_client.py ---------------------------------------------------------------------- diff --git a/src/test/python/apache/aurora/client/api/test_scheduler_client.py b/src/test/python/apache/aurora/client/api/test_scheduler_client.py index 9319728..10e8ebb 100644 --- a/src/test/python/apache/aurora/client/api/test_scheduler_client.py +++ b/src/test/python/apache/aurora/client/api/test_scheduler_client.py @@ -67,15 +67,14 @@ def test_coverage(): 'No test defined for RPC %s' % rpc_name) +SESSION = SessionKey(mechanism='test', data='test') + + class TestSchedulerProxy(scheduler_client.SchedulerProxy): """In testing we shouldn't use the real SSHAgentAuthenticator.""" def session_key(self): - return self.create_session('SOME_USER') - - @classmethod - def create_session(cls, user): - return SessionKey(mechanism='test', data='test') + return SESSION class TestSchedulerProxyInjection(unittest.TestCase): @@ -100,14 +99,14 @@ class TestSchedulerProxyInjection(unittest.TestCase): return TestSchedulerProxy(Cluster(name='local')) def test_startCronJob(self): - self.mock_thrift_client.startCronJob(IsA(JobKey), IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.startCronJob(IsA(JobKey), SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().startCronJob(JOB_KEY) def test_createJob(self): self.mock_thrift_client.createJob( IsA(JobConfiguration), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().createJob(JobConfiguration()) @@ -115,21 +114,21 @@ class TestSchedulerProxyInjection(unittest.TestCase): self.mock_thrift_client.replaceCronTemplate( IsA(JobConfiguration), IsA(Lock), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().replaceCronTemplate(JobConfiguration(), Lock()) def test_scheduleCronJob(self): self.mock_thrift_client.scheduleCronJob( IsA(JobConfiguration), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().scheduleCronJob(JobConfiguration()) def test_descheduleCronJob(self): self.mock_thrift_client.descheduleCronJob( IsA(JobKey), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().descheduleCronJob(JOB_KEY) @@ -142,7 +141,7 @@ class TestSchedulerProxyInjection(unittest.TestCase): self.mock_thrift_client.restartShards( IsA(JobKey), IgnoreArg(), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().restartShards(JOB_KEY, set([0])) @@ -157,7 +156,7 @@ class TestSchedulerProxyInjection(unittest.TestCase): self.make_scheduler_proxy().getJobs(ROLE) def test_killTasks(self): - self.mock_thrift_client.killTasks(IsA(TaskQuery), IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.killTasks(IsA(TaskQuery), SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().killTasks(TaskQuery()) @@ -178,12 +177,12 @@ class TestSchedulerProxyInjection(unittest.TestCase): IsA(JobKey), IgnoreArg(), IsA(Lock), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().addInstances(JobKey(), {}, Lock()) def test_acquireLock(self): - self.mock_thrift_client.acquireLock(IsA(Lock), IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.acquireLock(IsA(Lock), SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().acquireLock(Lock()) @@ -191,7 +190,7 @@ class TestSchedulerProxyInjection(unittest.TestCase): self.mock_thrift_client.releaseLock( IsA(Lock), IsA(LockValidation), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().releaseLock(Lock(), LockValidation()) @@ -208,55 +207,62 @@ class TestSchedulerProxyInjection(unittest.TestCase): def test_startJobUpdate(self): self.mock_thrift_client.startJobUpdate( IsA(JobUpdateRequest), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().startJobUpdate(JobUpdateRequest()) def test_pauseJobUpdate(self): - self.mock_thrift_client.pauseJobUpdate('update_id', IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.pauseJobUpdate('update_id', SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().pauseJobUpdate('update_id') def test_resumeJobUpdate(self): self.mock_thrift_client.resumeJobUpdate( 'update_id', - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().resumeJobUpdate('update_id') def test_abortJobUpdate(self): - self.mock_thrift_client.abortJobUpdate('update_id', IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.abortJobUpdate('update_id', SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().abortJobUpdate('update_id') def test_pulseJobUpdate(self): - self.mock_thrift_client.pulseJobUpdate('update_id', IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.pulseJobUpdate('update_id', SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().pulseJobUpdate('update_id') + def test_raise_auth_error(self): + self.mock_thrift_client.killTasks(TaskQuery(), None, None, SESSION).AndRaise( + TRequestsTransport.AuthError()) + self.mox.ReplayAll() + with pytest.raises(scheduler_client.SchedulerProxy.AuthError): + self.make_scheduler_proxy().killTasks(TaskQuery(), None, None) + class TestSchedulerProxyAdminInjection(TestSchedulerProxyInjection): def test_startMaintenance(self): self.mock_thrift_client.startMaintenance( IsA(Hosts), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().startMaintenance(Hosts()) def test_drainHosts(self): - self.mock_thrift_client.drainHosts(IsA(Hosts), IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.drainHosts(IsA(Hosts), SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().drainHosts(Hosts()) def test_maintenanceStatus(self): self.mock_thrift_client.maintenanceStatus( IsA(Hosts), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().maintenanceStatus(Hosts()) def test_endMaintenance(self): - self.mock_thrift_client.endMaintenance(IsA(Hosts), IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.endMaintenance(IsA(Hosts), SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().endMaintenance(Hosts()) @@ -264,7 +270,7 @@ class TestSchedulerProxyAdminInjection(TestSchedulerProxyInjection): self.mock_thrift_client.setQuota( IgnoreArg(), IsA(ResourceAggregate), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().setQuota(ROLE, ResourceAggregate()) @@ -272,60 +278,60 @@ class TestSchedulerProxyAdminInjection(TestSchedulerProxyInjection): self.mock_thrift_client.forceTaskState( 'taskid', IgnoreArg(), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().forceTaskState('taskid', ScheduleStatus.LOST) def test_performBackup(self): - self.mock_thrift_client.performBackup(IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.performBackup(SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().performBackup() def test_listBackups(self): - self.mock_thrift_client.listBackups(IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.listBackups(SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().listBackups() def test_stageRecovery(self): self.mock_thrift_client.stageRecovery( IsA(TaskQuery), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().stageRecovery(TaskQuery()) def test_queryRecovery(self): self.mock_thrift_client.queryRecovery( IsA(TaskQuery), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().queryRecovery(TaskQuery()) def test_deleteRecoveryTasks(self): self.mock_thrift_client.deleteRecoveryTasks( IsA(TaskQuery), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().deleteRecoveryTasks(TaskQuery()) def test_commitRecovery(self): - self.mock_thrift_client.commitRecovery(IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.commitRecovery(SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().commitRecovery() def test_unloadRecovery(self): - self.mock_thrift_client.unloadRecovery(IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.unloadRecovery(SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().unloadRecovery() def test_snapshot(self): - self.mock_thrift_client.snapshot(IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + self.mock_thrift_client.snapshot(SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().snapshot() def test_rewriteConfigs(self): self.mock_thrift_client.rewriteConfigs( IsA(RewriteConfigsRequest), - IsA(SessionKey)).AndReturn(DEFAULT_RESPONSE) + SESSION).AndReturn(DEFAULT_RESPONSE) self.mox.ReplayAll() self.make_scheduler_proxy().rewriteConfigs(RewriteConfigsRequest()) http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/test/python/apache/aurora/client/cli/test_context.py ---------------------------------------------------------------------- diff --git a/src/test/python/apache/aurora/client/cli/test_context.py b/src/test/python/apache/aurora/client/cli/test_context.py index d63f060..1a134a2 100644 --- a/src/test/python/apache/aurora/client/cli/test_context.py +++ b/src/test/python/apache/aurora/client/cli/test_context.py @@ -11,14 +11,20 @@ # See the License for the specific language governing permissions and # limitations under the License. # +import mock +import pytest -from apache.aurora.client.api import AuroraClientAPI +from apache.aurora.client.api import AuroraClientAPI, SchedulerProxy +from apache.aurora.client.cli import Context, EXIT_AUTH_ERROR from apache.aurora.client.cli.context import AuroraCommandContext from apache.aurora.client.hooks.hooked_api import HookedAuroraClientAPI +from apache.aurora.common.aurora_job_key import AuroraJobKey from apache.aurora.common.cluster import Cluster from apache.aurora.common.clusters import CLUSTERS -TEST_CLUSTER = Cluster(name='some-cluster') +from ...api_util import SchedulerProxyApiSpec + +TEST_CLUSTER = Cluster(name='some-cluster', auth_mechanism='nothing', scheduler_uri='nowhere') def test_get_api_defaults_to_hooks_enabled(): @@ -48,3 +54,24 @@ def test_get_api_caches_hook_enabled_apis_separately(): assert unhooked_api in context.unhooked_apis.values() assert unhooked_api not in context.apis.values() + + +def test_handles_api_auth_error(): + context = AuroraCommandContext() + + mock_scheduler_proxy = mock.create_autospec(spec=SchedulerProxyApiSpec, instance=True) + mock_scheduler_proxy.killTasks.side_effect = SchedulerProxy.AuthError() + + mock_api = AuroraClientAPI(TEST_CLUSTER, 'user-agent') + mock_api._scheduler_proxy = mock_scheduler_proxy + + context.apis = { + TEST_CLUSTER.name: mock_api + } + api = context.get_api(TEST_CLUSTER.name, clusters={TEST_CLUSTER.name: TEST_CLUSTER}) + + with pytest.raises(Context.CommandError) as e: + api.kill_job(AuroraJobKey(TEST_CLUSTER.name, 'role', 'env', 'job')) + + assert e.value.code == EXIT_AUTH_ERROR + assert mock_scheduler_proxy.killTasks.call_count == 1 http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/test/python/apache/aurora/common/test_transport.py ---------------------------------------------------------------------- diff --git a/src/test/python/apache/aurora/common/test_transport.py b/src/test/python/apache/aurora/common/test_transport.py index e4f93ef..f307e8d 100644 --- a/src/test/python/apache/aurora/common/test_transport.py +++ b/src/test/python/apache/aurora/common/test_transport.py @@ -77,9 +77,8 @@ def test_request_transport_timeout(): def test_raise_for_status_causes_exception(): - response = create_autospec(spec=requests.Response, instance=True) - response.headers = {'header1': 'data', 'header2': 'data2'} - response.raise_for_status.side_effect = requests.exceptions.HTTPError() + response = requests.Response() + response.status_code = 503 session = create_autospec(spec=requests.Session, instance=True) session.headers = {} @@ -97,7 +96,23 @@ def test_raise_for_status_causes_exception(): transport.close() - response.raise_for_status.assert_called_once_with() + +def test_raises_auth_error(): + response = requests.Response() + response.status_code = 401 + + session = create_autospec(spec=requests.Session, instance=True) + session.headers = {} + session.post.return_value = response + + transport = TRequestsTransport('http://localhost:12345', session_factory=lambda: session) + protocol = TJSONProtocol.TJSONProtocol(transport) + client = ReadOnlyScheduler.Client(protocol) + + with pytest.raises(TRequestsTransport.AuthError): + client.getRoleSummary() + + transport.close() def test_request_any_other_exception(): @@ -138,9 +153,8 @@ def test_transport_applies_default_user_agent_if_no_factory_provided(): def test_auth_type_valid(): - response = create_autospec(spec=requests.Response, instance=True) - response.headers = {'header1': 'data', 'header2': 'data2'} - response.raise_for_status.side_effect = requests.exceptions.HTTPError() + response = requests.Response() + response.status_code = 500 session = create_autospec(spec=requests.Session, instance=True) session.headers = {} http://git-wip-us.apache.org/repos/asf/aurora/blob/9a2bbded/src/test/sh/org/apache/aurora/e2e/test_end_to_end.sh ---------------------------------------------------------------------- diff --git a/src/test/sh/org/apache/aurora/e2e/test_end_to_end.sh b/src/test/sh/org/apache/aurora/e2e/test_end_to_end.sh index cc7cdee..501d111 100755 --- a/src/test/sh/org/apache/aurora/e2e/test_end_to_end.sh +++ b/src/test/sh/org/apache/aurora/e2e/test_end_to_end.sh @@ -271,14 +271,24 @@ test_admin() { aurora_admin get_scheduler $_cluster | grep ":8081" } -readonly RPC_DATA="[1,\"snapshot\",1,0,{}]" +restore_netrc() { + mv ~/.netrc.bak ~/.netrc >/dev/null 2>&1 || true +} + test_basic_auth_unauthenticated() { - # TODO(ksweeney): Replace this with a call to the client removing the .netrc when AURORA-1248 is - # fixed. - [[ 401 == $(curl -w '%{http_code}\n' \ - -o /dev/null \ - -s 'http://localhost:8081/api' \ - --data-binary "$RPC_DATA") ]] + local _cluster=$1 _role=$2 _env=$3 _job=$4 + local _config=$5 + local _jobkey="$_cluster/$_role/$_env/$_job" + + mv ~/.netrc ~/.netrc.bak + trap restore_netrc EXIT + + aurora job create $_jobkey $_config || retcode=$? + if [[ $retcode != 30 ]]; then + echo "Expected auth error exit code, got $retcode" + exit 1 + fi + restore_netrc } RETCODE=1 @@ -324,7 +334,7 @@ sudo docker build -t http_example ${TEST_ROOT} test_http_example "${TEST_DOCKER_ARGS[@]}" test_admin "${TEST_ADMIN_ARGS[@]}" -test_basic_auth_unauthenticated +test_basic_auth_unauthenticated "${TEST_ARGS[@]}" /vagrant/src/test/sh/org/apache/aurora/e2e/test_kerberos_end_to_end.sh RETCODE=0
