Comment assembler une image Oracle DB pour Testcontainers

Le code doit être testé sur le SGBD avec lequel il fonctionnera. Testcontainers est une bibliothèque qui vous permet d'utiliser presque tous les SGBD dans les tests unitaires avec la même facilité que les bases de données intégrées comme HSQLDB ou H2. Il n'y aurait qu'une image de docker



Cet article est consacré à l'assemblage d'une image docker qui est pratique pour une utilisation avec Testcontainers. Quand j'ai essayé de le faire, j'ai eu des problèmes, et ici je partage ma solution.
Je vais collecter l'image pour Oracle 11, car elle est petite et j'ai assez de version 11. Avec d'autres versions, l'approche est à peu près la même.


Afin d'indiquer clairement comment utiliser l'image, il y aura également du code Java qui montre l'utilisation de l'image pour tester les applications Spring Boot. La méthode de connexion aux conteneurs de test que j'ai donnée n'est probablement pas la meilleure. Mais tout d'abord, il montre comment utiliser les paramètres spécifiés lors de la création de l'image. Deuxièmement, c'est simple. Et troisièmement, il n'est presque pas lié à Spring, il peut même être bloqué dans du code Java, dans lequel il n'y a que du vide public statique principal.


Il est supposé que le lecteur est superficiellement familier avec Docker et Testcontaners, et connaît également bien Java. Pour construire, vous devez utiliser linux, si vous construisez sous Windows, vous devrez utiliser msys2 ou quelque chose comme ça.


Le code de démonstration est téléchargé sur le github ici https://github.com/poxu/testcontainers-spring-demo Les scripts corrigés pour assembler l'image peuvent être consultés dans ma fourchette des instructions d'Oraklov https://github.com/poxu/docker-images/tree/ master / OracleDatabase / SingleInstance


Créer une image Docker


Oracle ne fournit pas d'images pour docker, mais a publié des instructions détaillées sur la façon de les assembler sur le github.


Malheureusement, il n'est pas possible d'utiliser ces images dans des conteneurs de test car, car le conteneur qui est lancé à partir de cette image démarre de deux à 20 minutes.


Ceci est inacceptable pour une utilisation dans les tests unitaires, vous devez donc apporter vos propres modifications aux scripts, mais tout d'abord, il est préférable d'essayer d'assembler le conteneur conformément aux instructions fournies par Oracle. Je vais faire un bref récit ici, une instruction plus complète se trouve sur ce lien https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance


Assemblage d'images selon les instructions d'Oracle


Tout d'abord, vous devez cloner le référentiel avec des instructions sur la façon d'assembler l'image.


git clone https://github.com/oracle/docker-images.git 

Procurez-vous ensuite le package rpm pour la version express autorisée d'Oracle 11.2.0.2. Ce n'est pas très difficile, il vous suffit de vous inscrire sur le site Web d'Oracle, d'accéder à la page de téléchargement d'Oracle DBMS, de sélectionner la version 11.2.0.2 XE et de télécharger le fichier rpm compressé oracle-xe-11.2.0 -1.0.x86 ~ 64 ~ .rpm.zip.


Placez le fichier dans le référentiel git téléchargé dans le répertoire docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 /


Ensuite, accédez au répertoire docker-images / OracleDatabase / SingleInstance / dockerfiles et exécutez la commande


 ./buildDockerImage.sh -v 11.2.0.2 -x 

Le docker assemblera une image appelée oracle / database: 11.2.0.2-xe sur la base de laquelle vous devez créer le conteneur avec cette commande


 docker run --rm --name vanilla_oracle_test_habr \ -p 1234:1521 \ -p 5678:5500 \ -e ORACLE_PWD=123 \ --shm-size="1g" \ oracle/database:11.2.0.2-xe 

Le conteneur démarre quelques minutes, car après le démarrage, il crée une nouvelle base de données et ce processus n'est pas rapide.


Après quelques minutes, une bannière apparaîtra dans la console


############################
LA BASE DE DONNÉES EST PRÊTE À UTILISER!
############################

Après cela, vous pouvez vous connecter à la base de données à l'aide de la connexion SYSTEM, le mot de passe est 123, l'adresse de connexion est localhost et SID est XE.


Si tout a fonctionné, vous pouvez passer au processus de création d'une image sous testcontainers. Sinon, il est préférable de parcourir d'abord le manuel d'Oracle et de découvrir ce qui ne va pas.


Comme nous l'avons déjà découvert, le conteneur démarre longtemps car une base de données est créée après le démarrage. Dans certains cas, cela peut probablement être pratique, mais cela cause maintenant un préjudice complet. Il est nécessaire de rendre le récipient prêt à l'emploi immédiatement après le lancement.


Raffinement manuel de l'image


Une façon d'obtenir l'image à partir de la base de données terminée est d'attendre que le conteneur démarre et que la création de la base de données soit terminée, puis d'enregistrer le conteneur dans une nouvelle image.


Il est seulement important de ne pas démarrer le conteneur avec l'argument --rm, sinon le docker le battra immédiatement après l'arrêt.


 docker commit --message "container for unit tests" <container id> my/oracle-11.2.0.2-for-unit-tests 

Cela créera une nouvelle image à partir du conteneur, qui sera lancée non seulement quelques minutes, mais 20 à 30 secondes.


Modification du processus d'assemblage d'images afin d'obtenir une image finale immédiatement après l'assemblage


Bien sûr, il est bon d'avoir des instructions pour assembler l'image sous forme de code afin de pouvoir démarrer l'assemblage avec une seule commande et il n'est pas nécessaire d'attendre que le conteneur démarre et de créer une image à partir de celle-ci avec vos mains.


La dernière ligne du dockerfile indique la commande qui est exécutée après le démarrage


 CMD exec $ORACLE_BASE/$RUN_FILE 

\ $ ORACLE ~ BASE ~ / \ $ RUN ~ FILE ~ pointe vers le fichier docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 / runOracle.sh


Si vous arrêtez ce conteneur, puis le réexécutez, par conséquent, pour la première fois, le script créera la base de données et, pour la deuxième fois, il le démarrera simplement. On peut supposer que si vous exécutez le script au stade de l'assemblage d'image, l'image sera assemblée à partir de la base de données déjà créée.


Mais sur le chemin de la mise en œuvre de ce plan audacieux, une complication se pose.


Le script s'exécute pour toujours, c'est-à-dire jusqu'à ce qu'un signal arrive dans le conteneur indiquant que le travail doit être terminé. Ceci est résolu tout simplement. La dernière ligne du fichier runOracle.sh contient la commande wait. Nous savons qu'au stade de l'assemblage, vous n'avez pas besoin d'attendre quoi que ce soit, vous devez terminer le travail et nous mettrons donc une instruction conditionnelle dans le script.


Il vérifiera si l'argument --running-while-building n'est pas passé au fichier et si cet argument est passé, alors n'attendez aucun signal, mais interrompez simplement le travail. Autrement dit, faites comme ceci:


 if [ "$1" != "--running-while-building" ] then wait $childPID fi 

Eh bien, dans le dockerfile, nous ajoutons un autre appel de script, uniquement au stade de l'assemblage. Ça va tourner comme ça.


 RUN $ORACLE_BASE/$RUN_FILE --running-while-building CMD exec $ORACLE_BASE/$RUN_FILE 

Modifications requises pour une utilisation dans les tests


Élimine l'utilisation du volume


Besoin de trouver la ligne


VOLUME ["\ $ ORACLE ~ BASE ~ / oradata"]

Et commentez-le. Il n'est pas nécessaire d'utiliser des volumes, car toutes les modifications seront apportées après chaque test, mais des problèmes d'utilisation des volumes peuvent facilement survenir lors de la copie d'images.


Supprimer les fichiers inutiles


Besoin d'ajouter des lignes


 rm -rf $ORACLE_HOME/demo && \ rm -rf $ORACLE_HOME/jdbc && \ rm -rf $ORACLE_HOME/jlib && \ rm -rf $ORACLE_HOME/md && \ rm -rf $ORACLE_HOME/nls/demo && \ rm -rf $ORACLE_HOME/odbc && \ rm -rf $ORACLE_HOME/rdbms/jlib && \ rm -rf $ORACLE_HOME/rdbms/public && \ rm -rf $ORACLE_HOME/rdbms/demo && \ rm -rf $ORACLE_HOME/bin/rman && \ 

Juste avant la ligne


 chmod ug+x $ORACLE_BASE/*.sh 

Cela supprimera de l'image tous les fichiers qui ne sont pas nécessaires à des fins de test. Plus l'image est petite, mieux c'est.


Supprimer la division d'image


Afin de réduire l'image, vous devez la construire en utilisant l'argument squash . Il supprimera la séparation en couches de l'image, ce qui réduira encore son volume. L'argument squash est expérimental, vous devez donc l'activer séparément. Dans différents systèmes d'exploitation, cela se fait de différentes manières.


Pour transmettre des arguments au docker, docker-images / OracleDatabase / SingleInstance / dockerfiles / buildDockerImage.sh fournit l'argument -o. Autrement dit, pour transmettre l'argument --squash au docker, vous devez appeler buildDockerImage.sh comme ceci


 ./buildDockerImage.sh -o '--squash' 

Changer le nom de l'image


À ce jour, l'image est sensiblement différente de ce qu'Oracle propose de faire, elle doit donc être renommée. Pour ce faire, vous devez déjà éditer le fichier buildDockerImage.sh lui-même. Le script prend le nom de l'image dans la variable IMAGE ~ NAME ~, dont la valeur est directement définie dans le fichier


Ici


 # Oracle Database Image Name IMAGE_NAME="oracle/database:$VERSION-$EDITION" 

J'ai changé pour


 # Oracle Database Image Name IMAGE_NAME="my/oracle-for-habr:$VERSION-$EDITION" 

Définir le mot de passe DB


Ce mot de passe est défini dans la variable d'environnement ORACLE ~ PWD ~ lors du premier démarrage du conteneur. Mais nous avons la configuration de la base de données lors de la construction de l'image, donc la variable doit être définie à ce stade. Si la possibilité de définir un mot de passe pour chaque assemblage via la ligne de commande n'est pas nécessaire, vous pouvez simplement le saisir dans le dockerfile:


 ENV ORACLE_PWD=123 

Si, pour quelque chose, vous devez être en mesure de déterminer à nouveau le mot de passe à chaque génération, puis pour transmettre l'argument au docker, vous pouvez à nouveau utiliser -o


 ./buildDockerImage.sh -v 11.2.0.2 -x -o '--squash --build-arg ORACLE_PWD=123' 

Cela transfèrera la variable d'environnement ORACLE ~ PWD ~ dans le fichier docker, mais le fichier docker ne la transmettra pas aux scripts qui s'exécutent pendant la génération. Pour ce faire, ajoutez l'instruction ARG au dockerfile.


 ARG ORACLE_PWD=default 

Le mot de passe, comme il est probablement déjà devenu clair, sera 123, et si vous ne passez pas ORACLE ~ PWD ~ à buildDockerImage.sh alors par défaut.


Parfois, Oracle pense que le mot de passe est incorrect et ne veut pas fonctionner, il peut donc être nécessaire de remplacer 123 par autre chose


Test augmentant l'image résultante


Vous pouvez maintenant essayer d'exécuter le conteneur en fonction de l'image


 docker run --rm --name dockertest_habr \ -p 1234:1521 \ -p 5678:5500 \ --shm-size="1g" \ my/oracle-for-habr:11.2.0.2-xe 

L'argument --shm-size = "1g" est important ici, sans lequel le conteneur démarre, mais Oracle 11.2.0.2 lui-même ne peut pas fonctionner. Cela, au cas où, ne signifie pas que le conteneur aura besoin d'un gigaoctet de RAM, il consomme environ 100 mégaoctets.


Si le conteneur a augmenté normalement, vous pouvez essayer de vous connecter à la base de données, qui s'y trouve.


Adresse de base - hôte local le plus probable
Port - 1234
Utilisateur - SYSTÈME
Mot de passe - 123


S'il démarre normalement, vous pouvez passer à l'étape suivante.


Script d'initialisation de la base de données


Pour que le programme puisse fonctionner avec la base de données à partir de l'image, il est nécessaire qu'il y ait un circuit après le lancement. Vous pouvez créer ce circuit au stade de la construction, mais je préfère le faire lorsque le conteneur démarre.


Après le démarrage, le conteneur cherchera dans le répertoire u01 / app / oracle / scripts / startup et exécutera tous les scripts sql qu'il y trouve, que vous pouvez utiliser en y plaçant le fichier qui créera le circuit. Quelque chose comme ça.


 CREATE USER TEST_USER IDENTIFIED BY passwordnoquotes; ALTER USER TEST_USER QUOTA unlimited ON SYSTEM; GRANT CREATE SESSION, CONNECT, RESOURCE, DBA TO TEST_USER; GRANT ALL PRIVILEGES TO TEST_USER; 

Tout cela doit être ajouté au fichier init ~ db ~ .sql, et le fichier est jeté dans le conteneur en utilisant -v


 docker run --rm --name dockertest_habr \ -p 1234:1521 \ -p 5678:5500 \ -e ORACLE_PWD=123 \ -v ${PWD}/init_db.sql:/u01/app/oracle/scripts/startup/init_db.sql \ --shm-size="1g" \ my/oracle-for-habr:11.2.0.2-xe 

\ $ {PWD} Ici, il est utilisé car le chemin absolu du fichier est nécessaire, lorsque vous utilisez Windows, vous devez le spécifier différemment. Si, après le démarrage, le schéma TEST ~ USER ~ a été créé avec succès, vous pouvez continuer à visser le conteneur fraîchement créé aux tests.


Utiliser une image en code Java


Lors des tests à l'aide de la base de données intégrée, en règle générale, le même problème se pose. Si la configuration ne peut pas être extraite du cache, Spring la recueille à nouveau. En particulier, il recrée la base de données intégrée, ce qui ralentit bien sûr sérieusement les tests. J'ai résolu le problème de la force brute en faisant simplement de la pièce de levage de conteneur un singleton. Vrai, condo.


Pour Oracle XE, Testcontainers a une classe spécialement préparée. Tout d'abord, cette classe sait que nous parlons d'un conteneur avec un SGBD et que pour déterminer s'il est déclenché, nous devons essayer de nous connecter à la base de données en utilisant jdbc.


Un objet de cette classe attendra que le conteneur soit soulevé par lui-même, il vous suffit de lui indiquer les journaux avec mot de passe à utiliser.


 import org.testcontainers.containers.BindMode; import org.testcontainers.containers.OracleContainer; public class StaticOracleContainer { public static OracleContainer getContainer() { return LazyOracleContainer.ORACLE_CONTAINER; } private static class LazyOracleContainer { private static final OracleContainer ORACLE_CONTAINER = makeContainer(); private static OracleContainer makeContainer() { //       testcontainers.properties //  oracle.container.image final var dockerImageName = "my/oracle-for-habr"; final var container = new OracleContainer(dockerImageName) //    ,  testcontainers //  ,  ,  //   .withUsername("SYSTEM").withPassword("123") // ,  testcontainers  //     .withExposedPorts(1521, 5500) //      shared memory //    .withSharedMemorySize(2147483648L) //   ,       // -v /path/to/init_db.sql:/u01/app/oracle/scripts/startup/init_db.sql //    init_db.sql   .withClasspathResourceMapping("init_db.sql" , "/u01/app/oracle/scripts/startup/init_db.sql" , BindMode.READ_ONLY); container.start(); return container; } } } 

En outre, les conteneurs de test, lorsqu'ils sont lancés, mappent les ports internes du conteneur aux ports externes inoccupés définis de manière aléatoire. Par conséquent, vous ne pouvez pas avoir peur que le conteneur ne monte pas, car le port est déjà utilisé par quelqu'un. Un port externe peut être obtenu à partir du conteneur à l'aide de la méthode getOraclePort ().


Vous pouvez également obtenir l'adresse du conteneur à l'aide de la méthode getContainerIpAddress (), mais tout conteneur possède cette méthode.


Après le premier appel à la méthode getContainer, le conteneur ne sera pas recréé, mais celui existant sera renvoyé. Cette méthode peut maintenant être utilisée en Java nu ou dans la configuration de Spring afin d'obtenir un objet avec un conteneur à partir duquel vous pouvez extraire les ports et l'adresse de la connexion.


Par exemple, vous pouvez créer un initialiseur qui, lors de l'élévation du contexte, remplacera les attributs Spring qui sont responsables de la connexion à la base de données.


Classe pour attacher des conteneurs de test à Spring


Après avoir soulevé le conteneur, l'initialiseur remplace les propriétés responsables de l'URL pour la connexion à la base de données, la connexion, le mot de passe et tout cela.


Mais, s'il n'y a pas de paramètre evilcorp.testcontainers.enabled dans application.properties, le conteneur ne sera pas levé et tout fonctionnera comme si personne ne connectait les conteneurs de test.


 package com.evilcorp.demo; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.testcontainers.containers.OracleContainer; public class TestcontainersInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { static Logger log = LoggerFactory.getLogger(TestcontainersInitializer.class); @Override public void initialize(ConfigurableApplicationContext applicationContext) { // ,   Testcontainers final String testcontainersEnabled = applicationContext.getEnvironment().getProperty("evilcorp.testcontainers.enabled"); if (!"true".equals(testcontainersEnabled)) { return; } OracleContainer oracleContainer = StaticOracleContainer.getContainer(); oracleContainer.followOutput(s -> log.debug(() -> s.getUtf8String())); // IP       //  ,    // (linux, MacOs, Windows  ) , //    oracleContainer.getContainerIpAddress() //    // // Testcontainers     //   ,    oracleContainer.getOraclePort() //  ,         final String jdbcUrl = "jdbc:oracle:thin:@//" + oracleContainer.getContainerIpAddress() + ":" + oracleContainer.getOraclePort() + "/XE"; //      init_db.sql //      final String user = "TEST_USER"; final String password = "passwordnoquotes"; TestPropertyValues.of( "spring.jpa.properties.hibernate.default_schema=" + user, "spring.datasource.driver-class-name=oracle.jdbc.OracleDriver", "spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect", "spring.datasource.username=" + user, "spring.datasource.password=" + password, "spring.datasource.url=" + jdbcUrl, "spring.liquibase.url=" + jdbcUrl, "spring.liquibase.user=" + user, "spring.liquibase.password=" + password ).applyTo(applicationContext.getEnvironment(), TestPropertyValues.Type.MAP, "test"); } } 

Cette configuration peut être utilisée dans le test de démarrage de printemps pour remplacer les paramètres de la base de données à la volée.


Test à l'aide de Testcontainers


Le test écrit simplement un objet dans la base de données, puis le lit, rien de spécial.


 package com.evilcorp.demo; import com.evilcorp.demo.entity.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest @ContextConfiguration(initializers = {TestcontainersInitializer.class}) class TestcontainersSpringDemoApplicationTests { @Autowired UserRepository userRepository; private User createdUser; @BeforeEach void setUp() { createdUser = new User(); createdUser.setName("Fry"); userRepository.save(createdUser); } @Test void userRepositoryLoaded() { assertNotNull(userRepository); } @Test void userAdded() { final Optional<User> loadedUser = userRepository.findById(createdUser.getUserId()); assertTrue(loadedUser.isPresent()); assertEquals("Fry", loadedUser.get().getName()); assertNotSame(createdUser, loadedUser.get()); } } 

Et oui, ajoutez des dépendances à pom.xml


  <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.12.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>oracle-xe</artifactId> <version>1.12.3</version> <scope>test</scope> </dependency> 

Voilà comment vous pouvez créer une image de docker Oracle DBMS et l'utiliser dans du code Java. Il ne reste plus qu'à mettre l'image dans le référentiel d'entreprise des artefacts et à organiser le lancement des tests dans un autre conteneur docker. Mais c'est une histoire complètement différente.

Source: https://habr.com/ru/post/fr480106/


All Articles