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 <[email protected]> Authored: Wed Feb 10 15:12:36 2016 -0800 Committer: Maxim Khutornenko <[email protected]> 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):
