Publié le 24 Novembre 2011

Jenkins permet de builder automatiquement ses projets mais il permet aussi de publier les fichiers générés sur des machines distantes.

Pour ma part je l'utilise pour déployer le dernier build automatiquement sur la machine de test.

Cela se fait au moyen du plugin "Publish Over SSH".

Il s'installe depuis le menu "manage Jenkins / manage plugins".

Une fois installé il faut définir la machine distante à laquelle on va se connecter: Cela se fait depuis le menu "manage Jenkins / configure system" dans la section SSH.

On peut également configurer les clés SSH directement dans Jenkins. Pour ma part j'ai configuré les clés SSH pour ne pas utiliser de mot de passe comme expliqué dans cet article.

Ensuite il faut sélectionner le projet qui effectue le build puis aller dans configure à la section "Build environment" et cocher l'option "Send files or execute commands over SSH after the build runs".

Toute une nouvelle section apparît alors. On va pouvoir séléctionner la machine distante configurée précédemment et préciser les fichier à uploader. Noter le "remove prefix" très pratique pour ne pas recréer l'arborescence des fichiers sur la machine distante.

On peut également exécuter une commande distante qui permet de démarrer automatiquement les tests après la mise à jour par exemple.

Tous les détails de la configuration du plugin "Public Over SSH" se trouve ici: https://wiki.jenkins-ci.org/display/JENKINS/Publish+Over+SSH+Plugin

Voir les commentaires

Rédigé par Bliz

Publié dans #Intégration continue

Repost 0

Publié le 23 Novembre 2011

Bizarrement en java il est très facile de trier une map selon ses clés mais pas selon ses valeurs.
Pour trier une Map selon ses clés il suffit d'utiliser une TreeMap en lui passant éventuellement un Comparator pour trier les clés.

Dans cette article nous allons trier une Map de nombre définit comme suit:

Map<Integer, String> numbers = new HashMap<Integer, String>();
numbers.put(5, "Five");
numbers.put(1, "one");
numbers.put(8, "eight");
numbers.put(0, "zero");
numbers.put(3, "three");
numbers.put(4, "Four");
numbers.put(7, "Seven");
numbers.put(9, "Nine");
numbers.put(6, "Six");
numbers.put(2, "Two"); 

 

Pour trier cette Map selon ces clés il suffit d'utiliser une TreeMap:

Comparator<Integer> keyComparator = new Comparator<Integer>(){
   @Override
   public int compare(Integer int1, Integer int2){
      return int1.compareTo(int2);
   }
};
Map<Integer, String> sortedOnKeysMap = new TreeMap(keyComparator);
sortedOnKeysMap.putAll(numbers);

 

Oui je sais, on pourrait se passer du KeyComparator dans ce cas mais ça aide pour introduire la suite.

Pour trier selon les valeurs c'est un peu plus compliquer, on va utiliser un comparateur à double niveau: je m'explique.

On fournit toujours un Comparator<Integer> au constructor du TreeMap mais ce comparateur va en fait utiliser un Comparator<String> pour trier les valeurs. Ce qui donne le code suivant:

Comparator<String> valueComparator = new Comparator<String>() {
   @Override
   public int compare(String s1, String s2) {
      return s1.compareToIgnoreCase(s2);
   }
};
MapValueComparator<Integer, String> mapComparator = new MapValueComparator<Integer, String>(numbers, valueComparator);
Map<Integer, String> sortedOnValuesMap = new TreeMap<Integer, String>(mapComparator);
sortedOnValuesMap.putAll(numbers);

 

Toute la magie se passe dans la classe MapValueComparator dont voici le code:

public class MapValueComparator<K, V> implements Comparator<K>{

  private final Map<K, V> mapToSort;
  private final Comparator<V> valueComparator;

  public MapValueComparator(Map<K, V> mapToSort, Comparator<V> valueComparator){
     this.mapToSort = mapToSort;
  this.valueComparator = valueComparator;
  }

   @Override
  public int compare(K key1, K key2) {
  return valueComparator.compare(mapToSort.get(key1), mapToSort.get(key2));
}

 

Et cerise sur le gâteau, comme la classe est générique ça marche avec n'importe quelle Map!

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 22 Novembre 2011

SVN permet de définir des actions (hooks) en fonction des évènements de gestion conf. Il permet de définir les hooks suivant:

  • post-commit: déclenché après un commit
  • post-lock: déclenché après le lock d'un fichier ou d'un repertoire
  • post-revprop-change: déclenché après un changement de propriétés SVN
  • post-unlock: déclenché après la libération d'un lock
  • pre-commit: déclenché avant un commit
  • pre-lock: déclenché avant l'obtention d'un lock
  • pre-revprop-change: déclenché avant le changement de propriétés SVN
  • pre-unlock: déclenché avant la libération d'un lock
  • start-commit: déclenché avant l'opération de commit ne soit créé (avant le pre-commit).

Pour configurer un hook il faut placer un fichier exécutable (script, binaire, etc ..) dans le repertoire hooks de SVN, le nom du fichier devant être le nom du hook: http://svnbook.red-bean.com/en/1.5/svn.reposadmin.create.html#svn.reposadmin.create.hooks

Dans notre cas, seul le hook post-commit nous intéresse: Il faut donc créé un fichier nommé post-commit (sans .sh à la fin) et lui donner les drois 755 (soit rwxr-xr-x).

Le contenu du script doit être:

#!/bin/sh
#
REPOS="$1"
REV="$2"
UUID=`svnlook uuid $REPOS`
/usr/bin/wget \
  --header "Content-Type:text/plain;charset=UTF-8" \
  --post-data "`svnlook changed --revision $REV $REPOS`" \
  --output-document "-" \
  --timeout=2 \
  http://localhost/subversion/${UUID}/notifyCommit?rev=$REV

Penser à remplacer localhost par l'adresse du serveur Jenkins.

Comme on peut le voir lors d'un commit une commande wget sera exécutée pour envoyer une requête HTTP au serveur Jenkins.

Attention à bien activer le 'Poll SCM' dans la configuration du job Jenkins pour que le hook fonctionne (dans le cas contraire Jenkins ne déclenchera pas le build). Comme on a un déclenchement automatique on peut augmenter largement l'intervalle des vérifications SVN (une fois / jour par exemple).

Tout ceci est expliqué sur la page de configuration du plugin SVN : https://wiki.jenkins-ci.org/display/JENKINS/Subversion+Plugin

Voir les commentaires

Rédigé par Bliz

Publié dans #Intégration continue

Repost 0

Publié le 22 Novembre 2011

J'ai récemment ajouté l'analyse des sources dans Sonar à l'aide du SCM Activity plugin (voir article précédent) et tout marche nickel sauf pour un projet pour lequel Sonar fait systématiquement échouer le build.

En cherchant dans les logs du build j'ai trouvé l'erreur suivante:

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Can not execute Sonar

Embedded error: Fail to load SCM data as there are local modifications: 
[com/over-blog/patatos/xml/AuthenticationType.java:modified]
        [...]
[INFO] ------------------------------------------------------------------------
[INFO] Trace
org.apache.maven.lifecycle.LifecycleExecutionException: Can not execute Sonar
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:719)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeStandaloneGoal(DefaultLifecycleExecutor.java:569)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:539)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:387)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:284)
        ....
Caused by: org.apache.maven.plugin.MojoExecutionException: Can not execute Sonar
at org.codehaus.mojo.sonar.Bootstraper.executeMojo(Bootstraper.java:103)
at org.codehaus.mojo.sonar.Bootstraper.start(Bootstraper.java:79)
at org.codehaus.mojo.sonar.SonarMojo.execute(SonarMojo.java:88)
at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:490)
at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:694)
... 17 more
Caused by: org.sonar.api.utils.SonarException: Fail to load SCM data as there are local modifications:
        [com/over-blog/patatos/xml/AuthenticationType.java:modified]
        [...]
at org.sonar.plugins.scmactivity.LocalModificationChecker.doCheck(LocalModificationChecker.java:69)
at org.sonar.plugins.scmactivity.LocalModificationChecker.check(LocalModificationChecker.java:50)
at org.sonar.plugins.scmactivity.ScmActivitySensor.analyse(ScmActivitySensor.java:80)
at org.sonar.batch.phases.SensorsExecutor.execute(SensorsExecutor.java:64)
at org.sonar.batch.phases.Phases.execute(Phases.java:93)
at org.sonar.batch.bootstrap.ProjectModule.doStart(ProjectModule.java:143)
        ...
... 21 more
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 minutes 23 seconds
[INFO] Finished at: Tue Nov 22 02:07:15 CET 2011
[INFO] Final Memory: 109M/448M
[INFO] ------------------------------------------------------------------------
Build step 'Sonar' changed build result to FAILURE
Build step 'Sonar' marked build as failure
Finished: FAILURE

Comme indiquer dans les logs le SCM Activity plugin a trouvé une différence entre les sources compilées et les sources sous SVN et donc il fait échouer le build.

Effectivement il a bien une différence au niveau des sources car les fichiers concernés sont des sources générées lors du build à l'aide d'XMLBeans. Certes ces fichiers ne devraient pas être présent dans la gestion de conf mais on tout n'est pas parfait en ce monde et ici, je dois faire avec. 

Heureusement Sonar a prévu le coup: il faut désactiver le check des sources pour ce projet là et rien de plus simple, il suffit de changer un paramètre de config pour ce projet:

  • Se connecter à Sonar en tant qu'admin via l'interface web
  • Sélectionner le projet incriminé
  • Aller dans 'Settings' dans le menu de gauche
  • Choisir la catégory 'SCM Activity'
  • Passer 'Ignore local modifications' (sonar.scm.ignoreLocalModifications) à true
  • Enregistrer les modifications

Et voilà maintenant tout mes projets build à nouveau avec l'analyse des sources !!

Voir les commentaires

Rédigé par Bliz

Publié dans #Intégration continue

Repost 0

Publié le 21 Novembre 2011

Une variable volatile est une variable pour laquelle la JVM garantie que l'on récupèrera toujours une valeur à jour.

D'un point de vue technique cela signifie qu'il n'y a pas d'optimisation CPU. Par exemple en utilisant le cache CPU qui pourrait engendré des valeurs différentes pour 2 threads tournant sur 2 CPU différents. 

Volatile garantit uniquement qu'on ne lira pas une valeur périmée mais il ne remplace pas un code 'synchronized' si on a besoin de contrôle d'accès.

Volatile est typiquement utile dans 2 cas:

  • Lorsque la mise à jour d'une variable ne dépend pas de sa valeur précédente. Par exemple pour le cas d'un flag d'initialisation du genre isReady.
  • Lorsqu'il n'y a qu'un seul Thread qui modifie la variable (les autres ne faisant que la lire).

En tout cas on ne rencontre pas ce mot clé tout les jours et je ne connais pas beaucoup de personne capable d'en expliquer le principe.

 

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 18 Novembre 2011

Aujourd'hui l'astuce du jour concerne les tableaux ou plus précisément les Collection en java.

En effet il existe une méthode dans l'interface Collection qui permet de ne retenir que les éléments communs entre deux collections:

List<String> croissant = Arrays.asList("1", "2", "3", "4");
List<String> decroissant = Arrays.asList("5", "4", "3");

croissant.retainAll(decroissant);
System.out.println(croissant); // affiche ["3", "4"]

Attention retainAll() modifie directement la liste depuis laquelle la méthode est invoquée. Il faut donc faire une copie préalable si on ne veut pas modifier la liste initiale:

List<String> communs = new ArrayList<String>(croissant);
communs.retainAll(decroissant); 

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 17 Novembre 2011

Si vous avez lu mon artile précédent sur SSH et que vous avez essayé la commande

ssh-copy-id user@remote-host 

Vous avez probablement rencontré l"erreur suivante: 

/usr/bin/ssh-copy-id: ERROR: No identities found

Cela vient du fait que sur Ubuntu il n'y a pas de fichier .ssh/id_rsa de créer. Seul le fichier known_hosts est présent dans le répertoire .ssh.

Cela signigie qu'il vous faut générer vos clés SSH. Cela se fait simplement avec la commande ssh-keygen:

ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/user/.ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab:ab user@local_host
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
+-----------------+

Voir les commentaires

Rédigé par Bliz

Publié dans #SSH

Repost 0

Publié le 15 Novembre 2011

Un singleton est une classe qui garantie qu'il n'y aura qu'une seule instance d'elle-même à l'éxecution (donc dans la JVM).

Il existe différentes méthodes pour créer un Singleton en java mais toutes ne sont pas équivalentes:

Certaines privilégient la concision du code mais créent l'instance au démarrage de la JVM ce qui allonge d'autant l'initialisation du programme. D'autres permettent en revanche de créer l'instance seulement lorsqu'on a besoin pour la première fois (on parle alors de lazy-initialisation).

La méthode la plus connue (d'après moi):

public class Singleton{
   
   private static final INSTANCE = new Singleton();
   
   /** private constructor pour être sûr que personne ne pourra créer une instance **/
   private Singleton(){
      // ... init code
   }
   
   public Singleton getInstance(){
      return instance;
   }
   
   // autres methodes de la classe ...
}

Une variante consiste à rendre le champ static INSTANCE public et à se passer de la méthode getInstance(). Au final ça ne change pas grand-chose (le remplacement étant fait automatiquement à la compilation), c'est donc plus une affaire de goût personnel.

 

Une manière plus élégante (et plus concise) est d'utiliser un enum:

public enum Singleton{
   
   INSTANCE;
   
   private Singleton(){
      // ... init code
   }
   
   // autres methodes de la classe ...
}

C'est pas beau ça ? Difficile de faire plus simple, non ? On a toujours la création de l'instance au démarrage de la JVM mais on gagne en clarté au niveau du code et contrairement à l'approche précédente on est blindé par rapport aux petits malins qui voudraient passés par la reflection ou la sérialization pour créer une autre instance.

Maintenant pour ce qui concerne l'initialisation tardive le code est un peu plus complexe:

public class Singleton{

   private static class SingletonLoader(){
   
      private static final Singleton INSTANCE = new Singleton();
   
   }

    /** private constructor pour être sûr que personne ne pourra créer une instance **/
   private Singleton(){
      // ... init code
   }

   public Singleton getInstance(){
      return SingletonLoader.INSTANCE;
   }

   // autres methodes de la classe ...
}

Voilà la seule méthode valide pour créer un singleton avec une initialisation tardive.

Attention au code suivant (qui semble très courant - d'après ce que je voie) et qui n'est absolument pas thread safe:

public class Singleton{
   
   private static instance = null;
   
   /** private constructor pour être sûr que personne ne pourra créer une instance **/
   private Singleton(){
      // ... init code
   }
   
   public Singleton getInstance(){
      if(instance == null){
         instance = new Singleton();
      }
      return instance;
   }
   
   // autres methodes de la classe ...
}

 

Attention à ne pas abuser du design pattern Singleton. Bien souvent une classe avec des méthodes statiques est suffisantes.

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 14 Novembre 2011

Le protocol HTTP définit toute une série de code d'erreur.

Les plus connus sont certainement:

404 not found
403 forbidden

etc...

La liste complète est disponible sur wikipedia.

Il y a une astuce pour savoir rapidement le type d'erreur. En fait c'est le premier chiffre qui donne cette indication (ici 4 correspond à une erreur côté client - qui essaie d'accèder à une ressource qui n'existe pas ou à laquelle il n'a pas accès).

Voilà à quoi correspondent les différentes valeurs:

1 Information
2 Succès
3 Redirection
4 Erreur client
5 Erreur serveur

Voir les commentaires

Rédigé par Bliz

Repost 0

Publié le 12 Novembre 2011

Aujourd'hui j'ai ramené mon PC du travail à la maison mais là, problème impossible de me connecter au wifi. Mon PC n'arrête pas de me demander mon mot de passe sans succès.

Je me dis que le problème ne vient pas du mot de passe (et oui vu que c'est le bon). Par curiosité je vais jeter un oeil dans /var/log/syslog et là super: je trouve un indice:

NetworkManager[1040]: <warn> Activation (wlan0/wireless): association took too long.

Voilà qui va me permettre d'avancer. Hop j'envoie le message d'erreur dans google et après quelques visites sur différents forum, je tente la commande suivante:

sudo rmmod iwlagn
sudo modprobe iwlagn

Et là surprise même pas le temps de fermer le terminal que je suis déjà connecté. Ouf!

Voir les commentaires

Rédigé par Bliz

Publié dans #Linux

Repost 0