HOAB

History of a bug

Jenkins et déploiement automatique

Rédigé par gorki Aucun commentaire

Le problème :

Déployer à partir de Jenkins, une application fraichement buildée sur des serveurs distants (avec des scripts en tout genre, mise à jour bdd, fichiers, autres)

Solution :

Il est possible de tout faire dans Jenkins à l'aide des plugins (Publish over SSH et SSH Credentials), mais c'est fastidieux et lent de tester/maintenir ses scripts ainsi.

Je préfère, et de loin, avoir mes scripts disponibles sous un shell pour les tester efficacement et pourquoi pas, les réutiliser ailleurs. Dans ce cas, le plugin de base pour exécuter un script suffit.

Quelques problèmes se sont posés, d'où quelques bonnes pratiques à avoir :

  1. utiliser des clés publiques / clés privées pour ne pas avoir à gérer les mots de passe dans Jenkins.
  2. séparer les jobs de builds des jobs deploy et les organiser avec MultiJob
  3. stocker les scripts sous votre SVN / Git (Jenkins permet de déployer une arborescence dans un sous-répertoire)
    • par exemple : url SVN des scripts => à extraire dans ./deploy
  4. dans la partie build : déterminer automatiquement la version et stocker là dans un fichier. Cela permet de pointer sur le trunk plus facilement
  5. dans la partie deploy :
    • retourner un code à Jenkins en fin de script pour indiquer si le build est OK ou KO
    • utiliser les variables d'environnements locales (Jenkins), distantes.
# tracer l’environnement local (script Jenkins) avec 
env > /path/to/env_jenkins.txt

# tracer l'environnement distant avec : 
ssh $SERVEUR_DIST << FIN
	env > ~/env_distant.txt
FIN

# passer les variables d'un environnement à l'autre (bash remplace les variable locales (Jenkins) au moment de transférer le script) :
ssh $SERVEUR_DIST << FIN
	echo $VARIABLE_JENKINS
	echo \$VARIABLE_DISTANT
FIN
  • comme tout bon script :
    • tester les arguments
    • mettez des logs
    • mettez des messages d'erreurs clairs
    • tester les codes retours de vos commandes : ça ne coute rien !
if [ $? -ne 0 ] ; then exit 1; fi

Merci capt'ain Obvious dirons-nous, mais au moins c'est écrit.
En effet toute cette artillerie peut avoir à fonctionner toutes les nuits, ça merdera forcément, autant que la panne soit facile à identifier.

 

 

Java ne fonctionne pas avec Firefox

Rédigé par gorki Aucun commentaire

Le problème :

Un plugin Java obsolète, désinstallation, réinstallation, pas de java dans le navigateur.

Solution :

Réponse rapide : si vous avez un Java 64 bits, alors il vous faut un Firefox 64 bits, sinon la solution conseillée est d'installer une JRE 32 bits.

Désinstallation de plugin :

  1. désintaller le programme associé (Java, flash, VLC, etc...)
  2. savoir où est le plugin :  about:plugins dans la barre d'adresse de Firefox
  3. les plugins peuvent être
    • dans le répertoire plugins de Firefox : <home firefox>/plugins
    • inscrits dans la base de registre :
      • HKEY_CURRENT_USER\Software\MozillaPlugins (32 bits)
      • HKEY_LOCAL_MACHINE\SOFTWARE\MozillaPlugins (32 bits)
      • HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\MozillaPlugins (64 bits)

Comme les plugins sont des DLLs intégrées dans Firefox, il semble donc qu'un programme 32 bits ne peut pas charger des DLL 64 bits (confirmé ici , Quote:

"While running a fully 64-bit Windows system sounds great,
the reality is that you'll very likely need to run Win32
code for a while. Towards that end, x64 versions of Windows
include the WOW64 subsystem that lets Win32 and Win64
processes run side-by-side on the same system. However,
loading your 32-bit DLL into a 64-bit process, or vice
versa, isn't supported."
)

@EJB @Asynchronous ExceptionExecution

Rédigé par gorki Aucun commentaire

Le problème :

La récupération des résultats des EJB asynchrone.

La technique de base pour déclencher un traitement asynchrone :

  1. créer une méthode taggué "@Asynchronous", la méthode retourne un Future<?>
  2. l'appeler
  3. attendre le retour

Souvent, on déclenche X fois la méthode, le tout passe dans une boucle :

  1. Pour toutes les tâches
    1. créer une méthode taggué "@Asynchronous", la méthode retourne un Future<?>
    2. l'appeler
    3. stocker le Future
  2. attendre que tous les Futures soit terminés
  3. récupérer les résultats

On part du principe que la méthode asynchone gère correctement ses erreurs à son niveau (un try/catch Exception avec un retour Future correct).

Seulement, certaines exceptions peuvent être remontées par le container : transaction timeout, erreur lors du commit, etc... sous forme d'ExecutionException. Et là, si le code est mal fait, on peut avoir des surprises et ne pas savoir dire ce qui s'est passé exactement

Solution :

Il faut donc penser lors de la récupération des résultats à :

  1. prévoir un try/catch d'ExecutionException / CancellationException / InterruptedException pour les Future.get()
  2. bien le mettre PAR Future, puisque chaque Future.get() va pouvoir lancer sa propre exception. (Certaines tâches peuvent provoquer une erreur au commit et pas d'autre)
  3. gérer un état global de récupération du résultat (est-ce que le code est correct si seulement une partie des tâches ont réussi ?)
  4. la gestion de traces et d'erreurs associés pour permettre de retrouver les tâches en erreur.

Exemple :

  • ici, le résultat est retourné dans une map
  • sinon le bean lui-même est retourné avec l'exception, il contient des maps pour stocker les erreurs et les futures
  • la gestion de traces et d'erreur est faite par l'appelant, les informations nécessaires pour décrire le contexte métier du Future sont dans un bean "FutureDescription"
// Attributs : 
private Map<Future<R>, FutureDescription> futures = new ConcurrentHashMap<Future<R>, FutureDescription>();
private Map<Future<R>, Exception> futureException = new ConcurrentHashMap<Future<R>, Exception>();

...

public Map<Future<R>, R> extractResultsMap(boolean throwException) throws MultiThreadException {

Map<Future<R>, R> results = new HashMap<Future<R>, R>();

// parcours de chaque Future pour récupérer son résultat d'exécution
for (Future<R> future : futures.keySet()) {
   try {
         results.put(future, future.get());
   } catch (InterruptedException | ExecutionException e) {
         futureException.put(future, e);
   }
}
if (throwException && (futureException.size() > 0)) {
    throw new MultiThreadException(ErrorCode.TECHNICAL_GLOBAL, "Erreur pendant la recuperation des resultats", this);
}
return results;

}

 

 

Couplés avec :

  • une classe qui fait du monitor pour tracer l'avancement des threads
  • une classe qui stocke les Future, leur résultats, leurs exceptions
  • des méthodes utilitaires

Cela donne les outils nécessaire pour gérer facilement l'asynchronisme.

Hibernate Envers - Audit de table - Relation 1-N

Rédigé par gorki Aucun commentaire

Le problème :

Envers est un module core d'Hibernate qui permet d'auditer des entités, i.e, historiser toutes les modifications de l'objet.

Le principe est de stocker dans une table quasiment identique les différentes versions de l'objet.

Exemple :

Table source : UTILISATEUR(id, version, nom, adresse)

Table audit : UTILISATEUR_AUD(id, rev, nom, adresse)

Le champ version de la table source est utilisé pour l'optimist locking, on n'a donc pas besoin de ce champ dans la tableau auditée.

Par contre la table auditée a un champ un peu équivalent : "révision" qui permet de stocker les différentes versions de l'objet.

Attention cette révision est différente de la version de l'objet, en effet cela correspond plus à un numéro de commit global : tous les objets modifiés et audités dans une même transaction auront la même révision (un peu comme un commit SVN).

Coté code source :

@Audited
@Entity
public class UTILISATEUR {

    @Id
    private Long id;

    @Version
    private Long version;

    private String nom;

    private String adresse;
}

Facile comme tout (il manque des trucs bien sur : création de la table d'audit, des séquences qui vont bien, des informations supplémentaires trackées avec l'audit, etc...)

A chaque sauvegarde d'une instance UTILISATEUR, on aura une ligne dans UTILISATEUR_AUD.

Bon, mais que se passe-t-il si au lieu d'avoir 1 adresse, UTILISATEUR a N adresses ?

@Audited
@Entity
public class UTILISATEUR {

    @Id
    private Long id;

    @Version
    private Long version;

    private String nom;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "UTILISATEUR_ID")
    private List<String> adresses;
}

Solution :

Déjà lire la documentation.

Ensuite choisir : est-ce que les adresses doivent être auditée ? Si oui, on continue.

Evidemment il va y a voir une deuxième table ADRESSE_AUD.

Mais un des points forts d'envers est d'auditer seulement les deltas : une ADRESSE modifiée ne veut pas dire que UTILISATEUR est aussi modifié.
La conséquence importante est que là où on a une relation 1..N, on passe à une relation N..N. Pourquoi ?

Voici ce qui se passe :

// Sauvegarde d'un utilisateur avec une adresse, dans les tables d'audit on obtient

U1 (rev1) -> A1 (rev1)

// on modifie l'adresse (toujours du 1..N)

U1 (rev1) -> A1 (rev1)

U1 (rev1) -> A1 (rev2)

// on modifie l'utilisateur (la relation devient N..N ! deux utilisateurs pointent vers la même adresse)

U1 (rev1) -> A1 (rev1)

U1 (rev1) -> A1 (rev2)

U1 (rev3) -> A1 (rev2)

P.S : Notez bien que les objets modifiés dans une même transaction ont la même révision

Les conséquences directes sont :

- une table d'association est nécessaire entre UTILISATEUR_AUD et ADRESSE_AUD (alors que ce n'est pas le cas dans le modèle normal).

- en 1..N, le lien est porté par la table fille, on a donc dans ADRESSE une colonne qui référence l'utilisateur (UTILISATEUR_ID) ; dans la table d'audit, c'est la table d'association qui porte ce lien.

@Audited
@Entity
public class UTILISATEUR {

    @Id
    private Long id;

    @Version
    private Long version;

    private String nom;

    private List<String> adresses;
}

 

@Audited
@Entity
public class ADRESSE {

    @Id
    private Long id;

    @Version
    private Long version;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "UTILISATEUR_ID")
    @AuditJoinTable(name = "UTILISATEUR_ADRESSE_AUD", inverseJoinColumns = {
        @JoinColumn(name = "ADRESSE_ID")
    })
    private List<String> adresses
}

Je mettrais bien le DDL, mais j'ai la flemme :)

Trucs à savoir

  • évidemment Envers utilise beaucoup de ressource, attention aux performances
  • en plus des tables d'audit, il y a une table pour référencer toutes les révisions, cette table est customisable pour y ajouter des informations (utilisateur, objets, etc...)
  • il est possible d'utiliser des EJB dans les EntityTrackingRevisionListener, en faisant un lookup JNDI.(entre autre pour retrouver un EJB qui possède une variable @RequestScoped). Si j'ai le temps, je ferai une description rapide du truc.
  • Best practice : il y a des fonctions pour recharger les révisions, mais ça ne marche bien que si l'objet "chapeau" est modifié à chaque révision. Sinon c'est compliqué de retrouver l'historique à partir de l'objet chapeau. (Il suffit par exemple de positionner une date de modification) => ça n'aide pas les performances.

 

@EJB @Singleton et ConcurrencyManagement

Rédigé par gorki Aucun commentaire

Le problème :

Un EJB singleton qui sert de cache remonte une erreur avec JBoss 7.1.1 :

EJB Invocation failed on component XXXX for method public java.lang.Object:
javax.ejb.EJBTransactionRolledbackException: JBAS014373: EJB 3.1 PFD2 4.8.5.5.1
concurrent access timeout on org.jboss.invocation.InterceptorContext$Invocation@398e7b17 
- could not obtain lock within 5000MILLISECONDS

Solution :

Tout est déjà écrit bien sur, encore aurait-il fallu le lire :)
Par défaut les EJB @Singleton sont

  1. @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)

  2. protégés par un @Lock(LockType.WRITE)

donc les méthodes sont synchronisés par le container. On peut

  • soit passer la méthode ou la classe en LockType.READ,
  • soit passer en Bean-managed (@ConcurrencyManagement(ConcurrencyManagementType.BEAN)). Là c'est vous qui gérer le tout.

Pour reproduire systématiquement, il suffit de mettre un sleep de 6s dans la méthode problématique.

Note:

  • Si le pool des ejb n'est pas assez grand on a un permit timeout et non un concurrent ascess timeout.
  • Le timeout sous JBoss est configuré dans cette section très intéressante :
<subsystem xmlns="urn:jboss:domain:ejb3:1.2">
            <session-bean>
                <stateless>
                    <bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
                </stateless>
                <stateful default-access-timeout="5000" cache-ref="simple"/>
                <singleton default-access-timeout="5000"/>
            </session-bean>
            <pools>
                <bean-instance-pools>
                    <strict-max-pool name="slsb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
                    <strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
                </bean-instance-pools>
            </pools>
            <caches>
                <cache name="simple" aliases="NoPassivationCache"/>
                <cache name="passivating" passivation-store-ref="file" aliases="SimpleStatefulCache"/>
            </caches>
            <passivation-stores>
                <file-passivation-store name="file"/>
            </passivation-stores>
            <async thread-pool-name="default"/>
            <timer-service thread-pool-name="default">
                <data-store path="timer-service-data" relative-to="jboss.server.data.dir"/>
            </timer-service>
            <remote connector-ref="remoting-connector" thread-pool-name="default"/>
            <thread-pools>
                <thread-pool name="default">
                    <max-threads count="10"/>
                    <keepalive-time time="100" unit="milliseconds"/>
                </thread-pool>
            </thread-pools>
        </subsystem>

 

 

Fil RSS des articles