:: Enseignements :: Master :: M1 :: 2020-2021 :: Java Avancé ::
[LOGO]

Projet de Java Avancé Master 1 - 2020


Exercice 1 - LocalKube

Le but du projet LocalKube est d'écrire une application permettant le déploiement et le contrôle d'applications écrite en Java à l'intérieur de conteneurs docker.
LocalKube est composée :
  • d'une application serveur nommée local-kube 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 LocalKube et les applications déployées ;
  • d'une librairie cliente nommée local-kube-api 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 LocalKube 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 d'un même conteneur docker.
    Attention : si une application s'arrête à cause d'un bug ou est arrêtée par une commande docker, LocalKube 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 implanter les différents services REST, votre application doit utiliser Spring Boot version 2.3.4.
  • La partie cliente HTTP doit utiliser l'API REST client de Spring.
  • Pour la sérialisation/dé-sérialisation JSON des requêtes, vous utiliserez une API de parsing JSON Jackson 2.11.3
  • La création d'image docker se fait en utilisant la librarie jib core 0.16.0 qui a l'avantage de ne pas nécessiter de deamon pour builder une image docker.
  • 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 utiliserez la base de donnée Sqlite JDBC 3.32.3.2 et la librarie jdbi 3.16.0.
    Les objets de la BDD devront être mappé sous forme de record Java dans votre application.
  • Vous devez utiliser Maven comme outil de build et IntelliJ comme IDE.
  • Pour tester vos services REST, vous pouvez utiliser Postman ou tout autres clients capables de faire des requètes REST.
  • Les test unitaires Java doivent être effectués avec JUnit 5.7.0, vous pouvez vous référer au guide d'utilisation.
  • A part les libraries listées ci-dessus, vous ne devrez pas utiliser d'autres libraries !
  • Le code est hébergé sur gitlab.com.
    Pour vous authentifier, vous utiliserez le mécanisme de clé SSH publique/privée
    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 !
  • Il n'y a aucune raison que le login/mdp de la BDD (SQLite) soit en dur dans votre code !

Description des services REST que doit posséder l'application LocalKube 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/start 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 à partir duquel l'application sera accessible.
    Par exemple
         {
           "app": "todomvc:8081"
         }
         

    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 LocalKube, le nom de l'instance du conteneur docker (qui doit contenir le nom du conteneur docker)
         {
           "id": 201,
           "app": "todomvc:8081",
           "port": 8081,
           "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:8081",
             "port": 8081,
             "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:8081",
             "port": 8081,
             "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:8081",
             "port": 8081,
             "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" soit un nom parmis "byId", "byApp" ou "byInstance" suivi d'un slash suivi repectivement d'un id, d'un nom ou d'une instance docker.
    Exemple des requètes valides:
    • GET /logs/10/203 renvoie les logs des 10 dernières minutes pour l'application ayant un id, un nom ou une instance docker correspondant à "203".
    • GET /logs/3/byId/203 renvoie les logs des 3 dernières minutes pour l'application ayant l'id 203
    • GET /logs/10/byApp/todomv:8082 renvoie les logs des 10 dernières minutes pour l'application ayant le nom "todomv:8082"
  • 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:8081": 2,
           "demo:8083": 1
         }
         

    Le JSON renvoyé doit contenir les actions que doit entreprendre l'auto-scale.
    Par exemple,
           {
             "todomvc:8081": "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:8081": "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:8081": 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 LocalKube 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 LocalKube,
  • logs qui contient les fichiers de logs,
  • lib qui contient local-kube-api.jar.
De plus, l'application en elle même est à la racine de ces répertoires sous forme d'un jar exécutable nommé local-kube.jar

Calendrier des rendus.
début novembre :
Écrire une base où tous les services ont le codage et décodage du JSON implanté en utilisant Spring Boot et buildé avec Maven.
Packager une appli demo qui fait juste un hello, aussi en utilisant Spring Boot mais déployable avec docker. mi-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 LocalKube.
fin 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.
début décembre :
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 !
mi décembre :
Implanter l'auto-scale.
fin décembre :
Corriger les derniers bugs. Écrire de la doc technique et de la doc utilisateur.
Déposer votre archive avant le 31 décembre à 23h59 sur elearning !

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 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.
  • Le fichier POM.xml ne doit pas contenir de dépendances non listées dans ce document où ayant une autre version que la version demandée.