Incron: monitorare directory e rispondere ad eventi specifici

12

inotify

Con questo articolo vedremo come, utilizzando un tool che dialoga direttamente con il kernel Linux, monitorare un particolare path e rispondere ad alcuni eventi lanciando diversi comandi.

Come al solito mi piace analizzare una problematica reale per far comprendere meglio il problema e la soluzione che andremo ad implementare.

Immaginiamo di avere una macchina Linux (in questo caso specifico andremo ad utilizzare un server CentOS) attestata sulla rete perimetrale/pubblica della nostra azienda.

Questa macchina ha delle directory utente che vengono utilizzate, da procedure automatiche presenti su server remoti di altre aziende, per caricare dei file che la nostra azienda ha necessità di elaborare.

La soluzione “quick & dirty” sarebbe quella di accordarsi con l’azienda che caricherà il file sul nostro sistema sull’ora e definire una seguente procedura:

  • Alle ore 00:00 viene avviato il trasferimento del file (di dimensione variabile) tramite scp
  • Alle ore 01:00 viene avviato tramite cron un job che prende il file e lo elabora

Questa soluzione tendenzialmente può funzionare, ma possono verificarsi diversi problemi che possono bloccare la nostra procedura. Per esempio:

  • Un problema sulle procedure dell’azienda remota tardano o falliscono, facendo slittare l’upload del file di più di un’ora. In questo caso il nostro job in partenza alle 01:00 fallirà poiché non troverà il file caricato.
  • Sempre un problema sulle procedure dell’azienda remota fa tardare l’avvio dell’upload del file. In questo caso, quando il nostro job parte, potrebbe tentare di elaborare un file non ancora completo
  • Le procedure partono correttamente ma, un lag di rete dato da N possibili motivi, fa allungare il tempo di trasferimento del file. Anche qui il nostro job potrebbe tentare di elaborare un file non ancora completamente trasferito.
  • Delle modifiche alle procedure remote fanno variare il nome del file (che era stato precedentemente accordato). In questo caso, anche se il trasferimento è completo, il nostro script (se non correttamente ingegnerizzato) potrebbe non trovare il file semplicemente perché non si chiama come ci si aspetterebbe
  • Sempre per modifiche alle procedure remote, il file potrebbe arrivare non normalizzato (quindi con permission sballate, etc.). In questo caso, magari, il nostro script potrebbe non essere in grado di leggere/scrivere o, più in generale, di elaborare il file

Come appare evidente, le problematiche possono essere molteplici!!!
Fortunatamente stiamo lavorando su linux e, il più delle volte, ci viene data la possibilità di risolvere i nostri problemi con un po’ di ingegno!

inotify

inotify è un sottosistema del kernel linux che lavora a livello di filesystem e ci permette, a fronte di alcuni eventi catturati, di notificare le variazioni all’applicazione.
E’ stato incluso nel mainstream del kernel linux con la versione 2.6.13 (rilasciata a Giugno 2005), ma può essere compilato con la versione 2.6.12 (e precedenti), tramite l’utilizzo di una patch.

In poche parole, dato un path, il sottosistema ne controlla gli inode puntati, e reagisce ad uno o più eventi su essi. A fronte dello scatenarsi di un evento (definito mask – maschera), possono essere eseguite delle operazioni.

Alcuni eventi che possono essere monitorati sono i seguenti:

  • IN_ACCESS – E’ stato fatto un’accesso in lettura ad un file
  • IN_MODIFY – E’ stata eseguita una modifica ad un file
  • IN_ATTRIB – E’ stata eseguita una modifica agli attributi di un file
  • IN_OPEN – Un file è stato aperto
  • IN_CLOSE_WRITE – Un file, aperto in scrittura, è stato chiuso
  • IN_CLOSE_NOWRITE – Un file, aperto NON in scrittura, è stato chiuso
  • IN_MOVED_FROM e IN_MOVED_TO – Un file è stato spostato o copiato
  • IN_DELETE – Un file/directory è stato cancellato
  • IN_CREATE – Un file/directory è stato creato
  • IN_DELETE_SELF – Il file/directory monitorato è stato cancellato

incrond

incrond è un demone che funziona in maniera molto simile a quello che è l’ormai conosciutissimo demone cron; la differenza sta nel fatto che, se cron lavora con giorni ed orari, incrond lavora invece con inotify.

Quello che possiamo fare per rendere la nostra procedura “intelligente” è andare a monitorare il path in cui l’azienda remota esegue l’upload del file e, al termine dell’upload, lanciare il job che elabora il file.
Questo ci permette di evitare di legarsi a gli orari, e di lanciare il nostro job ogni volta che un file viene caricato, indipendentemente dall’ora o dal nome del file. Esattamente quello che ci interessa.

Intanto andiamo ad installare il demone, operazione estremamente semplice:

# yum install incrond

A questo punto, la prima cosa da fare è creare il file /etc/incrond.allow contenente la lista degli utenti abilitati all’uso di incrond. Nel nostro caso abilitiamo l’utente root e l’utente matteo:

# cat > /etc/incrond.allow <root
>matteo
>EOF
# cat /etc/incrond.allow
root
matteo

Avviamo il demone ed assicuriamoci che sia abilitato per l’avvio automatico al boot della macchina:

# service incrond start
Starting incrond:                                          [  OK  ]
# chkconfig --list incrond
incrond        	0:off	1:off	2:off	3:off	4:off	5:off	6:off
# chkconfig incrond on

Ok, adesso siamo pronti per il definire il nostro monitoraggio.

Poniamo, nel nostro esempio, che l’utente matteo sia l’utente che carica il file, e che lo carica nel path /home/matteo/uploads.

Iniziamo a definire la incrontab (vedrete che l’assonanza con cron non è solo nelle definizioni, ma anche nei comandi) per l’utente matteo. Il comando è semplicissimo:

# su - matteo
$ incrontab -e

Questo apre il nostro editor di default e ci permette di compilare la incrontab.
Questa incrontab è composta da 3 campi (che vedremo di seguito) separati da spazio

ATTENZIONE – I campi DEVONO essere separati dal carattere di spaziatura. Separare i campi con il consueto TAB porta a problemi di interpretazione della incrontab da parte del demone incrond (dai, è tutto molto bello, un piccolo difetto lo possiamo anche concedere 🙂 ).

I tre campi sono, nell’ordine:

  • path – Il path ASSOLUTO che si desidera monitorare
  • mask – L’evento che si vuole monitorare. Sono supportati tutte le maschere che sono state descritte prima, più la maschera IN_ALL_EVENTS che, semplicemente, cattura tutti gli eventi (ottima per il debugging)
  • command– Il comando da eseguire quando viene catturato l’evento. Da notare che in questo comando possiamo utilizzare delle variabili di inotify che ci vengono passate direttamente dal demone. Queste variabili sono:
    • $@ – Il path monitorato
    • $# – Il file sul quale si è scatenato l’evento
    • $% – L’evento, in forma testuale, che si è verificato
    • $& – L’evento, in forma numerica, che si è verificato
    • $$ – Il carattere $

Semplice, non trovate?

Allora, torniamo all’esempio. Il nostro job si trova nella directory /home/matteo/scripts/, si chiama elaborazione.sh ed accetta come parametro il nome del file da elaborare.

Facciamo un’attimo il punto della situazione. In un certo momento (non sappiamo quale), la procedura remota andrà a caricare, con scp, il nostro file.
Una volta effettuata la connessione con successo, il file verrà in primo luogo creato (IN_CREATE), aperto in scrittura (IN_OPEN), scritto (una sequenza di IN_MODIFY) ed infine chiuso (IN_CLOSE_WRITE, visto che era stato aperto in scrittura).

Quindi, l’evento che andrà a determinare la fine del trasferimento è IN_CLOSE_WRITE. Andiamo quindi a compilare la incrontab dell’utente matteo:

/home/matteo/uploads IN_CLOSE_WRITE /home/matteo/scripts/elaborazione.sh $@/$#

Salviamo usciamo ed assicuriamoci che la nuova incrontab sia stata letta ed interpretata dal sistema:

table updated
$ exit
# tail -1 /var/log/cron
Oct 27 10:30:39 myserver incrond[889]: table for user matteo changed, reloading

Possiamo testare se funziona caricando un dummy file (supponiamo che i file contenti il testo “dummy” vengano ignorati dal nostro script) da una macchina remota:

client$ cat > dummyfile <dummy
>EOF
client$ cat dummyfile
dummy
client$ scp dummyfile matteo@myserver:uploads/
Password:

Torniamo sul server e, dal log di cron, vediamo che incrond ha catturato l’evento ed ha eseguito il nostro elaboratore:

# tail -1 /var/log/cron
Oct 27 10:35:43 myserver incrond[889]: (matteo) CMD (/home/matteo/scripts/elaborazione.sh /home/matteo/uploads/dummyfile)

 

Conclusioni

Abbiamo visto come installare e configurare incrond per monitorare il cambio di stato di un file in una directory.
Questa è una possibile soluzione pratica ad un problema che, tendenzialmente, si verifica abbastanza di frequente in ambienti enterprise, in cui ci sono flussi di lavoro locali/remoti che devono lavorare in relazione gli uni con gli altri.

In realtà, gli utilizzi di inotify sono estremamente ampi; per esempio, è il sistema che viene normalmente utilizzato dagli indicizzatori di filesystem su linux (per esempio, Beagle di SUSE).
Andando a monitorare l’intero filesystem (/) ed avendo le notifiche sulle modifiche di qualsiasi file al suo interno, l’indicizzatore può aggiornare i suoi database senza doversi rileggere l’intero filesystem.

Un’altro utilizzo può essere quello di monitorare il file di log di un’applicazione. Quando questa applicazione andrà a loggare qualcosa, possiamo mandare una mail contenente il file di configurazione a qualcuno.

Il limite è il vostro ingegno 😉

Utente Linux/Unix da più di 20 anni, cerco sempre di condividere il mio know-how; occasionalmente, litigo con lo sviluppatore di Postfix e risolvo piccoli bug in GNOME. Adoro tutto ciò che può essere automatizzato e reso dinamico, l’HA e l’universo container. Autore dal 2011, provo a condividere quei piccoli tips&tricks che migliorano il lavoro e la giornata.

12 risposte a “Incron: monitorare directory e rispondere ad eventi specifici”

  1. Avatar pierissimo

    Altro uso possibile dato da inotify ( ma più specificatamente anche incron ) è la realizzazione di un software backup efficente ed efficace.
    Stranamente gli sviluppatori trascurano questa possibile implementazione, affidandosi all’affermato rsync, anche se un software di backup che deve leggere ogni volta la lista dei file, non è efficente.
    Il blasonato e inneggiato TimeMachine di Apple, non è altro che la combo di tecnologie simili o equivalenti a quelle presenti su linux/unix, un file system watcher, un demone per la sincronizzazione asincrona delle modifice, l’uso di hard links sul file system.
    Ripeto, tutto teoricamente implementabile per linux, per la creazione di un software di backup che non crei grattacapi e sia supportato da una gui semplice e potente.
    Canonical, e parlo di canonical perchè si sta mostrando la più “coraggiosa” se così vogliamo dire, si è affidata a dejadup, non è la strada giusta da intraprendere secondo me.

  2. Avatar Raoul Scarazzini

    Il fatto che incron sia utilizzabile per realizzare un sistema di backup è certamente lampante, ma credo che alla fine, nel momento in cui si debbano creare snapshot, piuttosto che versionare directory e via dicendo si debba ritornare ai tool menzionati: rsync, così come rdiff-backup, subversion e via così.
    Quello che voglio dire è che comunque qualcosa che faccia il “lavoro sporco” ci vuole… Non basta incron.

  3. Avatar Vittorio Faraoni
    Vittorio Faraoni

    Neanche a farlo apposta, per impratichirmi col Perl, un mesetto fa ho scritto qualche riga di codice come PoC (proof of concept) utilizzando inotify. E’ un daemon che monitora una directory e, quando “vede” un nuovo file, lo copia in un’altra directory.
    Così com’è è magari poco utile, ma con poche modifiche potrebbe essere utilizzato con profitto.
    Se interessa, posto il codice.

  4. Avatar Raoul Scarazzini

    Ehi Whip… C’è anche da chiederlo?

    🙂

  5. Avatar Vittorio Faraoni
    Vittorio Faraoni

    Come dicevo, è solo una PoC, sto studiando un po’ di perl. 🙂
    Volevo realizzare un daemon e fargli fare qualcosa di plausibile, mi è venuto in mente inotify e ho pensato di scrivere un daemon che facesse qualcosa basato su questa feature del kernel linux.
    “As-is” serve a poco, ma introducendo un file di configurazione, meccanismi di copia remota e sfruttando non solo IN_CREATE come ho fatto io potrebbe essere utile per backup, ad esempio.
    Raccomando a chiunque volesse usare il codice come base per altro di verificare che non ci siano clamorose idiozie, come dicevo era solo un esercizio…

    #!/usr/bin/perl
    use strict;
    use warnings;
    use POSIX;
    use File::Pid;
    use File::Copy;
    use Linux::Inotify2;
    
    
    my $daemonName    = "watchandcopy";
    #
    my $dieNow        = 0;
    my $sleepMainLoop = 1;
    my $logging       = 1;
    my $logFilePath   = "/var/log/";
    my $logFile       = $logFilePath . $daemonName . ".log";
    my $pidFilePath   = "/var/run/";      
    my $pidFile       = $pidFilePath . $daemonName . ".pid";
    
    # daemonize
    use POSIX qw(setsid);
    chdir '/';
    umask 0;
    open STDIN,  '/dev/null'   or die "Impossibile scrivere su /dev/null: $!";
    open STDOUT, '>>/dev/null' or die "Impossibile scrivere su /dev/null: $!";
    open STDERR, '>>/dev/null' or die "Impossibile scrivere su /dev/null: $!";
    defined( my $pid = fork ) or die "Impossibile fare fork: $!";
    exit if $pid;
    
    POSIX::setsid() or die "Impossibile creare una nuova sessione";
    
    $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signalHandler;
    $SIG{PIPE} = 'ignore';
    
    my $pidfile = File::Pid->new( { file => $pidFile, } );
    
    $pidfile->write or die "Impossibile scrivere nel PID file, /dev/null: $!";
    
    if ($logging) {
    	open LOG, ">>$logFile";
    	select((select(LOG), $|=1)[0]); 
    }
    
    until ($dieNow) {
    	sleep($sleepMainLoop);
    	my $watcheddir = '/home/vittorio/perl-test';
    	my $targetdir = '/home/vittorio/perl-test/copied';
    	my $watchfile = new Linux::Inotify2
       		or die "Impossibile creare il nuovo oggetto: $!";
    	 	$watchfile->watch ("$watcheddir", IN_CREATE, sub {
    	    	my $watchfile = shift;
    	    	my $name = $watchfile->fullname;
    	    	copy("$name","$targetdir") or die "copia fallita: $!";
    		logEntry ("$name creato\n");
    	 });
    	1 while $watchfile->poll;
    }
    
    sub logEntry {
    	my ($logText) = @_;
    	my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
    	my $dateTime = sprintf "%4d-%02d-%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec;
    	if ($logging) {
    		print LOG "$dateTime $logText\n";
    	}
    }
    
    sub signalHandler {
    	$dieNow = 1;
    }
    
    END {
    	if ($logging) { close LOG }
    	$pidfile->remove if defined $pidfile;
    }
    
  6. Avatar Vittorio Faraoni
    Vittorio Faraoni

    Ehm, ovviamente ho sbagliato nel copiare da terminale
    Copiata a partire da quel “vittorio@newflame:~/perl-test$ cat watchandcopydaemon.pl”
    eventualmente. 🙂

  7. Avatar Matteo Cappadonna

    Scusami Vittorio, ma con IN_CREATE monitori la creazione di un file, giusto?
    Nel senso, se apro un file descriptor in scrittura dicendo (poi varia a seconda del linguaggio) di creare il file nel caso non sia presente, con incrond, il messaggio IN_CREATE viene catturato prima che vado a scrivere il contenuto del file.

    Questo fa si che, nel caso utilizzi incrond, agendo sull’IN_CREATE, andrei a copiare sempre un file vuoto.

    Questo è lo stesso motivo per cui, nel tutorial di cui sopra, ho agganciato l’esecuzione dello script all’evento IN_CLOSE_WRITE.

    In perl funziona in maniera differente?

    Ciao

  8. Avatar Vittorio Faraoni
    Vittorio Faraoni

    Matteo, osservazione assolutamente corretta. Come detto, il mio povero codice era solo un PoC e ne verificavo il funzionamento con un banale
    echo aaaa>nuovofile.
    Ovviamente, per un utilizzo produttivo, come del resto specificavo in un precedente post, è necessario usare i giusti eventi inotify.

  9. Avatar Vittorio Faraoni
    Vittorio Faraoni

    A riguardarlo, il codice è pieno di difetti; quel

    or die “copia fallita: $!”;

    è ad esempio inopportuno, per un daemon, meglio piuttosto loggare la fallita copia.

  10. Avatar bits4beats

    Complimenti bellissimo articolo non conoscevo incrond.

    In passato avevo usato inotify per fare la stessa cosa ma con uno script bash.
    Ecco il post che avevo scritto nel mio piccolo blog.

    http://www.bits4beats.it/linux/monitorare-file-creati-modificati-e-cancellati-in-una-directory/

  11. Avatar Raoul Scarazzini

    Whip il problema non si pone: del resto quel che proviamo a dar qua son solo spunti da cui evolversi in base alla propria inventiva e fantasia.

    A proposito di questo ho messo l’highlight del codice sul tuo commento, così è più leggibile.

    Sono anche felice che tu abbia conosciuto Teo, mio fedele Padawan e compagno di avventure 😉

  12. Avatar Vittorio Faraoni
    Vittorio Faraoni

    The pleasure is all mine. 🙂
    Ti scriverò per sapere come ti vanno le cose; io ho passato un brutto periodo (replica del malanno dello scorso anno…) ma ora va decisamente meglio.

    A presto.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *