You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by ma...@apache.org on 2016/02/11 00:12:55 UTC
aurora git commit: Implementing 'aurora job add' command.
Repository: aurora
Updated Branches:
refs/heads/master d657f952a -> 46277a11b
Implementing 'aurora job add' command.
Bugs closed: AURORA-1258
Reviewed at https://reviews.apache.org/r/43373/
Project: http://git-wip-us.apache.org/repos/asf/aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/aurora/commit/46277a11
Tree: http://git-wip-us.apache.org/repos/asf/aurora/tree/46277a11
Diff: http://git-wip-us.apache.org/repos/asf/aurora/diff/46277a11
Branch: refs/heads/master
Commit: 46277a11b2c026f2c53cbeb7ce933da8e51cb4dd
Parents: d657f95
Author: Maxim Khutornenko <ma...@apache.org>
Authored: Wed Feb 10 15:12:36 2016 -0800
Committer: Maxim Khutornenko <ma...@apache.org>
Committed: Wed Feb 10 15:12:36 2016 -0800
----------------------------------------------------------------------
.../python/apache/aurora/client/api/__init__.py | 7 ++
.../python/apache/aurora/client/cli/context.py | 29 +++++--
.../python/apache/aurora/client/cli/jobs.py | 85 +++++++++++++++----
.../python/apache/aurora/client/cli/options.py | 17 ++--
.../apache/aurora/client/hooks/hooked_api.py | 8 ++
src/test/python/apache/aurora/api_util.py | 2 +-
.../python/apache/aurora/client/api/test_api.py | 14 ++++
.../python/apache/aurora/client/cli/test_add.py | 86 ++++++++++++++++++++
.../apache/aurora/client/cli/test_options.py | 27 +++++-
.../aurora/client/hooks/test_hooked_api.py | 3 +-
.../aurora/client/hooks/test_non_hooked_api.py | 3 +-
11 files changed, 249 insertions(+), 32 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/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 63bd649..c5469bd 100644
--- a/src/main/python/apache/aurora/client/api/__init__.py
+++ b/src/main/python/apache/aurora/client/api/__init__.py
@@ -26,6 +26,7 @@ from .updater_util import UpdaterConfig
from gen.apache.aurora.api.constants import LIVE_STATES
from gen.apache.aurora.api.ttypes import (
+ InstanceKey,
JobKey,
JobUpdateKey,
JobUpdateQuery,
@@ -99,6 +100,12 @@ class AuroraClientAPI(object):
log.info("Retrieving jobs for role %s" % role)
return self._scheduler_proxy.getJobs(role)
+ def add_instances(self, job_key, instance_id, count):
+ key = InstanceKey(jobKey=job_key.to_thrift(), instanceId=instance_id)
+ log.info("Adding %s instances to %s using the task config of instance %s"
+ % (count, job_key, instance_id))
+ return self._scheduler_proxy.addInstances(None, None, key, count)
+
def kill_job(self, job_key, instances=None, lock=None):
log.info("Killing tasks for job: %s" % job_key)
self._assert_valid_job_key(job_key)
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/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 24a37ec..9b15118 100644
--- a/src/main/python/apache/aurora/client/cli/context.py
+++ b/src/main/python/apache/aurora/client/cli/context.py
@@ -71,9 +71,8 @@ def add_auth_error_handler(api):
class AuroraCommandContext(Context):
- LOCK_ERROR_MSG = """Error: job is locked by an incomplete update.
- run 'aurora job cancel-update' to release the lock if no update
- is in progress"""
+ LOCK_ERROR_MSG = """Error: job is locked by an active update.
+ Run 'aurora update abort' or wait for the active update to finish."""
"""A context object used by Aurora commands to manage command processing state
and common operations.
@@ -183,18 +182,32 @@ class AuroraCommandContext(Context):
key.role, key.env, key.name)
return jobs
- def get_active_instances(self, key):
- """Returns a list of the currently active instances of a job"""
+ def get_active_tasks(self, key):
+ """Returns a list of the currently active tasks of a job
+
+ :param key: Job key
+ :type key: AuroraJobKey
+ :return: set of active tasks
+ """
api = self.get_api(key.cluster)
resp = api.query_no_configs(
api.build_query(key.role, key.name, env=key.env, statuses=ACTIVE_STATES))
self.log_response_and_raise(resp, err_code=EXIT_INVALID_PARAMETER)
return resp.result.scheduleStatusResult.tasks
- def verify_instances_option_validity(self, jobkey, instances):
- """Verifies all provided job instances are currently active."""
- active = set(task.assignedTask.instanceId for task in self.get_active_instances(jobkey) or [])
+ def get_active_instances_or_raise(self, key, instances):
+ """Same as get_active_instances but raises error if
+ any of the requested instances are not active.
+
+ :param key: Job key
+ :type key: AuroraJobKey
+ :param instances: instances to verify
+ :type instances: list of int
+ :return: set of all currently active instances
+ """
+ active = set(task.assignedTask.instanceId for task in self.get_active_tasks(key) or [])
unrecognized = set(instances) - active
if unrecognized:
raise self.CommandError(EXIT_INVALID_PARAMETER,
"Invalid instance parameter: %s" % (list(unrecognized)))
+ return active
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/main/python/apache/aurora/client/cli/jobs.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/jobs.py b/src/main/python/apache/aurora/client/cli/jobs.py
index 67ab4f0..3cbd607 100644
--- a/src/main/python/apache/aurora/client/cli/jobs.py
+++ b/src/main/python/apache/aurora/client/cli/jobs.py
@@ -46,6 +46,7 @@ from apache.aurora.client.cli import (
)
from apache.aurora.client.cli.context import AuroraCommandContext
from apache.aurora.client.cli.options import (
+ ADD_INSTANCE_WAIT_OPTION,
ALL_INSTANCES,
BATCH_OPTION,
BIND_OPTION,
@@ -61,6 +62,7 @@ from apache.aurora.client.cli.options import (
MAX_TOTAL_FAILURES_OPTION,
NO_BATCHING_OPTION,
STRICT_OPTION,
+ TASK_INSTANCE_ARGUMENT,
WATCH_OPTION,
CommandOption
)
@@ -91,6 +93,26 @@ WILDCARD_JOBKEY_OPTION = CommandOption("jobspec", type=arg_type_jobkey,
help="A jobkey, optionally containing wildcards")
+def wait_until(wait_until_option, job_key, api, instances=None):
+ """Waits for job instances to reach specified status.
+
+ :param wait_until_option: Expected instance status
+ :type wait_until_option: ADD_INSTANCE_WAIT_OPTION
+ :param job_key: Job key to wait on
+ :type job_key: AuroraJobKey
+ :param api: Aurora scheduler API
+ :type api: AuroraClientAPI
+ :param instances: Specific instances to wait on
+ :type instances: set of int
+ """
+ if wait_until_option == "RUNNING":
+ JobMonitor(api.scheduler_proxy, job_key).wait_until(
+ JobMonitor.running_or_finished,
+ instances=instances)
+ elif wait_until_option == "FINISHED":
+ JobMonitor(api.scheduler_proxy, job_key).wait_until(JobMonitor.terminal, instances=instances)
+
+
class CreateJobCommand(Verb):
@property
def name(self):
@@ -100,14 +122,9 @@ class CreateJobCommand(Verb):
def help(self):
return "Create a service or ad hoc job using aurora"
- CREATE_STATES = ("PENDING", "RUNNING", "FINISHED")
-
def get_options(self):
return [BIND_OPTION, JSON_READ_OPTION,
- CommandOption("--wait-until", choices=self.CREATE_STATES,
- default="PENDING",
- help=("Block the client until all the tasks have transitioned into the requested "
- "state. Default: PENDING")),
+ ADD_INSTANCE_WAIT_OPTION,
BROWSER_OPTION,
JOBSPEC_ARGUMENT, CONFIG_ARGUMENT]
@@ -124,10 +141,9 @@ class CreateJobCommand(Verb):
err_msg="Job creation failed due to error:")
if context.options.open_browser:
webbrowser.open_new_tab(get_job_page(api, context.options.jobspec))
- if context.options.wait_until == "RUNNING":
- JobMonitor(api.scheduler_proxy, config.job_key()).wait_until(JobMonitor.running_or_finished)
- elif context.options.wait_until == "FINISHED":
- JobMonitor(api.scheduler_proxy, config.job_key()).wait_until(JobMonitor.terminal)
+
+ wait_until(context.options.wait_until, config.job_key(), api)
+
# Check to make sure the job was created successfully.
status_response = api.check_status(config.job_key())
if (status_response.responseCode is not ResponseCode.OK or
@@ -392,8 +408,8 @@ class AbstractKillCommand(Verb):
def kill_in_batches(self, context, job, instances_arg, config):
api = context.get_api(job.cluster)
- # query the job, to get the list of active instances.
- tasks = context.get_active_instances(job)
+ # query the job, to get the list of active tasks.
+ tasks = context.get_active_tasks(job)
if tasks is None or len(tasks) == 0:
context.print_err("No tasks to kill found for job %s" % job)
return EXIT_INVALID_PARAMETER
@@ -424,6 +440,46 @@ class AbstractKillCommand(Verb):
raise context.CommandError(EXIT_COMMAND_FAILURE, "Errors occurred while killing instances")
+class AddCommand(Verb):
+ @property
+ def name(self):
+ return "add"
+
+ @property
+ def help(self):
+ return textwrap.dedent("""\
+ Add instances to a scheduled job. The task config to replicate is specified by the
+ /INSTANCE value of the task_instance argument.""")
+
+ def get_options(self):
+ return [BROWSER_OPTION,
+ BIND_OPTION,
+ ADD_INSTANCE_WAIT_OPTION,
+ CONFIG_OPTION,
+ JSON_READ_OPTION,
+ TASK_INSTANCE_ARGUMENT,
+ CommandOption('instance_count', type=int, help='Number of instances to add.')]
+
+ def execute(self, context):
+ job = context.options.task_instance.jobkey
+ instance = context.options.task_instance.instance
+ count = context.options.instance_count
+
+ active = context.get_active_instances_or_raise(job, [instance])
+ start = max(list(active)) + 1
+
+ api = context.get_api(job.cluster)
+ resp = api.add_instances(job, instance, count)
+ context.log_response_and_raise(resp)
+
+ wait_until(context.options.wait_until, job, api, range(start, start + count))
+
+ if context.options.open_browser:
+ webbrowser.open_new_tab(get_job_page(api, job))
+
+ return EXIT_OK
+
+
class KillCommand(AbstractKillCommand):
@property
def name(self):
@@ -444,7 +500,7 @@ class KillCommand(AbstractKillCommand):
"The instances list cannot be omitted in a kill command!; "
"use killall to kill all instances")
if context.options.strict:
- context.verify_instances_option_validity(job, instances_arg)
+ context.get_active_instances_or_raise(job, instances_arg)
api = context.get_api(job.cluster)
config = context.get_job_config_optional(job, context.options.config)
if context.options.no_batching:
@@ -581,7 +637,7 @@ class RestartCommand(Verb):
instances = (None if context.options.instance_spec.instance == ALL_INSTANCES else
context.options.instance_spec.instance)
if instances is not None and context.options.strict:
- context.verify_instances_option_validity(job, instances)
+ context.get_active_instances_or_raise(job, instances)
api = context.get_api(job.cluster)
config = context.get_job_config_optional(job, context.options.config)
restart_settings = RestartSettings(
@@ -775,3 +831,4 @@ class Job(Noun):
self.register_verb(OpenCommand())
self.register_verb(RestartCommand())
self.register_verb(StatusCommand())
+ self.register_verb(AddCommand())
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/main/python/apache/aurora/client/cli/options.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/cli/options.py b/src/main/python/apache/aurora/client/cli/options.py
index 2263978..1245ff1 100644
--- a/src/main/python/apache/aurora/client/cli/options.py
+++ b/src/main/python/apache/aurora/client/cli/options.py
@@ -116,19 +116,19 @@ TaskInstanceKey = namedtuple('TaskInstanceKey', ['jobkey', 'instance'])
def parse_task_instance_key(key):
pieces = key.split('/')
if len(pieces) != 5:
- raise ValueError('Task instance specifier %s is not in the form '
+ raise ArgumentTypeError('Task instance specifier %s is not in the form '
'CLUSTER/ROLE/ENV/NAME/INSTANCE' % key)
(cluster, role, env, name, instance_str) = pieces
try:
instance = int(instance_str)
except ValueError:
- raise ValueError('Instance must be an integer, but got %s' % instance_str)
+ raise ArgumentTypeError('Instance must be an integer, but got %s' % instance_str)
return TaskInstanceKey(AuroraJobKey(cluster, role, env, name), instance)
def instance_specifier(spec_str):
if spec_str is None or spec_str == '':
- raise ValueError('Instance specifier must be non-empty')
+ raise ArgumentTypeError('Instance specifier must be non-empty')
parts = spec_str.split('/')
if len(parts) == 4:
jobkey = AuroraJobKey(*parts)
@@ -149,11 +149,11 @@ def binding_parser(binding):
"""
binding_parts = binding.split("=", 1)
if len(binding_parts) < 2:
- raise ValueError('Binding parameter must be formatted name=value')
+ raise ArgumentTypeError('Binding parameter must be formatted name=value')
try:
ref = Ref.from_address(binding_parts[0])
except Ref.InvalidRefError as e:
- raise ValueError("Could not parse binding parameter %s: %s" % (binding, e))
+ raise ArgumentTypeError("Could not parse binding parameter %s: %s" % (binding, e))
return {ref: binding_parts[1]}
@@ -273,6 +273,13 @@ TASK_INSTANCE_ARGUMENT = CommandOption('task_instance', type=parse_task_instance
help='A task instance specifier, in the form CLUSTER/ROLE/ENV/NAME/INSTANCE')
+ADD_INSTANCE_WAIT_OPTION = CommandOption('--wait-until',
+ choices=('PENDING', 'RUNNING', 'FINISHED'),
+ default='PENDING',
+ help='Block the client until all the tasks have transitioned into the requested '
+ 'state. Default: PENDING')
+
+
WATCH_OPTION = CommandOption('--watch-secs', type=int, default=30,
help='Minimum number of seconds a instance must remain in RUNNING state before considered a '
'success.')
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/main/python/apache/aurora/client/hooks/hooked_api.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/client/hooks/hooked_api.py b/src/main/python/apache/aurora/client/hooks/hooked_api.py
index 185e57d..300071f 100644
--- a/src/main/python/apache/aurora/client/hooks/hooked_api.py
+++ b/src/main/python/apache/aurora/client/hooks/hooked_api.py
@@ -49,6 +49,9 @@ class NonHookedAuroraClientAPI(AuroraClientAPI):
* is thus available to API methods in subclasses
"""
+ def add_instances(self, job_key, instance_id, count, config=None):
+ return super(NonHookedAuroraClientAPI, self).add_instances(job_key, instance_id, count)
+
def kill_job(self, job_key, instances=None, lock=None, config=None):
return super(NonHookedAuroraClientAPI, self).kill_job(job_key, instances=instances, lock=lock)
@@ -154,6 +157,11 @@ class HookedAuroraClientAPI(NonHookedAuroraClientAPI):
return self._hooked_call(config, None,
_partial(super(HookedAuroraClientAPI, self).create_job, config, lock))
+ def add_instances(self, job_key, instance_id, count, config=None):
+ return self._hooked_call(config, job_key,
+ _partial(super(HookedAuroraClientAPI, self).add_instances,
+ job_key, instance_id, count, config=config))
+
def kill_job(self, job_key, instances=None, lock=None, config=None):
return self._hooked_call(config, job_key,
_partial(super(HookedAuroraClientAPI, self).kill_job,
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/test/python/apache/aurora/api_util.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/api_util.py b/src/test/python/apache/aurora/api_util.py
index 9d44b88..4bb306f 100644
--- a/src/test/python/apache/aurora/api_util.py
+++ b/src/test/python/apache/aurora/api_util.py
@@ -88,7 +88,7 @@ class SchedulerThriftApiSpec(ReadOnlyScheduler.Iface):
def killTasks(self, query, lock, jobKey, instances):
pass
- def addInstances(self, config, lock):
+ def addInstances(self, config, lock, key, count):
pass
def acquireLock(self, lockKey):
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/test/python/apache/aurora/client/api/test_api.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/api/test_api.py b/src/test/python/apache/aurora/client/api/test_api.py
index 974fc7e..c066ae7 100644
--- a/src/test/python/apache/aurora/client/api/test_api.py
+++ b/src/test/python/apache/aurora/client/api/test_api.py
@@ -24,6 +24,7 @@ from apache.aurora.config.schema.base import UpdateConfig
from ...api_util import SchedulerThriftApiSpec
from gen.apache.aurora.api.ttypes import (
+ InstanceKey,
JobConfiguration,
JobKey,
JobUpdateKey,
@@ -109,6 +110,19 @@ class TestJobUpdateApis(unittest.TestCase):
config.instances.return_value = 5
return config
+ def test_add_instances(self):
+ """Test adding instances."""
+ api, mock_proxy = self.mock_api()
+ job_key = AuroraJobKey("foo", "role", "env", "name")
+ mock_proxy.addInstances.return_value = self.create_simple_success_response()
+ api.add_instances(job_key, 1, 10)
+
+ mock_proxy.addInstances.assert_called_once_with(
+ None,
+ None,
+ InstanceKey(jobKey=job_key.to_thrift(), instanceId=1),
+ 10)
+
def test_start_job_update(self):
"""Test successful job update start."""
api, mock_proxy = self.mock_api()
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/test/python/apache/aurora/client/cli/test_add.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_add.py b/src/test/python/apache/aurora/client/cli/test_add.py
new file mode 100644
index 0000000..b22b9f7
--- /dev/null
+++ b/src/test/python/apache/aurora/client/cli/test_add.py
@@ -0,0 +1,86 @@
+#
+# 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 pytest
+from mock import call, patch
+
+from apache.aurora.client.cli import Context
+from apache.aurora.client.cli.jobs import AddCommand
+from apache.aurora.client.cli.options import TaskInstanceKey
+
+from .util import AuroraClientCommandTest, FakeAuroraCommandContext, mock_verb_options
+
+from gen.apache.aurora.api.ttypes import ScheduleStatus
+
+
+class TestAddCommand(AuroraClientCommandTest):
+ def setUp(self):
+ self._command = AddCommand()
+ self._mock_options = mock_verb_options(self._command)
+ self._mock_options.task_instance = TaskInstanceKey(self.TEST_JOBKEY, 1)
+ self._mock_options.instance_count = 3
+ self._fake_context = FakeAuroraCommandContext()
+ self._fake_context.set_options(self._mock_options)
+ self._mock_api = self._fake_context.get_api('test')
+
+ def test_add_instances(self):
+ self._mock_options.open_browser = True
+ self._fake_context.add_expected_query_result(self.create_query_call_result(
+ self.create_scheduled_task(1, ScheduleStatus.RUNNING)))
+
+ self._mock_api.add_instances.return_value = self.create_simple_success_response()
+
+ with patch('webbrowser.open_new_tab') as mock_webbrowser:
+ self._command.execute(self._fake_context)
+
+ assert self._mock_api.add_instances.mock_calls == [call(
+ self.TEST_JOBKEY,
+ self._mock_options.task_instance.instance,
+ 3)]
+ assert mock_webbrowser.mock_calls == [
+ call("http://something_or_other/scheduler/bozo/test/hello")
+ ]
+
+ def test_wait_added_instances(self):
+ self._mock_options.wait_until = 'RUNNING'
+ self._fake_context.add_expected_query_result(self.create_query_call_result(
+ self.create_scheduled_task(1, ScheduleStatus.PENDING)))
+
+ self._mock_api.add_instances.return_value = self.create_simple_success_response()
+
+ with patch('apache.aurora.client.cli.jobs.wait_until') as mock_wait:
+ self._command.execute(self._fake_context)
+
+ assert self._mock_api.add_instances.mock_calls == [call(
+ self.TEST_JOBKEY,
+ self._mock_options.task_instance.instance,
+ 3)]
+ assert mock_wait.mock_calls == [call(
+ self._mock_options.wait_until,
+ self.TEST_JOBKEY,
+ self._mock_api,
+ [2, 3, 4])]
+
+ def test_no_active_instance(self):
+ self._fake_context.add_expected_query_result(self.create_empty_task_result())
+ with pytest.raises(Context.CommandError):
+ self._command.execute(self._fake_context)
+
+ def test_add_instances_raises(self):
+ self._fake_context.add_expected_query_result(self.create_query_call_result(
+ self.create_scheduled_task(1, ScheduleStatus.PENDING)))
+
+ self._mock_api.add_instances.return_value = self.create_error_response()
+
+ with pytest.raises(Context.CommandError):
+ self._command.execute(self._fake_context)
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/test/python/apache/aurora/client/cli/test_options.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/cli/test_options.py b/src/test/python/apache/aurora/client/cli/test_options.py
index 21d5888..f2aae57 100644
--- a/src/test/python/apache/aurora/client/cli/test_options.py
+++ b/src/test/python/apache/aurora/client/cli/test_options.py
@@ -17,7 +17,12 @@ from argparse import ArgumentTypeError
import pytest
-from apache.aurora.client.cli.options import parse_instances
+from apache.aurora.client.cli.options import (
+ binding_parser,
+ instance_specifier,
+ parse_instances,
+ parse_task_instance_key
+)
class TestParseInstances(unittest.TestCase):
@@ -34,3 +39,23 @@ class TestParseInstances(unittest.TestCase):
with pytest.raises(ArgumentTypeError):
parse_instances("1-0")
+
+ def test_instance_specifier_fails_on_empty(self):
+ with pytest.raises(ArgumentTypeError):
+ instance_specifier("")
+
+ def test_parse_task_instance_key_missing_instance(self):
+ with pytest.raises(ArgumentTypeError):
+ parse_task_instance_key("cluster/role/env/name")
+
+ def test_parse_task_instance_key_fails_on_range(self):
+ with pytest.raises(ArgumentTypeError):
+ parse_task_instance_key("cluster/role/env/name/0-5")
+
+ def binding_parser_fails_on_invalid_parts(self):
+ with pytest.raises(ArgumentTypeError):
+ binding_parser("p=")
+
+ def binding_parser_fails_parsing(self):
+ with pytest.raises(ArgumentTypeError):
+ binding_parser("p=2342")
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/test/python/apache/aurora/client/hooks/test_hooked_api.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/hooks/test_hooked_api.py b/src/test/python/apache/aurora/client/hooks/test_hooked_api.py
index 67517a2..eb97c61 100644
--- a/src/test/python/apache/aurora/client/hooks/test_hooked_api.py
+++ b/src/test/python/apache/aurora/client/hooks/test_hooked_api.py
@@ -20,7 +20,8 @@ from apache.aurora.client.api import AuroraClientAPI
from apache.aurora.client.hooks.hooked_api import HookedAuroraClientAPI, NonHookedAuroraClientAPI
from apache.aurora.common.cluster import Cluster
-API_METHODS = ('create_job', 'kill_job', 'restart', 'start_cronjob', 'start_job_update')
+API_METHODS = ('add_instances', 'create_job', 'kill_job', 'restart',
+ 'start_cronjob', 'start_job_update')
API_METHODS_WITH_CONFIG_PARAM_ADDED = ('kill_job', 'restart', 'start_cronjob')
http://git-wip-us.apache.org/repos/asf/aurora/blob/46277a11/src/test/python/apache/aurora/client/hooks/test_non_hooked_api.py
----------------------------------------------------------------------
diff --git a/src/test/python/apache/aurora/client/hooks/test_non_hooked_api.py b/src/test/python/apache/aurora/client/hooks/test_non_hooked_api.py
index f4b771b..ca20ba5 100644
--- a/src/test/python/apache/aurora/client/hooks/test_non_hooked_api.py
+++ b/src/test/python/apache/aurora/client/hooks/test_non_hooked_api.py
@@ -18,8 +18,7 @@ import unittest
from apache.aurora.client.hooks.hooked_api import NonHookedAuroraClientAPI
from apache.aurora.common.aurora_job_key import AuroraJobKey
-API_METHODS = ('create_job', 'kill_job', 'restart', 'start_cronjob')
-API_METHODS_WITH_CONFIG_PARAM_ADDED = ('kill_job', 'restart', 'start_cronjob')
+API_METHODS = ('add_instances', 'create_job', 'kill_job', 'restart', 'start_cronjob')
class TestNonHookedAuroraClientAPI(unittest.TestCase):