You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by mc...@apache.org on 2014/06/06 16:19:42 UTC

git commit: Make errors in the thrift API interface handle-able by the aurora client.

Repository: incubator-aurora
Updated Branches:
  refs/heads/master 36cc66628 -> 274b185c5


Make errors in the thrift API interface handle-able by the aurora client.

- Add a general exception catch around the code that calls thrift RPCs,
  so that any error that occurs in the thrift code will be turned
  into a ThriftInternalException.
- Modify the client so that it catches internal errors without dumping
  stack, and cleanly exits with EXIT_API_ERROR.
- Add a file for tests of API functionality, instead of co-opting
  existing tests.

Reviewed at https://reviews.apache.org/r/22274/


Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/274b185c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/274b185c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/274b185c

Branch: refs/heads/master
Commit: 274b185c536d7e9b5d837a0c67795bb683042b46
Parents: 36cc666
Author: Mark Chu-Carroll <mc...@twopensource.com>
Authored: Fri Jun 6 10:11:55 2014 -0400
Committer: Mark Chu-Carroll <mc...@twitter.com>
Committed: Fri Jun 6 10:18:46 2014 -0400

----------------------------------------------------------------------
 .../python/apache/aurora/client/api/__init__.py |   6 +-
 .../aurora/client/api/scheduler_client.py       |   6 +
 .../python/apache/aurora/client/cli/__init__.py |   3 +
 src/test/python/apache/aurora/client/cli/BUILD  |  15 ++
 .../aurora/client/cli/test_api_from_cli.py      | 143 +++++++++++++++++++
 5 files changed, 172 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/274b185c/src/main/python/apache/aurora/client/api/__init__.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/api/__init__.py b/src/main/python/apache/aurora/client/api/__init__.py
index a73f23f..99444d6 100644
--- a/src/main/python/apache/aurora/client/api/__init__.py
+++ b/src/main/python/apache/aurora/client/api/__init__.py
@@ -33,6 +33,7 @@ class AuroraClientAPI(object):
   class Error(Exception): pass
   class TypeError(Error, TypeError): pass
   class ClusterMismatch(Error, ValueError): pass
+  class ThriftInternalError(Error): pass
 
   def __init__(self, cluster, verbose=False, session_key_factory=make_session_key):
     if not isinstance(cluster, Cluster):
@@ -109,7 +110,10 @@ class AuroraClientAPI(object):
                      environment=env)
 
   def query(self, query):
-    return self._scheduler_proxy.getTasksStatus(query)
+    try:
+      return self._scheduler_proxy.getTasksStatus(query)
+    except SchedulerProxy.ThriftInternalError as e:
+      raise self.ThriftInternalError(e.args[0])
 
   def update_job(self, config, health_check_interval_seconds=3, instances=None):
     """Run a job update for a given config, for the specified instances.  If

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/274b185c/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 86ccdd9..7c5fba6 100644
--- a/src/main/python/apache/aurora/client/api/scheduler_client.py
+++ b/src/main/python/apache/aurora/client/api/scheduler_client.py
@@ -182,6 +182,7 @@ class SchedulerProxy(object):
   class TimeoutError(Error): pass
   class AuthenticationError(Error): pass
   class APIVersionError(Error): pass
+  class ThriftInternalError(Error): pass
 
   def __init__(self, cluster, verbose=False, session_key_factory=make_session_key):
     """A callable session_key_factory should be provided for authentication"""
@@ -267,6 +268,11 @@ class SchedulerProxy(object):
           log.warning('Connection error with scheduler: %s, reconnecting...' % e)
           self.invalidate()
           time.sleep(self.RPC_RETRY_INTERVAL.as_(Time.SECONDS))
+        except Exception as e:
+          # Take any error that occurs during the RPC call, and transform it
+          # into something clients can handle.
+          raise self.ThriftInternalError("Error during thrift call %s to %s: %s" %
+              (method_name, self.cluster.name, e))
       raise self.TimeoutError('Timed out attempting to issue %s to %s' % (
           method_name, self.cluster.name))
 

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/274b185c/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 fc63dbf..552f9f1 100644
--- a/src/main/python/apache/aurora/client/cli/__init__.py
+++ b/src/main/python/apache/aurora/client/cli/__init__.py
@@ -394,6 +394,9 @@ class CommandLine(object):
       print_aurora_log(logging.INFO, "Error executing command: %s", c.msg)
       self.print_err("Error executing command: %s" % c.msg)
       return c.code
+    except Exception as e:
+      print_aurora_log(logging.ERROR, "Internal error executing command: %s", e)
+      return EXIT_API_ERROR
 
 
 class Noun(AuroraCommand):

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/274b185c/src/test/python/apache/aurora/client/cli/BUILD
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/BUILD b/src/test/python/apache/aurora/client/cli/BUILD
index c91ae42..3c88ed7 100644
--- a/src/test/python/apache/aurora/client/cli/BUILD
+++ b/src/test/python/apache/aurora/client/cli/BUILD
@@ -15,6 +15,7 @@
 python_test_suite(
   name = 'all',
   dependencies = [
+    pants(':api'),
     pants(':bridge'),
     pants(':command_hooks'),
     pants(':cron'),
@@ -83,6 +84,20 @@ python_tests(
 )
 
 python_tests(
+  name = 'api',
+  sources = [ 'test_api_from_cli.py' ],
+  dependencies = [
+    pants(':util'),
+    pants('3rdparty/python:mock'),
+    pants('3rdparty/python:twitter.common.contextutil'),
+    pants('src/main/python/apache/aurora/client/cli'),
+    pants('src/main/python/apache/aurora/client/cli:client_lib'),
+    pants('src/test/python/apache/aurora/client/commands:util')
+  ]
+)
+
+
+python_tests(
   name = 'config',
   sources = [ 'test_config_noun.py' ],
   dependencies = [

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/274b185c/src/test/python/apache/aurora/client/cli/test_api_from_cli.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_api_from_cli.py b/src/test/python/apache/aurora/client/cli/test_api_from_cli.py
new file mode 100644
index 0000000..bf72383
--- /dev/null
+++ b/src/test/python/apache/aurora/client/cli/test_api_from_cli.py
@@ -0,0 +1,143 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import contextlib
+
+from mock import Mock, patch
+
+from apache.aurora.client.cli import EXIT_API_ERROR
+from apache.aurora.client.cli.client import AuroraCommandLine
+from apache.aurora.client.cli.util import AuroraClientCommandTest
+
+from gen.apache.aurora.api.ttypes import (
+    AssignedTask,
+    Identity,
+    JobKey,
+    ResponseCode,
+    ScheduleStatus,
+    ScheduleStatusResult,
+    TaskConfig,
+    TaskEvent,
+    TaskQuery
+)
+
+
+class TestApiFromCLI(AuroraClientCommandTest):
+  """A container for tests that are probing at API functionality,
+  to see if the CLI can handle API-level errors.
+  """
+
+  @classmethod
+  def create_mock_scheduled_tasks(cls):
+    jobs = []
+    for name in ['foo', 'bar', 'baz']:
+      job = Mock()
+      job.key = JobKey(role=cls.TEST_ROLE, environment=cls.TEST_ENV, name=name)
+      job.failure_count = 0
+      job.assignedTask = Mock(spec=AssignedTask)
+      job.assignedTask.slaveHost = 'slavehost'
+      job.assignedTask.task = Mock(spec=TaskConfig)
+      job.assignedTask.task.maxTaskFailures = 1
+      job.assignedTask.task.metadata = []
+      job.assignedTask.task.owner = Identity(role='bozo')
+      job.assignedTask.task.environment = 'test'
+      job.assignedTask.task.jobName = 'woops'
+      job.assignedTask.task.numCpus = 2
+      job.assignedTask.task.ramMb = 2
+      job.assignedTask.task.diskMb = 2
+      job.assignedTask.instanceId = 4237894
+      job.assignedTask.assignedPorts = None
+      job.status = ScheduleStatus.RUNNING
+      mockEvent = Mock(spec=TaskEvent)
+      mockEvent.timestamp = 28234726395
+      mockEvent.status = ScheduleStatus.RUNNING
+      mockEvent.message = "Hi there"
+      job.taskEvents = [mockEvent]
+      jobs.append(job)
+    return jobs
+
+  @classmethod
+  def create_mock_scheduled_task_no_metadata(cls):
+    result = cls.create_mock_scheduled_tasks()
+    for job in result:
+      job.assignedTask.task.metadata = None
+    return result
+
+  @classmethod
+  def create_getjobs_response(cls):
+    result = Mock()
+    result.responseCode = ResponseCode.OK
+    result.result = Mock()
+    result.result.getJobsResult = Mock()
+    mock_job_one = Mock()
+    mock_job_one.key = Mock()
+    mock_job_one.key.role = 'RoleA'
+    mock_job_one.key.environment = 'test'
+    mock_job_one.key.name = 'hithere'
+    mock_job_two = Mock()
+    mock_job_two.key = Mock()
+    mock_job_two.key.role = 'bozo'
+    mock_job_two.key.environment = 'test'
+    mock_job_two.key.name = 'hello'
+    result.result.getJobsResult.configs = [mock_job_one, mock_job_two]
+    return result
+
+  @classmethod
+  def create_status_response(cls):
+    resp = cls.create_simple_success_response()
+    resp.result.scheduleStatusResult = Mock(spec=ScheduleStatusResult)
+    resp.result.scheduleStatusResult.tasks = set(cls.create_mock_scheduled_tasks())
+    return resp
+
+  @classmethod
+  def create_status_response_null_metadata(cls):
+    resp = cls.create_simple_success_response()
+    resp.result.scheduleStatusResult = Mock(spec=ScheduleStatusResult)
+    resp.result.scheduleStatusResult.tasks = set(cls.create_mock_scheduled_task_no_metadata())
+    return resp
+
+  @classmethod
+  def create_failed_status_response(cls):
+    return cls.create_blank_response(ResponseCode.INVALID_REQUEST, 'No tasks found for query')
+
+  def test_successful_status_deep(self):
+    """Test the status command more deeply: in a request with a fully specified
+    job, it should end up doing a query using getTasksStatus."""
+    (mock_api, mock_scheduler_proxy) = self.create_mock_api()
+    mock_scheduler_proxy.query.return_value = self.create_status_response()
+    with contextlib.nested(
+        patch('apache.aurora.client.api.SchedulerProxy', return_value=mock_scheduler_proxy),
+        patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
+      cmd = AuroraCommandLine()
+      cmd.execute(['job', 'status', 'west/bozo/test/hello'])
+      mock_scheduler_proxy.getTasksStatus.assert_called_with(TaskQuery(jobName='hello',
+          environment='test', owner=Identity(role='bozo')))
+
+  def test_status_api_failure(self):
+    # Following should use spec=SchedulerClient, but due to introspection for the RPC calls,
+    # the necessary methods aren't in that spec.
+    mock_scheduler_client = Mock()
+    mock_scheduler_client.getTasksStatus.side_effect = IOError("Uh-Oh")
+    with contextlib.nested(
+        patch('apache.aurora.client.api.scheduler_client.SchedulerClient.get',
+            return_value=mock_scheduler_client),
+        patch('apache.aurora.client.factory.CLUSTERS', new=self.TEST_CLUSTERS)):
+
+      cmd = AuroraCommandLine()
+      # This should create a scheduler client, set everything up, and then issue a
+      # getTasksStatus call against the mock_scheduler_client. That should raise an
+      # exception, which results in the command failing with an error code.
+      result = cmd.execute(['job', 'status', 'west/bozo/test/hello'])
+      assert result == EXIT_API_ERROR
+      mock_scheduler_client.getTasksStatus.assert_call_count == 1