24
Nov/09
0

Simplification des appels à erlang depuis ruby

Dans les articles précédents, les appels au processus erlang via la ligne de commande étaient écrits ainsi :

system "erl -pa #{EBIN_DIR} #{ROOT}/deps/*/ebin -boot start_sasl -s reloader -s inets -s xxx -mnesia dir 'db'"

On améliore l’écriture de cette commande afin de la rendre plus lisible et maintenable. Pour cela, on crée une fonction wrapper comme ci dessous :

def erl(args, options)
  dirs = "-pa #{options[:dirs].join(' ')}" if options[:dirs]
  run = "-s init stop" if options[:init]
  noshell = "-noshell -noinput" if options[:noshell]
  func = "-s #{options[:func][0]} #{options[:func][1]}" if options[:func]
  mods = options[:mods].map { |e| "-s #{e}" }.join(' ') if options[:mods]
  boot = "-boot #{options[:boot]}" if options[:boot]
 
  puts "erl #{dirs} #{boot} #{noshell} #{mods} #{args} #{func} #{run}"
  system "erl #{dirs} #{boot} #{noshell} #{mods} #{args} #{func} #{run}"
end

La commande peut être ré-écrite ainsi :

erl "-mnesia dir 'db'",
    :boot => "start_sasl",
    :mods => ["reloader", "inets", "xxx"],
    :dirs => [EBIN_DIR, "#{ROOT}/deps/*/ebin"]

Ce qui, me semble, est plus compréhensible que précédemment. On peut remarquer que le cas de la base mnesia n’a pas encore été pris en compte.

Filed under: Erlang, Rake, build
5
Nov/09
0

rush, un peu plus d’OOP dans les scripts

Problématique

Le lancement de l’application se fait par une tâche Rake, comme décrit précédemment dans le premier article concernant les builds.

task :start => :build do ...

La tâche de lancement est dépendante d’un build complet.

Cependant, on ne prend pas en compte l’environnement, et différents processus doivent être lancés avant de démarrer l’application. Il s’agit d’une dépendance sur des processus externes, et cela est généralement réalisé par des appels système.

Rush

Cependant, les commandes à créer sont un peu complexes et lourdes à maintenir. Pour cela, nous utiliserons donc une application Ruby qui s’inscrit dans la direction des shell basés sur de la programmation objet, comme le powershell de Microsoft.

Bienvenue à Rush. Rush a été créé pour les tâches d’administration liées au projet Heroku comme expliqué dans un article de l’auteur.

Vous pouvez également consulter une présentation de rush, qui expliquera bien mieux que moi les intérêts d’une telle technologie.

On l’installe comme tout autre gem :

gem install rush

A noter que je suis passé à gemcutter pour la gestion des modules (nouveau site officiel de gestion des gems).

ATTENTION ! Rake n’est pas vraiment compatible avec les systèmes de shell (et ceci m’a coûté beauuucoup de temps). En effet Rake redéfini les entrées et sorties standard, ce qui fait qu’il n’est plus possible de lire sur l’entrée standard, et donc qu’il est impossible de rediriger des commandes par pipe. C’est pourquoi les commandes shell seront écrites dans un fichier séparé.

Si vous insistez, vous vous retrouverez avec cette erreur peu compréhensible :

rake aborted!
Broken pipe

Start

On peut étendre la tâche de démarrage pour ajouter une dépendance à l’environnement :

task :start => [ :build, 'init:env' ] do 
end

et créer une tâche dans l’espace init :

require 'rush'
 
namespace 'init' do
  desc "Init servers"
  task :env do
    app = Rush[ROOT]
    app["*.dump"].each{ |f| f.destroy }
    app["*.swp"].each{ |f| f.destroy }
    system "#{ROOT}/process.sh"
  end
end

Ici, on efface des fichiers temporaires à chaque nouveau démarrage en utilisant la syntaxe rush (ROOT est une constante qui représente le chemin racine du projet).

Un point important, comme indiqué précédemment, c’est que la gestion des processus est délélguée à un fichier externe, et lancée dans un shell externe par la commande ruby directement. Ceci car rake et la gem session ne sont pas compatibles.

En tout cas, il n’est pas possible de démarrer une session bash dans une tâche rake, l’inverse étant sûrement possible.

Gestion des processus

La tâche d’initialisation a pour but de vérifier que les processus serveurs sont lancés, et si non de les lancer. Les serveurs sont actuellement Nginx et CouchDB.

On peut écrire le code ainsi (dans process.sh) :

#!/usr/bin/ruby -rubygems
 
require "rush"
 
if Rush::box.processes.filter(:cmdline => /nginx/).empty?
  puts "Starting nginx"
  Rush.box.bash "nginx", :user => "root"
end
 
if Rush::box.processes.filter(:cmdline => /couchdb/).empty?
  puts "Starting couchdb"
  Rush.box.bash "couchdb -b", :user => "root"
end

Et les processus ne seront démarrés que s’ils n’existent pas.

La commande indiquée dans le site est la suivante :

if Rush::box.processes.filter(:cmdline => /nginx/).alive?

Malheureusement, cela ne fonctionne pas du fait que les processus sont lancés en tant que root, et je n’ai pas trouvé comment contourner ce problème.

Filed under: Rake, build
2
Oct/09
0

Vim et Rake

La structure de notre projet implique des phases de pré-processing permettant soit de compiler des sources en binaires, soit de transformer des modèles de vues en fichiers cibles.

Ces actions sont réalisées via le gestionnaire de tâches rake. Ces actions sont effectuées pour l’instant en ligne de commandes. Par exemple :

$ rake -T
rake build      # Build the sources
rake clean      # Clean workspace
rake compile    # Compile the sources
rake db:create  # Initialise a database
rake edoc       # Create documentation
rake front      # Preprocess views and styles
rake release    # Packaging
rake start      # Start application
rake test       # Unit tests
 
$ rake front
Creating style /d/apps/gns/priv/www/assets/styles/application.css
 
$

Nous allons intégrer ces commandes au sein de Vim afin de simplifier le processus (ne pas avoir à jongler entre les fenêtres) et l’automatiser (ne rien oublier afin de ne pas s’énerver parce que ça ne marche pas !).

25
Sep/09
0

Automatisation des microtests Erlang via Rake

Les fonctions de tests unitaires commencent à apparaitre dans plusieurs fichiers, et il devient nécessaire de pouvoir régulièrement rejouer l’ensemble de ces tests. Ceci ne pouvant se faire que de manière automatique pour être efficace.

Attention on rappelle qu’il ne s’agit ici que de vérifier simplement et rapidement des comportements écrits directement dans les fichiers sources. Il est évident que pour des opérations plus complexes, un réel outil de tests unitaires tel que eUnit devra être mis en place.

Pour cela, on va réutiliser ce que l’on a déjà mis en place précédemment concernant l’exécution de la machine virtuelle Erlang via Rake (voir posts précédents).

Le but est de parcourir l’ensemble des fichiers sources .erl et déterminer pour chacun d’eux si une fonction tes/0 a été définie, auquel cas on exécute la méthode du module en question.

L’analyse du fichier sera une bête expression régulière, et pour cela on patch la classe File de Ruby afin de conserver une certaine harmonie dans l’écriture :

class File
  def self.contains?(filename, regexp)
    text = File.read filename
    return text.match(regexp) != nil
  end
end

Pas très efficace comme programmation mais nous ne sommes concernés que par des fichiers sources qui sont, par définition, des fichiers textes de petite taille.

On crée alors une tâche Rake :

desc "Unit tests"
task :test => :compile do
  sources.each do |source|
    if File.contains? source, /^test\(\) ->$/
      mod = File.basename(source, ".erl")
      puts "Unit testing #{mod}"
      system "erl -pa #{EBIN_DIR} #{ROOT}/deps/*/ebin -noshell -noinput -s inets -s crypto -s #{mod} test -s init stop"
    end
  end
end

Cette tâche dépend de la compilation afin de pouvoir jouer les fonctions.

On a repris la commande système utilisée pour lancer l’application. Certains paramètres ont été ajoutés :

-noshell -noinput # Exécution en mode console non interactive
-s inets -s crypto # Démarrage de modules nécessaires, ici crypto est lié aux cookies
-s #{mod} test # Exécution de la fonction test() du module mod
-s init stop # Arrêt de la machine virtuelle