:: Enseignements :: ESIPE :: E4INFO :: 2019-2020 :: Java Avancé ::
[LOGO]

Projet de Java Avancé INFO2 2019


Exercice 1 - Square

Le but du projet Square est d'écrire une application permettant le déploiement et le contrôle d'autres applications à l'intérieur de conteneurs docker.
Square est composée :
  • d'une application serveur nommée square composée de deux types de services REST :
    • des services REST permettant de contrôler les applications déployées par un utilisateur ;
    • des services REST permettant la discussion entre square et les applications déployées ;
  • d'une librairie cliente nommée square-client qui est ajoutée dans chaque conteneur. Elle est accessible par chaque application déployée et peut discuter avec les services REST de discussion entre square et les applications déployées.

L’application doit permettre de :
  • Déployer et monitorer une application en créant un conteneur docker (pas d'en réutiliser un existant) autour d'une application puis en démarrant une instance du conteneur docker. L'application doit aussi pouvoir gérer l'ensemble des instances de conteneurs docker lancées, c'est à dire, lister les instances en cours d'exécution et offrir la possibilité de les arrêter.
    Une même application doit pouvoir être déployée plusieurs fois dans des instances différentes du même conteneur docker.
    Attention : si une application s'arrête à cause d'un bug ou est arrêtée par une commande docker, square doit se rendre compte que l'instance n'existe plus et ne plus la lister dans la liste des instances.
  • Regrouper l'ensemble des logs de chaque application dans toutes les instances des conteneurs docker et pouvoir afficher une vue filtrée des logs.
  • Démarrer et arrêter un service d'auto-scale qui permet d'assurer que le nombre d'instances d'une application qui tournent reste constant et supprimant ou démarrant de nouvelles instances.

  • La partie serveur, pour les différents services REST, doit utiliser le serveur d'API REST Quarkus.
  • La partie cliente HTTP doit utiliser l'API REST client de Quarkus.
  • Pour la sérialisation/dé-sérialisation JSON des requêtes, vous utiliserez une API de parsing JSON compatible avec Quarkus.
  • L'exécution de commandes docker doit se faire en utilisant la classe java.lang.ProcessBuilder.html.
    Attention à bien récupérer les erreurs potentielles !
  • Pour la persistance des logs vous pouvez soit utiliser Sqlite avec JPA, soit écrire dans des fichiers à plat. Vous pouvez utiliser Hibernate with panache si vous voulez.
  • Vous devez utiliser Maven comme outil de build. Remarque : la version eclipse-full (le plugin m2e) est capable de créer des projets Maven et de configurer automatiquement Eclipse à partir des dépendances du pom.xml.
  • Pour tester vos services REST, vous pouvez utiliser Postman sachant qu'à part la version macOs, les versions Linux et Windows sont en béta.
  • Les test unitaires Java doivent être effectués avec JUnit 5, vous pouvez vous référer au guide d'utilisation.
  • Le code est hébergé sur gitlab.com.
    Il est vivement encouragé d'utiliser le mécanisme de merge request entre les deux membres d'un binôme pour augmenter la qualité du code.
    Tout commit trop gros (qui introduit plusieurs features d'un coup) sera revert.

Sécurité :
  • Pas de HTTPS pour ce projet (c'est mal) mais c'est pour vous aider à débugger !
  • Les entrées des services web au niveau de l'URL ou de la partie JSON doivent être validées pour éviter les injections de code.
  • Il n'y a aucune raison que les conteneur docker s'exécutent en tant que root !
  • Si vous utilisez Sqlite comme base de données, il n'y a aucune raison d'avoir le login/mdp en dur dans votre code !

Description des services REST que doit posséder l'application square accessible par le port 8080 :
Attention, ses services vont être testés automatiquement donc le format des objets JSON en entrée doit être exactement celui demandé. Le format des objets JSON en sortie peut avoir des propriétés supplémentaires.
  • POST /app/deploy permet de démarrer une application à partir de son fichier jar situé dans le répertoire apps. Si le conteneur docker n'existe pas, celui-ci doit être créé dynamiquement.
    Le JSON en entrée doit spécifier le nom de l'application + le port sur lequel l'application va tourner.
    Par exemple
         {
           "app": "todomvc:8082"
         }
         

    Le JSON renvoyé doit spécifier un numéro unique de l'instance docker, le nom de l'application, le port de l'application, le port de discussion avec square, le nom de l'instance du conteneur docker (qui doit contenir le nom du conteneur docker)
         {
           "id": 201,
           "app": "todomvc:8082",
           "port": 8082,
           "service-port": 15201,
           "docker-instance": "todomvc-12"
         }
         
  • GET /app/list permet de lister l'ensemble des instances des conteneurs qui tournent
    Le JSON renvoyé doit contenir un tableau avec les mêmes informations que lors du déploiement ainsi que le temps d'exécution.
    Par exemple,
         [
           {
             "id": 201,
             "app": "todomvc:8082",
             "port": 8082,
             "service-port": 15201,
             "docker-instance": "todomvc-12",
             "elapsed-time": "3m33s"
           },
           {
             "id": 202,
             "app": "todomvc:8082",
             "port": 8082,
             "service-port": 15202,
             "docker-instance": "todomvc-13",
             "elapsed-time": "3m18s"
           },
           {
             "id": 203,
             "app": "demo:8083",
             "port": 8083,
             "service-port": 15203,
             "docker-instance": "demo-2",
             "elapsed-time": "1m22s"
           }
         ]
         
    si 2 instances de l'application todomvc sont déployées et 1 instance de demo est déployée.
  • POST /app/stop permet stopper une instance par son id
    Le JSON en entrée doit spécifier l'id de l'instance que l'on veut stopper.
    Par exemple
         {
           "id": 201
         }
         

    Le JSON renvoyé doit contenir les informations de l'application au moment de l'arrêt de celle-ci.
    Par exemple,
           {
             "id": 201,
             "app": "todomvc:8082",
             "port": 8082,
             "service-port": 15201,
             "docker-instance": "todomvc-12",
             "elapsed-time": "4m37s"
           }
         
  • GET /logs/:time renvoie l'ensemble des logs depuis les time dernières minutes
    Le JSON renvoyé doit contenir les messages de logs (avec un timestamp qui correspond au moment où le message a été émis) de toutes les instances depuis les 10 dernières minutes (dans l'ordre des timestamps).
    Par exemple,
           [
           {
             "id": 201,
             "app": "todomvc:8082",
             "port": 8082,
             "service-port": 15201,
             "docker-instance": "todomvc-12",
             "message": "ceci est un message de log",
             "timestamp": "2019-10-15T23:58:00.000Z"
           },
           {
             "id": 202,
             "app": "todomvc:8082",
             "port": 8082,
             "service-port": 15202,
             "docker-instance": "todomvc-13",
             "message": "ceci est un message de log",
             "timestamp": "2019-10-15T23:58:45.000Z"
           },
           {
             "id": 203,
             "app": "demo:8083",
             "port": 8083,
             "service-port": 15203,
             "docker-instance": "demo-2",
             "message": "demo d'un message de log",
             "timestamp": "2019-10-15T23:59:34.000Z"
           }
           ]
         
  • GET /logs/:time/:filter renvoie les logs valides pour le filtre depuis les time dernières minutes
    Le JSON renvoyé est le même format que ci-dessus.
    filter est soit un id, soit un nom d'application comme "todomvc:8082", soit une instance particulière d'un docker comme "todomvc-13".
  • POST /auto-scale/update permet de démarrer ou changer la configuration de l'auto-scale si celui-ci tourne déjà.
    Le JSON en entrée doit spécifier, pour chaque application, le nombre d'instances qui doivent être présentes.
    Par exemple
         {
           "todomvc:8082": 2,
           "demo:8083": 1
         }
         

    Le JSON renvoyé doit contenir les actions que doit entreprendre l'auto-scale.
    Par exemple,
           {
             "todomvc:8082": "need to start 1 instance(s)",
             "demo:8083": "need to stop 2 instance(s)"
           }
         

    Note : les instances des applications qui ne sont pas listées dans le JSON reçu ne doivent pas être arrêtées/démarrées par l'auto-scale.
  • GET /auto-scale/status permet de savoir quelle sont les actions que doit effectuer l'auto-scale.
    Le JSON renvoyé doit contenir les actions que doit entreprendre l'auto-scale.
    Par exemple,
           {
             "todomvc:8082": "no action",
             "demo:8083": "need to stop 1 instance(s)"
           }
         
  • GET /auto-scale/stop permet d'arrêter l'auto-scale
    Le JSON renvoyé indique le nombre d'instances de chaque application qui étaient gérées par l'auto-scale.
    Par exemple,
           {
             "todomvc:8082": 2,
             "demo:8083": 1
           }
         

    Les instances des applications qui étaient gérées par l'auto-scale continuent de fonctionner normalement ; seul l'auto-scale est arrêté.

L'application square sur le disque utilise les répertoires suivants
  • apps qui contient les jars des applications à déployer,
  • docker-images qui contient les images docker créées par square,
  • logs qui contient les fichiers de logs,
  • lib-client qui contient square-client.jar.
De plus, l'application en elle même est à la racine de ces répertoires sous forme d'un jar exécutable nommé square.jar. En bonus, l'application peut aussi être présente sous forme d'un exécutable linux (en utilisant le mode Quarkus native).

Calendrier des rendus.
27 octobre :
Écrire une base où tous les services ont le codage et décodage du JSON implanté en utilisant Quarkus et buildé avec Maven.
Packager une appli demo qui fait juste un hello, aussi en utilisant Quarkus mais déployable avec docker. 3 novembre :
Écrire un prototype de la librairie cliente qui log des messages. Écrire les tests Junit qui vont avec.
Faire en sorte que l'ajout de la librairie et le déploiement avec docker soient fait en exécutant du code et connecter ce code au service de démarrage. Écrire les tests Junit qui vont avec.
Faire en sorte que vous sachiez si une instance de docker meurt dans l'application square.
10 novembre :
Récupérer et persister les logs du coté de l'application. Écrire les tests Junit qui vont avec.
Implanter le listing et l'arrêt des instances dockers. Écrire les tests Junit qui vont avec.
17 novembre :
Implanter le filtrage des logs et les services correspondants. Écrire les tests Junit qui vont avec.
Faire une revue du code existant et l'améliorer !
24 novembre :
Implanter l'auto-scale.
1 décembre :
Corriger les derniers bugs. Écrire de la doc technique et de la doc utilisateur.

Pour vous aider, si vous ne respectez pas les indications de "sudden death" suivantes, votre projet sera considéré comme mort et noté 0.
Pour la partie Java, le programme doit être écrit en utilisant correctement les différents concepts vus lors du cours de Java Avancé (sous-typage, polymorphisme, lambdas, classes internes, exceptions, types paramétrés, annotations, collections, entrées/sorties).
  • Il ne doit pas y avoir de warnings lorsque l'on charge le code dans Eclipse avec tous les warnings activés !
  • Il ne doit pas y avoir de warnings lorsque l'on compile avec javac -Xlint:all.
  • Dans un module, les packages d'implantation ne doivent pas être exportés et requires transitive doit être utilisé là où c'est nécessaire.
  • Il ne doit pas y avoir de raw types, de @SuppressWarning non justifié, de cast non justifié.
  • Le principe d'encapsulation doit être respecté.
  • Il ne doit pas y avoir de champs ou méthodes protected.
  • Il ne doit pas y avoir d'instanceof/switch/if...else sur des types là où il est possible d'utiliser le polymorphisme.
  • Chaque interface devra être nécessaire.
  • Aucune classe abstraite ne doit être publique ou utilisée comme un type.
  • Chaque méthode devra être appelée (pas de code mort).
  • Aucune méthode ne doit faire plus de 10 lignes sans une vraie justification.
  • Il est interdit d'utiliser des champs static typés par un objet (pas de variables globales), seules les constantes (static final) de type primitif sont autorisées.