aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mchucarr...@apache.org
Subject git commit: Add a lightweight version of command hooks for clientv1 commands.
Date Tue, 22 Jul 2014 15:03:02 GMT
Repository: incubator-aurora
Updated Branches:
  refs/heads/master bceac4ecd -> fd60eab6f


Add a lightweight version of command hooks for clientv1 commands.

Bugs closed: aurora-581

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


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

Branch: refs/heads/master
Commit: fd60eab6f7fe5bd0950683e01c8e64f7812777b5
Parents: bceac4e
Author: Mark Chu-Carroll <mchucarroll@twopensource.com>
Authored: Tue Jul 22 10:50:00 2014 -0400
Committer: Mark Chu-Carroll <mchucarroll@twitter.com>
Committed: Tue Jul 22 10:50:05 2014 -0400

----------------------------------------------------------------------
 .../apache/aurora/client/commands/core.py       | 52 +++++++++++
 .../apache/aurora/client/commands/test_kill.py  | 93 +++++++++++++++++++-
 2 files changed, 144 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fd60eab6/src/main/python/apache/aurora/client/commands/core.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/commands/core.py b/src/main/python/apache/aurora/client/commands/core.py
index cbda8a9..3c45ed4 100644
--- a/src/main/python/apache/aurora/client/commands/core.py
+++ b/src/main/python/apache/aurora/client/commands/core.py
@@ -64,6 +64,50 @@ from apache.aurora.common.aurora_job_key import AuroraJobKey
 from gen.apache.aurora.api.constants import ACTIVE_STATES, AURORA_EXECUTOR_NAME, CURRENT_API_VERSION
 from gen.apache.aurora.api.ttypes import ExecutorConfig, ResponseCode, ScheduleStatus
 
+class CoreCommandHook(object):
+  """Limited version of the command hooks framework ported to clientv1 commands.
+  Core command hooks can only be created by invoking "CoreCommandHook.register_hook"
+  in a module compiled into the aurora client executable.
+  Core command hooks are currently only supported for the following commands:
+     create, kill, killall, restart, start_cron, update, cancel_update
+  """
+
+  def execute(self, cmd, options, *args, **kwargs):
+    """
+    :param cmd: the command being invoked
+    :param options: the options object created by processing command line options
+    :param args: all other positional arguments taken by the command.
+    :param kwargs: all other keyword argumetns taken by the command.
+
+    This is invoked by each core client command before the command is executed, *after* options
+    are parsed. If this returns non-zero, the command execution will be aborted and the return
+    code of this method will be used as the exit code. To make a hook work with a specific
+    command, hook implementors should check the "cmd" parameter.
+    """
+    pass
+
+  ALL_HOOKS = []
+
+  @property
+  def name(self):
+    pass
+
+  @classmethod
+  def register_hook(cls, hook):
+    cls.ALL_HOOKS.append(hook)
+
+  @classmethod
+  def clear_hooks(cls):
+    cls.ALL_HOOKS = []
+
+  @classmethod
+  def run_hooks(cls, cmd, options, *args, **kwargs):
+    for hook in cls.ALL_HOOKS:
+      result = hook.execute(cmd, options, *args, **kwargs)
+      if result != 0:
+        print("Command execution aborted by hook %s" % hook.name)
+        exit(result)
+
 
 def get_job_config(job_spec, config_file, options):
   try:
@@ -143,6 +187,7 @@ def create(job_spec, config_file):
   Creates a job based on a configuration file.
   """
   options = app.get_options()
+  CoreCommandHook.run_hooks("create", options, job_spec, config_file)
   maybe_disable_hooks(options)
   try:
     config = get_job_config(job_spec, config_file, options)
@@ -174,6 +219,7 @@ def diff(job_spec, config_file):
   By default the diff will be displayed using 'diff', though you may choose an alternate
   diff program by specifying the DIFF_VIEWER environment variable."""
   options = app.get_options()
+
   config = get_job_config(job_spec, config_file, options)
   if options.rename_from:
     cluster, role, env, name = options.rename_from
@@ -333,6 +379,7 @@ def start_cron(args, options):
   Invokes a cron job immediately, out of its normal cron cycle.
   This does not affect the cron cycle in any way.
   """
+  CoreCommandHook.run_hooks("start_cron", options, *args)
   maybe_disable_hooks(options)
   api, job_key, config_file = LiveJobDisambiguator.disambiguate_args_or_die(
       args, options, make_client_factory())
@@ -405,6 +452,7 @@ def kill(args, options):
   Kills a group of tasks in a running job, blocking until all specified tasks have terminated.
 
   """
+  CoreCommandHook.run_hooks("kill", options, *args)
   maybe_disable_hooks(options)
   if options.shards is None:
     print('Shards option is required for kill; use killall to kill all shards', file=sys.stderr)
@@ -472,6 +520,7 @@ def killall(args, options):
   """usage: killall cluster/role/env/job
   Kills all tasks in a running job, blocking until all specified tasks have been terminated.
   """
+  CoreCommandHook.run_hooks("killall", options, *args)
   maybe_disable_hooks(options)
   job_key = AuroraJobKey.from_path(args[0])
   config_file = args[1] if len(args) > 1 else None  # the config for hooks
@@ -601,6 +650,7 @@ def update(job_spec, config_file):
       time.sleep(5)
 
   options = app.get_options()
+  CoreCommandHook.run_hooks("update", options, job_spec, config_file)
   maybe_disable_hooks(options)
   config = get_job_config(job_spec, config_file, options)
   api = make_client(config.cluster())
@@ -663,6 +713,7 @@ def restart(args, options):
 
   Restarts are fully controlled client-side, so aborting halts the restart.
   """
+  CoreCommandHook.run_hooks("restart", options, *args)
   if options.max_total_failures < 0:
     print("max_total_failures option must be >0, but you specified %s" % options.max_total_failures,
       file=sys.stderr)
@@ -693,6 +744,7 @@ def cancel_update(args, options):
   or if another user is actively updating the job.  This command should only
   be used when the user is confident that they are not conflicting with another user.
   """
+  CoreCommandHook.run_hooks("cancel_update", options, *args)
   api, job_key, config_file = LiveJobDisambiguator.disambiguate_args_or_die(
       args, options, make_client_factory())
   config = get_job_config(job_key.to_path(), config_file, options) if config_file else None

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/fd60eab6/src/test/python/apache/aurora/client/commands/test_kill.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/commands/test_kill.py b/src/test/python/apache/aurora/client/commands/test_kill.py
index d8d54a2..642ee64 100644
--- a/src/test/python/apache/aurora/client/commands/test_kill.py
+++ b/src/test/python/apache/aurora/client/commands/test_kill.py
@@ -17,7 +17,7 @@ import contextlib
 from mock import Mock, patch
 from twitter.common.contextutil import temporary_file
 
-from apache.aurora.client.commands.core import kill, killall
+from apache.aurora.client.commands.core import CoreCommandHook, kill, killall
 from apache.aurora.client.commands.util import AuroraClientCommandTest
 from apache.aurora.common.aurora_job_key import AuroraJobKey
 
@@ -186,6 +186,97 @@ class TestClientKillCommand(AuroraClientCommandTest):
             name=self.TEST_JOB), None, config=mock_config)
       self.assert_scheduler_called(mock_api, self.get_expected_task_query(), 3)
 
+  def test_happy_hook(self):
+    """Test that hooks that return 0 don't block command execution"""
+
+    class HappyHook(CoreCommandHook):
+      @property
+      def name(self):
+        return "I'm so happy"
+
+      def execute(self, cmd, options, *args, **kwargs):
+        return 0
+
+    CoreCommandHook.register_hook(HappyHook())
+
+
+    mock_options = self.setup_mock_options()
+    mock_config = Mock()
+    (mock_api, mock_scheduler_proxy) = self.create_mock_api()
+    mock_api.kill_job.return_value = self.get_kill_job_response()
+    mock_scheduler_proxy.killTasks.return_value = self.get_kill_job_response()
+    mock_query_results = [
+        self.create_mock_status_query_result(ScheduleStatus.RUNNING),
+        self.create_mock_status_query_result(ScheduleStatus.KILLING),
+        self.create_mock_status_query_result(ScheduleStatus.KILLED),
+    ]
+    mock_scheduler_proxy.getTasksWithoutConfigs.side_effect = mock_query_results
+    with contextlib.nested(
+        patch('time.sleep'),
+        patch('apache.aurora.client.commands.core.make_client',
+            return_value=mock_api),
+        patch('twitter.common.app.get_options', return_value=mock_options),
+        patch('apache.aurora.client.commands.core.get_job_config', return_value=mock_config))
as (
+            sleep,
+            mock_make_client,
+            options,
+            mock_get_job_config):
+
+      with temporary_file() as fp:
+        fp.write(self.get_valid_config())
+        fp.flush()
+        killall(['west/mchucarroll/test/hello', fp.name], mock_options)
+
+      # Now check that the right API calls got made.
+      self.assert_kill_job_called(mock_api)
+      mock_api.kill_job.assert_called_with(
+        AuroraJobKey(cluster=self.TEST_CLUSTER, role=self.TEST_ROLE, env=self.TEST_ENV,
+            name=self.TEST_JOB), None, config=mock_config)
+      self.assert_scheduler_called(mock_api, self.get_expected_task_query(), 3)
+      CoreCommandHook.clear_hooks()
+
+
+
+  def test_hook_aborts_kill(self):
+    """Test that a command hook that returns non-zero does block command execution."""
+    class FailingKillHook(CoreCommandHook):
+      @property
+      def name(self):
+        return "failure"
+
+      def execute(self, cmd, options, *args, **kwargs):
+        if cmd == "killall":
+          assert args[0] == 'west/mchucarroll/test/hello'
+          return 1
+        else:
+          return 0
+
+    CoreCommandHook.register_hook(FailingKillHook())
+
+    mock_options = self.setup_mock_options()
+    mock_config = Mock()
+    (mock_api, mock_scheduler_proxy) = self.create_mock_api()
+    with contextlib.nested(
+        patch('time.sleep'),
+        patch('apache.aurora.client.commands.core.make_client',
+            return_value=mock_api),
+        patch('twitter.common.app.get_options', return_value=mock_options),
+        patch('apache.aurora.client.commands.core.get_job_config', return_value=mock_config))
as (
+            sleep,
+            mock_make_client,
+            options,
+            mock_get_job_config):
+
+      with temporary_file() as fp:
+        fp.write(self.get_valid_config())
+        fp.flush()
+        self.assertRaises(SystemExit, killall, ['west/mchucarroll/test/hello', fp.name],
mock_options)
+
+      CoreCommandHook.clear_hooks()
+      mock_api.kill_job.call_count == 0
+
+
+
   def create_status_call_result(cls):
     """Set up the mock status call that will be used to get a task list for
     a batched kill command.


Mime
View raw message