def test_start_update_and_wait_success(self): mock_config = self.create_mock_config() self._fake_context.get_job_config = Mock(return_value=mock_config) self._mock_options.wait = True resp = self.create_simple_success_response() resp.result = Result(startJobUpdateResult=StartJobUpdateResult( key=JobUpdateKey(job=JobKey( role="role", environment="env", name="name"), id="id"))) self._mock_api.start_job_update.return_value = resp self._mock_api.query_job_updates.side_effect = [ get_status_query_response(status=JobUpdateStatus.ROLLED_FORWARD) ] assert self._command.execute(self._fake_context) == EXIT_OK assert self._mock_api.start_job_update.mock_calls == [ call(ANY, None, None) ] assert self._mock_api.query_job_updates.mock_calls == [ call(update_key=resp.result.startJobUpdateResult.key) ] assert self._fake_context.get_out() == [ StartUpdate.UPDATE_MSG_TEMPLATE % ('http://something_or_other/scheduler/role/env/name/update/id'), 'Current state ROLLED_FORWARD' ] assert self._fake_context.get_err() == []
def test_start_update_command_line_succeeds(self): resp = self.create_simple_success_response() resp.result = Result(startJobUpdateResult=StartJobUpdateResult( key=JobUpdateKey(job=JobKey( role="role", environment="env", name="name"), id="id"))) self._mock_api.start_job_update.return_value = resp mock_config = self.create_mock_config() self._fake_context.get_job_config = Mock(return_value=mock_config) self._mock_options.instance_spec = TaskInstanceKey(self._job_key, None) self._mock_options.message = 'hello' with patch( 'apache.aurora.client.cli.update.DiffFormatter') as formatter: formatter.return_value = self._formatter assert self._command.execute(self._fake_context) == EXIT_OK assert self._formatter.show_job_update_diff.mock_calls == [ call(self._mock_options.instance_spec.instance) ] assert self._mock_api.start_job_update.mock_calls == [ call(ANY, 'hello', None, ANY) ] assert self._fake_context.get_out() == [ StartUpdate.UPDATE_MSG_TEMPLATE % ('http://something_or_other/scheduler/role/env/name/update/id'), ] assert self._fake_context.get_err() == []
def execute(self, context): return wait_for_update( context, self._clock, context.get_api(context.options.jobspec.cluster), JobUpdateKey(job=context.options.jobspec.to_thrift(), id=context.options.id), update_state_to_err_code )
def test_get_job_update_details(self): """Test getting job update details.""" api, mock_proxy = self.mock_api() key = JobUpdateKey(job=JobKey(role="role", environment="env", name="name"), id="id") api.get_job_update_details(key) mock_proxy.getJobUpdateDetails.assert_called_once_with(key)
def setUp(self): self._command = UpdateWait(clock=mock.create_autospec(spec=time)) self._mock_options = mock_verb_options(self._command) self._mock_options.jobspec = self.TEST_JOBKEY self._mock_options.id = 'update_id' self._fake_context = FakeAuroraCommandContext() self._fake_context.set_options(self._mock_options) self._mock_api = self._fake_context.get_api('UNUSED') self._fetch_call = call(update_key=JobUpdateKey( job=self.TEST_JOBKEY.to_thrift(), id=self._mock_options.id))
def test_start_update_and_wait_error(self): mock_config = self.create_mock_config() self._fake_context.get_job_config = Mock(return_value=mock_config) self._mock_options.wait = True resp = self.create_simple_success_response() resp.result = Result(startJobUpdateResult=StartJobUpdateResult( key=JobUpdateKey(job=JobKey(role="role", environment="env", name="name"), id="id"))) self._mock_api.start_job_update.return_value = resp self._mock_api.query_job_updates.side_effect = [ get_status_query_response(status=JobUpdateStatus.ERROR) ] with patch('apache.aurora.client.cli.update.DiffFormatter'): assert self._command.execute(self._fake_context) == EXIT_UNKNOWN_ERROR
def test_start_update_and_wait_rollback(self): mock_config = self.create_mock_config() self._fake_context.get_job_config = Mock(return_value=mock_config) self._mock_options.wait = True resp = self.create_simple_success_response() resp.result = Result(startJobUpdateResult=StartJobUpdateResult( key=JobUpdateKey(job=JobKey( role="role", environment="env", name="name"), id="id"))) self._mock_api.start_job_update.return_value = resp self._mock_api.query_job_updates.side_effect = [ get_status_query_response(status=JobUpdateStatus.ROLLED_BACK) ] assert self._command.execute( self._fake_context) == EXIT_COMMAND_FAILURE
def test_start_update_and_open_page(self): mock_config = self.create_mock_config() self._fake_context.get_job_config = Mock(return_value=mock_config) self._mock_options.open_browser = True resp = self.create_simple_success_response() resp.result = Result(startJobUpdateResult=StartJobUpdateResult( key=JobUpdateKey(job=JobKey(role="role", environment="env", name="name"), id="id"))) self._mock_api.start_job_update.return_value = resp with patch('apache.aurora.client.cli.update.DiffFormatter') as formatter: formatter.return_value = self._formatter assert self._command.execute(self._fake_context) == EXIT_OK assert self.mock_webbrowser.mock_calls == [ call('http://something_or_other/scheduler/role/env/name/update/id') ]
def test_start_update_command_line_succeeds(self): resp = self.create_simple_success_response() resp.result = Result(startJobUpdateResult=StartJobUpdateResult( key=JobUpdateKey(job=JobKey( role="role", environment="env", name="name"), id="id"))) self._mock_api.start_job_update.return_value = resp mock_config = self.create_mock_config() self._fake_context.get_job_config = Mock(return_value=mock_config) self._mock_options.instance_spec = TaskInstanceKey(self._job_key, None) self._mock_options.message = 'hello' assert self._command.execute(self._fake_context) == EXIT_OK update_url_msg = StartUpdate.UPDATE_MSG_TEMPLATE % ( 'http://something_or_other/scheduler/role/env/name/id') assert self._mock_api.start_job_update.mock_calls == [ call(ANY, 'hello', None) ] assert self._fake_context.get_out() == [update_url_msg] assert self._fake_context.get_err() == []
class TestJobUpdateApis(unittest.TestCase): """Job update APIs tests.""" UPDATE_CONFIG = { 'batch_size': 1, 'watch_secs': 50, 'max_per_shard_failures': 2, 'max_total_failures': 1, 'rollback_on_failure': True, 'wait_for_batch_completion': False, } JOB_KEY = AuroraJobKey("foo", "role", "env", "name") UPDATE_KEY = JobUpdateKey(job=JOB_KEY.to_thrift(), id="id") @classmethod def create_blank_response(cls, code, msg): return Response(responseCode=code, details=[ResponseDetail(message=msg)], result=create_autospec(spec=Result, spec_set=True, instance=True)) @classmethod def create_simple_success_response(cls): return cls.create_blank_response(ResponseCode.OK, 'OK') @classmethod def create_error_response(cls): return cls.create_blank_response(ResponseCode.ERROR, 'ERROR') @classmethod def mock_api(cls): api = AuroraClientAPI(Cluster(name="foo"), 'test-client') mock_proxy = create_autospec(spec=SchedulerProxyApiSpec, spec_set=True, instance=True) api._scheduler_proxy = mock_proxy return api, mock_proxy @classmethod def create_update_settings(cls): return JobUpdateSettings( updateGroupSize=1, updateStrategy=JobUpdateStrategy( queueStrategy=QueueJobUpdateStrategy(groupSize=1), batchStrategy=None, varBatchStrategy=None), maxPerInstanceFailures=2, maxFailedInstances=1, minWaitInInstanceRunningMs=50 * 1000, rollbackOnFailure=True, waitForBatchCompletion=False, slaAware=False) @classmethod def create_update_request(cls, task_config): return JobUpdateRequest(instanceCount=5, settings=cls.create_update_settings(), taskConfig=task_config) @classmethod def mock_job_config(cls, error=None): config = create_autospec(spec=AuroraConfig, instance=True) update_config = UpdateConfig(batch_size=1, watch_secs=50, max_per_shard_failures=2, max_total_failures=1) if error: config.update_config.side_effect = error else: config.update_config.return_value = update_config mock_task_config = create_autospec(spec=JobConfiguration, instance=True) mock_task_config.taskConfig = TaskConfig() config.job.return_value = mock_task_config 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( 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() task_config = TaskConfig() mock_proxy.startJobUpdate.return_value = self.create_simple_success_response( ) api.start_job_update(self.mock_job_config(), instances=None, message='hello') mock_proxy.startJobUpdate.assert_called_once_with( self.create_update_request(task_config), 'hello', retry=True) def test_start_job_update_fails_parse_update_config(self): """Test start_job_update fails to parse invalid UpdateConfig.""" api, mock_proxy = self.mock_api() self.assertRaises(AuroraClientAPI.UpdateConfigError, api.start_job_update, self.mock_job_config(error=ValueError()), None) def test_get_job_update_diff(self): """Test getting job update diff.""" api, mock_proxy = self.mock_api() task_config = TaskConfig() mock_proxy.getJobUpdateDiff.return_value = self.create_simple_success_response( ) api.get_job_update_diff(self.mock_job_config(), instances=None) mock_proxy.getJobUpdateDiff.assert_called_once_with( self.create_update_request(task_config), retry=True) def test_pause_job_update(self): """Test successful job update pause.""" api, mock_proxy = self.mock_api() mock_proxy.pauseJobUpdate.return_value = self.create_simple_success_response( ) api.pause_job_update(self.UPDATE_KEY, message='hello') mock_proxy.pauseJobUpdate.assert_called_once_with( self.UPDATE_KEY, 'hello') def test_resume_job_update(self): """Test successful job update resume.""" api, mock_proxy = self.mock_api() mock_proxy.resumeJobUpdate.return_value = self.create_simple_success_response( ) api.resume_job_update(self.UPDATE_KEY, message='hello') mock_proxy.resumeJobUpdate.assert_called_once_with( self.UPDATE_KEY, 'hello') def test_query_job_updates(self): """Test querying job updates.""" api, mock_proxy = self.mock_api() job_key = AuroraJobKey("foo", "role", "env", "name") query = JobUpdateQuery( jobKey=job_key.to_thrift(), updateStatuses={JobUpdateStatus.ROLLING_FORWARD}) api.query_job_updates(job_key=job_key, update_statuses=query.updateStatuses) mock_proxy.getJobUpdateSummaries.assert_called_once_with(query, retry=True) def test_query_job_updates_no_filter(self): """Test querying job updates with no filter args.""" api, mock_proxy = self.mock_api() query = JobUpdateQuery() api.query_job_updates() mock_proxy.getJobUpdateSummaries.assert_called_once_with(query, retry=True) def test_get_job_update_details(self): """Test getting job update details.""" api, mock_proxy = self.mock_api() key = JobUpdateKey(job=JobKey(role="role", environment="env", name="name"), id="id") api.get_job_update_details(key) query = JobUpdateQuery(key=key) mock_proxy.getJobUpdateDetails.assert_called_once_with(key, query, retry=True) def test_set_quota(self): """Test setting quota.""" api, mock_proxy = self.mock_api() api.set_quota("role", 1.0, 32, 64) actual = list(mock_proxy.setQuota.mock_calls[0][1][1].resources) assert Resource(numCpus=1.0) in actual assert Resource(ramMb=32) in actual assert Resource(diskMb=64) in actual
UpdateInfo, UpdateWait) from apache.aurora.common.aurora_job_key import AuroraJobKey from apache.aurora.config import AuroraConfig from apache.aurora.config.schema.base import Job from .util import AuroraClientCommandTest, FakeAuroraCommandContext, mock_verb_options from gen.apache.aurora.api.constants import ACTIVE_JOB_UPDATE_STATES from gen.apache.aurora.api.ttypes import ( GetJobUpdateDetailsResult, GetJobUpdateSummariesResult, JobInstanceUpdateEvent, JobKey, JobUpdate, JobUpdateAction, JobUpdateDetails, JobUpdateEvent, JobUpdateKey, JobUpdateState, JobUpdateStatus, JobUpdateSummary, Metadata, Response, ResponseCode, ResponseDetail, Result, StartJobUpdateResult) UPDATE_KEY = JobUpdateKey(job=AuroraClientCommandTest.TEST_JOBKEY.to_thrift(), id="update_id") def get_status_query_response(count=1, status=JobUpdateStatus.ROLLED_FORWARD): return Response( responseCode=ResponseCode.OK, result=Result(getJobUpdateSummariesResult=GetJobUpdateSummariesResult( updateSummaries=[ JobUpdateSummary(key=UPDATE_KEY, user="******", state=JobUpdateState( status=status, createdTimestampMs=1411404927, lastModifiedTimestampMs=14114056030)) for i in range(count) ])))
def execute(self, context): if context.options.id: key = JobUpdateKey(job=context.options.jobspec.to_thrift(), id=context.options.id) else: key = UpdateController( context.get_api(context.options.jobspec.cluster), context).get_update_key(context.options.jobspec) if key is None: context.print_err("There is no active update for this job.") return EXIT_INVALID_PARAMETER api = context.get_api(context.options.jobspec.cluster) response = api.get_job_update_details(key) context.log_response_and_raise(response) details = response.result.getJobUpdateDetailsResult.details if context.options.write_json: result = { "updateId": ("%s" % details.update.summary.key.id), "job": str(context.options.jobspec), "started": details.update.summary.state.createdTimestampMs, "last_modified": format_timestamp(details.update.summary.state.lastModifiedTimestampMs), "status": JobUpdateStatus._VALUES_TO_NAMES[details.update.summary.state.status], "update_events": [], "instance_update_events": [] } update_events = details.updateEvents if update_events is not None and len(update_events) > 0: for event in update_events: event_data = { "status": JobUpdateStatus._VALUES_TO_NAMES[event.status], "timestampMs": event.timestampMs } if event.message: event_data["message"] = event.message result["update_events"].append(event_data) instance_events = details.instanceEvents if instance_events is not None and len(instance_events) > 0: for event in instance_events: result["instance_update_events"].append({ "instance": event.instanceId, "timestamp": event.timestampMs, "action": JobUpdateAction._VALUES_TO_NAMES[event.action] }) context.print_out(json.dumps(result, indent=2, separators=[',', ': '], sort_keys=False)) else: context.print_out("Job: %s, UpdateID: %s" % (context.options.jobspec, details.update.summary.key.id)) context.print_out("Started %s, last activity: %s" % (format_timestamp(details.update.summary.state.createdTimestampMs), format_timestamp(details.update.summary.state.lastModifiedTimestampMs))) context.print_out("Current status: %s" % JobUpdateStatus._VALUES_TO_NAMES[details.update.summary.state.status]) update_events = details.updateEvents if update_events is not None and len(update_events) > 0: context.print_out("Update events:") for event in update_events: context.print_out("Status: %s at %s" % ( JobUpdateStatus._VALUES_TO_NAMES[event.status], format_timestamp(event.timestampMs) ), indent=2) if event.message: context.print_out(" message: %s" % event.message, indent=4) instance_events = details.instanceEvents if instance_events is not None and len(instance_events) > 0: context.print_out("Instance events:") for event in instance_events: context.print_out("Instance %s at %s: %s" % ( event.instanceId, format_timestamp(event.timestampMs), JobUpdateAction._VALUES_TO_NAMES[event.action] ), indent=2) return EXIT_OK