def split(self) -> Apps: if self.is_read_request(): if self.is_list_apps_request(): apps = self.marathon_client.list_apps() for app in apps: yield self.merge_marathon_apps(MarathonApp(), app), app elif self.is_app_request(): app = self._get_original_app(self.request.user, self.object_id) yield self.merge_marathon_apps(MarathonApp(), app), app elif self.is_group_request(): self.group = self._get_original_group(self.request.user, self.object_id) for app in self.group.iterate_apps(): yield self.merge_marathon_apps(MarathonApp(), app), app return # Request is a WRITE if self.is_app_request(): for app in self.get_request_data(): request_app = MarathonApp.from_json(app) app = self._get_original_app(self.request.user, self.object_id or request_app.id) yield self.merge_marathon_apps(request_app, app), app elif self.is_tasks_request(): request_data = self.request.get_json() for task_id in request_data['ids']: request_task = MarathonTask.from_json({"id": task_id}) yield request_task, request_task return
def test_split_groups_read_on_specific_group(self, group_b_fixture): with application.test_request_context('/v2/groups/group-b', method='GET') as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with RequestsMock() as rsps: rsps.add(method='GET', url=conf.MARATHON_ADDRESSES[0] + '/v2/groups//dev/group-b', body=json.dumps(group_b_fixture), status=200) apps = list(request_parser.split()) self.assertEqual(2, len(apps)) original_app_one = MarathonApp.from_json( {"id": "/dev/group-b/appb0"}) original_app_two = MarathonApp.from_json( {"id": "/dev/group-b/group-b0/app0"}) expected_apps = [ (request_parser.merge_marathon_apps( MarathonApp(), original_app_one), original_app_one), (request_parser.merge_marathon_apps( MarathonApp(), original_app_two), original_app_two), ] self.assertEqual(expected_apps, apps)
def test_can_read_app_if_already_migrated(self, single_full_app_fixture): """ Conferimos que é possível fazer um GET em /v2/apps/<app-id> para uma app que já está migrada. O <app-id> usado é sempre *sem* namespace """ request_data = deepcopy(single_full_app_fixture) single_full_app_fixture["id"] = "/dev/foo" with application.test_request_context("/v2/apps/foo", method="GET") as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with RequestsMock() as rsps: rsps.add( method="GET", url=conf.MARATHON_ADDRESSES[0] + "/v2/apps//dev/foo", body=json.dumps({"app": single_full_app_fixture}), status=200, ) apps = list(request_parser.split()) original_app = MarathonApp.from_json(single_full_app_fixture) expected_app = ( request_parser.merge_marathon_apps(MarathonApp(), original_app), original_app, ) self.assertEqual(apps, [expected_app])
def test_change_request_path_if_is_read_single_app( self, single_full_app_fixture): with application.test_request_context('/v2/apps/foo', method='GET') as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) single_full_app_fixture['id'] = "/dev/foo" apps = [(MarathonApp.from_json(single_full_app_fixture), MarathonApp.from_json(single_full_app_fixture))] request = request_parser.join(apps) self.assertIsInstance(request, HollowmanRequest) self.assertEqual("/v2/apps/dev/foo", request.path)
def test_split_groups_write_PUT_on_group(self): """ Atualmente, o único body que chega em um PUT em /v2/groups é: {"scaleBy": <N>} onde `<N>` é o fator que será multiplicado pelo atual número de TASK_RUNNING de cada app. O problema é que o Request.split() retorna uma lista de apps, e o Request.join() potencialmente vai reconstruir um body com essa lista de apps. O problema é que isso gera um request body *diferente* do orignal, já que agora temos um body contendo um APP_GROUP com todas suas apps (e sub apps). E se fazemos apenas isso, a informação do "scaleBy" se perdeu, pois se mandamos um request com o TASK_GROUP inteiro para o upstream, nada vai mudar já que as apps não foram modificadas. Uma ideia é o Core do hollowman decobrir essa ação de scaleBy e chamar o métoro "scale_by" dos filtros, já com a request_app tendo seu atributo "instances" multiplicado pelo fator. Opcionalmente o fator poderia ser passado como parametro para o filtro. Isso nos daria a possibilidade de "corrigir" um problema atual do scaleby que é: Quando damos scale_by = 2 em um app que está suspended, ela continua suspended já que 2 * 0 = 0. A ideia é que suspended apps também sejam ligadas considerando esse fator. O que faríamos no filtro seria, para toda app que instances = 0, consideramos instances = 1 e multiplicamos pelo fator. Enfim, apenas uma ideia. Temos que ver o que fazemos com esse teste aqui. """ with application.test_request_context("/v2/groups/group-b", method="PUT") as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with RequestsMock() as rsps: rsps.add( method="GET", url=conf.MARATHON_ADDRESSES[0] + "/v2/groups//dev/group-b", body=json.dumps(group_b_fixture), status=200, ) apps = list(request_parser.split()) self.assertEqual(2, len(apps)) expected_apps = [ ( MarathonApp(), MarathonApp.from_json({"id": "/dev/group-b/appb0"}), ), ( MarathonApp(), MarathonApp.from_json( {"id": "/dev/group-b/group-b0/app0"}), ), ] self.assertEqual(expected_apps, apps)
def setUp(self, single_full_app_fixture): self.request_apps = [ (MarathonApp(id='/xablau'), MarathonApp(id='/xena')), (MarathonApp(id='/foo'), MarathonApp(id='/bar')), ] rebuild_schema() self.session = HollowmanSession() self.user = User(tx_email="*****@*****.**", tx_name="John Doe", tx_authkey="69ed620926be4067a36402c3f7e9ddf0") self.account_dev = Account(id=4, name="Dev Team", namespace="dev", owner="company") self.user.accounts = [self.account_dev] self.session.add(self.user) self.session.add(self.account_dev) self.session.commit()
def test_a_read_single_app_request_returns_a_single_marathonapp_if_app_exists( self, fixture): with application.test_request_context('/v2/apps//foo', method='GET', data=b'') as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with patch.object(request_parser, 'marathon_client') as client: original_app = MarathonApp.from_json(fixture) client.get_app.return_value = original_app apps = list(request_parser.split()) self.assertEqual(apps, [(request_parser.merge_marathon_apps( MarathonApp(), original_app), client.get_app.return_value)])
def test_multiapp_response_returns_multiple_marathonapp_instances(self, fixture): modified_app = fixture.copy() modified_app['id'] = '/xablau' apps = [fixture, modified_app] with application.test_request_context('/v2/apps/', method='GET', data=b'') as ctx: response = FlaskResponse(response=json.dumps({"apps": apps}), status=HTTPStatus.OK, headers={}) response = Response(ctx.request, response) with patch.object(response, 'marathon_client') as client: original_apps = [MarathonApp.from_json(app) for app in apps] client.get_app.side_effect = original_apps apps = list(response.split()) self.assertEqual([call("/foo"), call("/xablau")], client.get_app.call_args_list) self.assertEqual( apps, [ (AsgardApp.from_json(fixture), original_apps[0]), (AsgardApp.from_json(modified_app), original_apps[1]) ] )
def run_paasta_metastatus_high_cpu(context, app_id): context.marathon_client.create_app( app_id, MarathonApp(cmd='/bin/sleep 1000', cpus=9, instances=3, container=CONTAINER))
def run_paasta_metastatus_high_cpu(context, app_id): context.marathon_clients.current[0].create_app( app_id, MarathonApp( cmd="/bin/sleep 100000", cpus=9.1, instances=3, container=CONTAINER ), )
def test_join_one_app_should_produce_one_app_not_a_list(self, fixture): """ Um POST em /v2/apps, apesar de receber no body apens uma app ({...}), após o request.join(), restá produzindo um request com uma lista de apps: [{...}], e a API do marathon não aceita lista no POST apenas no PUT. O problema parece ser no request.join():89, onde fazemos if self.is_list_app_request(). Precisamos olhar se é PUT ou POST e gerar list() ou dict() apropriadamente. """ with application.test_request_context("/v2/apps/", method="POST", data=json.dumps(fixture)) as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) mock_app = get_fixture("single_full_app.json") mock_apps = [(MarathonApp.from_json(mock_app), Mock())] joined_request = request_parser.join(mock_apps) self.assertIsInstance(joined_request, HollowmanRequest) joined_request_data = json.loads(joined_request.data) self.assertFalse( isinstance(joined_request_data, list), "Body não deveria ser uma lista", ) self.assertEqual("/foo", joined_request_data["id"])
def deploy_marathon_app(client, marathon_json, sleep_secs=10, retries=3): app_id = marathon_json['id'] CONFIG_URI = os.getenv('CONFIG_URI') if CONFIG_URI: marathon_json['uris'].append(CONFIG_URI) print("Attempting deploy Marathon app with id: %s" % app_id) print(marathon_json, file=sys.stderr) marathon_app = MarathonApp.from_json(marathon_json) # We are going to retry, in the case of blocked deployments attempt = 0 while attempt < retries: try: try: client.get_app(app_id) response = client.update_app(app_id, marathon_app) except NotFoundError: response = client.create_app(app_id, marathon_app) print(response, file=sys.stderr) print('Deployment succeeded.') break except Exception, ex: attempt += 1 print(ex.message) print('Failure attempting to deploy app. Retrying...') time.sleep(sleep_secs)
def _get_original_app(self, user, app_id): app_id_with_namespace = "/{}/{}".format(user.current_account.namespace, app_id.strip("/")) try: return self.marathon_client.get_app(app_id_with_namespace) except NotFoundError as e: return MarathonApp.from_json({"id": app_id_with_namespace})
def test_multiapp_response_returns_multiple_marathonapp_instances( self, fixture): modified_app = fixture.copy() modified_app["id"] = "/xablau" apps = [fixture, modified_app] with application.test_request_context("/v2/apps/", method="GET", data=b"") as ctx: response = FlaskResponse( response=json.dumps({"apps": apps}), status=HTTPStatus.OK, headers={}, ) response = Response(ctx.request, response) with patch.object(response, "marathon_client") as client: original_apps = [MarathonApp.from_json(app) for app in apps] client.get_app.side_effect = original_apps apps = list(response.split()) self.assertEqual( apps, [ (AsgardApp.from_json(fixture), original_apps[0]), (AsgardApp.from_json(modified_app), original_apps[1]), ], )
def test_empty_labels(self): app_dict = {"labels": {}} request_app = MarathonApp.from_json(app_dict) filtered_app = self.filter.write(Mock(), request_app, Mock()) filtered_app = filtered_app.json_repr() self.assertDictEqual(filtered_app["labels"], {})
async def setUp(self): await super(RequestHandlersTests, self).setUp() self.request_apps = [ (MarathonApp(id="/xablau"), MarathonApp(id="/xena")), (MarathonApp(id="/foo"), MarathonApp(id="/bar")), ] self.user = UserDB( tx_email="*****@*****.**", tx_name="John Doe", tx_authkey="69ed620926be4067a36402c3f7e9ddf0", ) self.account_dev = AccountDB(id=4, name="Dev Team", namespace="dev", owner="company") self.user.accounts = [self.account_dev]
def test_adjust_apps_request_path_repeating_non_final_path(self): with application.test_request_context('/v2/apps/app0/app0/app1', method='GET') as ctx: request_wrapper = Request(ctx.request) original_app = MarathonApp(id="/dev/app0/app0/app1") request_wrapper._adjust_request_path_if_needed( request_wrapper.request, original_app) self.assertEqual("/v2/apps/dev/app0/app0/app1", request_wrapper.request.path)
def test_adjust_apps_request_path_no_aaditional_paths(self): with application.test_request_context('/v2/apps/my-app', method='GET') as ctx: request_wrapper = Request(ctx.request) original_app = MarathonApp(id="/dev/my-app") request_wrapper._adjust_request_path_if_needed( request_wrapper.request, original_app) self.assertEqual("/v2/apps/dev/my-app", request_wrapper.request.path)
def test_adjust_apps_tasks_task_id_DELETE_request_path(self): with application.test_request_context('/v2/apps/my-app/tasks/task_id', method='DELETE') as ctx: request_wrapper = Request(ctx.request) original_app = MarathonApp(id="/dev/my-app") request_wrapper._adjust_request_path_if_needed( request_wrapper.request, original_app) self.assertEqual("/v2/apps/dev/my-app/tasks/task_id", request_wrapper.request.path)
def test_adjust_apps_versions_request_path(self): with application.test_request_context("/v2/apps/my-app/versions", method="GET") as ctx: request_wrapper = Request(ctx.request) original_app = MarathonApp(id="/dev/my-app") request_wrapper._adjust_request_path_if_needed( request_wrapper.request, original_app) self.assertEqual("/v2/apps/dev/my-app/versions", request_wrapper.request.path)
def test_a_request_for_restart_operation_with_appid_in_url_path_returns_a_tuple_of_marathonapp( self, fixture): with application.test_request_context('/v2/apps/xablau/restart', method='PUT', data=b'{"force": true}') as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with RequestsMock() as rsps: rsps.add(method='GET', url=conf.MARATHON_ADDRESSES[0] + '/v2/apps//dev/xablau', body=json.dumps({'app': fixture}), status=200) apps = list(request_parser.split()) original_app = MarathonApp.from_json(fixture) expected_app = (request_parser.merge_marathon_apps( MarathonApp(), original_app), original_app) self.assertEqual(apps, [expected_app])
def run_paasta_metastatus_high_disk(context, app_id): context.marathon_clients.current[0].create_app( app_id, MarathonApp( cmd='/bin/sleep 100000', disk=95, instances=3, container=CONTAINER, ), )
def test_a_request_for_write_operation_with_appid_in_url_path_returns_a_tuple_of_marathonapp( self, fixture): scale_up = {'instances': 10} with application.test_request_context( '/v2/apps/foo', method='PUT', data=json.dumps(scale_up)) as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with RequestsMock() as rsps: rsps.add(method='GET', url=conf.MARATHON_ADDRESSES[0] + '/v2/apps//dev/foo', body=json.dumps({'app': fixture}), status=200) apps = list(request_parser.split()) original_app = MarathonApp.from_json(fixture) expected_apps = (request_parser.merge_marathon_apps( MarathonApp.from_json(scale_up), original_app), original_app) self.assertEqual(apps, [expected_apps])
def test_split_does_not_break_when_removing_force_parameter_if_request_is_a_list( self, fixture): request_data = {"id": "/foo", "instances": 2} with application.test_request_context( '/v2/apps/', method='PUT', data=json.dumps(request_data)) as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with RequestsMock() as rsps: rsps.add(method='GET', url=conf.MARATHON_ADDRESSES[0] + '/v2/apps//dev/foo', body=json.dumps({'app': fixture}), status=200) apps = list(request_parser.split()) original_app = MarathonApp.from_json(fixture) expected_app = (request_parser.merge_marathon_apps( MarathonApp.from_json(request_data), original_app), original_app) self.assertEqual(apps, [expected_app])
def test_adjust_apps_request_path_keep_additional_paths_multiple_paths( self): with application.test_request_context( '/v2/apps/my-app/versions/2017-10-31T13:01:07.768Z', method='GET') as ctx: request_wrapper = Request(ctx.request) original_app = MarathonApp(id="/dev/my-app") request_wrapper._adjust_request_path_if_needed( request_wrapper.request, original_app) self.assertEqual( "/v2/apps/dev/my-app/versions/2017-10-31T13:01:07.768Z", request_wrapper.request.path)
def __init__(self, scheduler, executable='dask-worker', docker_image='mrocklin/dask-distributed', marathon_address='http://localhost:8080', username=None, password=None, auth_token=None, app_name=None, **kwargs): self.scheduler = scheduler self.executor = ThreadPoolExecutor(1) # Create Marathon App to run dask-worker args = [ executable, scheduler.address, '--name', '$MESOS_TASK_ID', # use Mesos task ID as worker name '--worker-port', '$PORT_WORKER', '--nanny-port', '$PORT_NANNY', '--http-port', '$PORT_HTTP' ] ports = [{ 'port': 0, 'protocol': 'tcp', 'name': port_name } for port_name in ['worker', 'nanny', 'http']] if 'mem' in kwargs: args.extend( ['--memory-limit', str(int(kwargs['mem'] * 0.6 * 1e6))]) kwargs['cmd'] = ' '.join(args) container = MarathonContainer({'image': docker_image}) app = MarathonApp(instances=0, container=container, port_definitions=ports, **kwargs) # Connect and register app self.client = MarathonClient(servers=marathon_address, username=username, password=password, auth_token=auth_token) self.app = self.client.create_app(app_name or 'dask-%s' % uuid.uuid4(), app)
def do_full_rollback(client: MarathonClient, rollback: list): print('------------------\nPerforming rollback in order:') print('\n'.join(rollback)) print('------------------') for each in rollback: if os.path.isfile(each): with open(each) as json_file: app = MarathonApp.from_json(json.load(json_file)) _update_application(client, app, each, False) else: deployment = client.delete_app(each, True) wait_for_deployment(client, deployment)
def test_it_recreates_a_post_request_for_a_single_app(self, fixture): with application.test_request_context('/v2/apps//foo', method='POST', data=json.dumps(fixture)) as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with patch.object(request_parser, 'marathon_client') as client: client.get_app.return_value = MarathonApp.from_json(fixture) apps = list(request_parser.split()) request = request_parser.join(apps) self.assertIsInstance(request, HollowmanRequest) self.assertEqual(request.get_json()['id'], '/foo')
def test_it_recreates_a_get_request_for_a_single_app(self, fixture): with application.test_request_context("/v2/apps//foo", method="GET", data=b"") as ctx: ctx.request.user = self.user request_parser = Request(ctx.request) with patch.object(request_parser, "marathon_client") as client: client.get_app.return_value = MarathonApp.from_json(fixture) apps = list(request_parser.split()) request = request_parser.join(apps) self.assertIsInstance(request, HollowmanRequest) self.assertEqual(request, ctx.request) self.assertEqual(request.data, b"")
def register_services(self, service_registry="conf/marathon"): for app_def in glob.glob(os.path.join(service_registry, "*json")): with open(app_def, "r") as stream: args = json.loads(stream.read()) app_id = args['id'] args = Names.snake_case(args) logger.debug("Creating service: %s", json.dumps(args, indent=2)) args['tasks'] = [] app = MarathonApp(**args) try: logging.info("Creating app [id=>{0}]".format(app_id)) self.marathon.create_app(app_id, app) except: traceback.print_exc()
def create_app_from_json(self, json_data ): a = MarathonApp.from_json(json_data) return MarathonClient.create_app(self, a.id, a)
def update_app_from_json( self, json_data, force ): a = MarathonApp.from_json(json_data) return MarathonClient.update_app(self, a.id, a, force)