Skip to content

Instantly share code, notes, and snippets.

@jfsmig

jfsmig/README.md Secret

Last active March 12, 2019 21:44
Show Gist options
  • Save jfsmig/fbad7342186715134869cbe6b66cea9a to your computer and use it in GitHub Desktop.
Save jfsmig/fbad7342186715134869cbe6b66cea9a to your computer and use it in GitHub Desktop.
QA exercise

QA exercise

Pour le temps imparti, les exercices sont volontairement trop nombreux et/ou difficiles et/ou peu spécifiés. Ainsi va la vie professionnelle de l'ingénieur: des problèmes flous, à résoudre le mieux possible avec des ressources limitées. Nous serons sensibles aux critères qui ont dirigé les choix, c'est pourquoi nous demandons que la démarche soit expliquée.

Quand aucun contrainte n'est mentionnée, vous avez toute liberté de choix dans la résolution des exercices. Pas de problème pour faire appel à une bibliothèque externe ou à une application tierse.

Contexte

Nous disposons d'un outil de benchmark décomposé en un contrôleur et un ensemble d'agents. Les agents génèrent une charge sur un service visé, ils sont destinés à être multipliés. Le contrôleur communique vers ses agents à l'aide d'une socket ZeroMQ dans laquelle il émet les ordres à faire exécuter. Il attend en retour le resultat de l'opération, composé du code de retour et du temps dépensé.

Chaque contrôleurs disposer d'une socket ZeroMQ "DEALER" qui est bind'ée sur une adresse TCP/IP. Chaque agent reçoit la liste des contrôleurs auxquels se connecter.

Pour démarrer un contrôleur:

python benchmark.py --controller tcp://127.0.0.1:6000

Pour démarrer un agent:

python benchmark.py --worker tcp://127.0.0.1:6000

Exercice 1: Déploiement

Nous souhaitons disposer d'une image Docker pour déployer simplement un agent. Actuellement, le seul paramètre qu'il convient de donner au lancement de chaque agent est la liste des adresses des contrôleurs auxquels il doit se connecter.

Exercice 2: Un peu de code

Le code de l'agent ne fait rien, alors implémentez action() pour solliciter un service HTTP qui répond à des méthodes PUT/GET/DELETE sur des objets identifiés par un identifiant unique.

A titre d'exemple, imaginez une version simplifiée du service rawx de OpenIO SDS documenté ici.

Exercice 3: Discovery

Il est trop rigide de devoir expliciter démarrage des agents quels sont les contrôleurs qui vont les piloter. Nous souhaitons disposer d'une ferme d'agents qui soit stable, mais qui va devoir s'adapter à des contrôleurs plus volatiles.

Par exemple, parce que nous souhaitons disposer de plusieurs types de contrôleurs, chacun étant capable de piloter un scénario spécifique.

Présentez un exemple de solution (son design seulement, pas de code).

Exercice 4: au choix

Ramp Up

Le code du contrôleur envoie aussi fort que possible, dans les limites de la capacité des agents à consommer les messages.

Il vous est demandé ici d'implémenter une "rampe" qui augmente progressivement la fréquence d'envoi et rapporte la fréquence à partir de laquelle la queue n'est plus consommée assez rapidement (empilement).

Scenario

Proposez un design (i.e. il n'est pas nécessaire de coder) pour une évolution de l'outil pour qu'il travaille sur une population donnée d'objets, et que pour chacune il fasse évoluer un scénario (roundtrip) à base de PUT, GET et DELETE.

Performance

Cet outil est sous-performant. Expliquez les pistes d'amélioration qui existent selon vous, et comment vous en choisiriez une.

#!/usr/bin/env python
import zmq
from time import time
def action(command, object_id):
# TODO implement the RAWX action and return the HTTP code
return 501
def worker(zctx, endpoints):
zcmd = zctx.socket(zmq.REP)
for endpoint in endpoints:
zcmd.connect(endpoint)
try:
while True:
message = zcmd.recv_string()
action_id, command, object_id = message.split()
pre = time()
rc = action(command, object_id)
post = time()
zcmd.send_string("%s %d %0.6f" % (action_id, rc, post - pre))
except KeyboardInterrupt:
pass
zcmd.close()
def controller(zctx, endpoints):
zcmd = zctx.socket(zmq.DEALER)
for endpoint in endpoints:
zcmd.bind(endpoint)
try:
reqid = 0
while True:
events = zcmd.poll(timeout=1000, flags=zmq.POLLIN | zmq.POLLOUT)
if events & zmq.POLLIN:
zcmd.recv()
reply = zcmd.recv_string()
print reply
if events & zmq.POLLOUT:
cmd = ('PUT', 'GET', 'DELETE')
obj = "0123456789BACDEF" * 4
action = cmd[reqid % 3]
zcmd.send("", flags=zmq.SNDMORE)
zcmd.send_string("{0} {1} {2}".format(reqid, action, obj))
reqid += 1
except KeyboardInterrupt:
pass
zcmd.close()
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser("Benchmark tool")
parser.add_argument("--worker",
dest="worker", action='store_true', default=False,
help='Start a worker connecter to the given addresses')
parser.add_argument("--controller",
dest="controller", action='store_true', default=False,
help='Start a controller bond to the given addresses')
parser.add_argument("endpoints",
metavar='ENDPOINT', type=str, nargs='+',
help='Endpoints to connect/bind to')
args = parser.parse_args()
zctx = zmq.Context()
if args.controller:
controller(zctx, args.endpoints)
elif args.worker:
worker(zctx, args.endpoints)
else:
parser.print_help()
zctx.term()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment