venerdì 25 febbraio 2011

FreeBSD: gmirror e array degradato

gmirror è un ottimo strumento per la costruzione di array RAID 1 in software. Qualora un array sia segnalato come degradato, la soluzione è quella di inserire (o reinserire) il disco che deve entrare a far parte dell'array. Purtroppo gmirror impedisce l'inserimento di un nuovo provider se uno o piu' dischi risultano mancanti nell'array. La ragione di ciò è che gmirror non etichetta i dischi usando il loro nome di device, bensì con le informazioni salvate sul disco stesso (e visibili con gmirror dump ). Ne consegue che il sistema riconosce l'array ma trova che alcuni componenti sono mancanti. Non sapendo quale manca, il sistema impedisce l'inserimento di un disco nell'array per evitare di inserire il disco sopra  a se stesso. La soluzione è quella di istruire gmirror sull'esecuzione di futuri comandi particolari, e quindi con gmirror forget si istruisce il sistema a non curarsi dei dischi mancanti. Successivamente è possibile fare l'insert del disco nell'array.

FreeBSD: ZFS montato al boot

Sui sistemi FreeBSD è possibile creare pool e filesystem ZFS in modo esattamente identico a quello di OpenSolaris e derivati. Se i file system sono di sistema (es. /etc) allora il loro mount deve essere specificato nel file /etc/fstab; se invece i filesystem sono accessori il mount può essere ancora specificato nel file /etc/fstab, ma risulta piu' comodo inserire l'opzione

zfs_enable="YES"

nel file /etc/rc.conf che corrisponde al comando

zfs mount -a

ossia monta automaticamente tutti i filesystem configurati ZFS.

domenica 20 febbraio 2011

PostgreSQL @ Codemotion

L'associazione ITPug sarà presente al Codemotion di Roma (5 Marzo 2011). Avrei dovuto tenere io il talk di presentazione PostgreSQL ma a causa di un impegno non posso partecipare; Gabriele Bartolini (presidente di ITPug) terrà il talk al posto mio.
Ritengo che la partecipazione ad un evento come questo sia fondamentale per la visbilità di ITPug e PostgreSQL in Italia, soprattutto se si considera l'alta affluenza attesa per l'evento.

lunedì 14 febbraio 2011

SVN: recuperare dalle versioni precedenti i file rimossi

A volte capita che in un sistema centralizzato come SVN venga "perso" un file, cancellato/rinominato da uno dei committers senza che gli altri se ne rendano conto. Passando il tempo, e accumulandosi le revisioni, diventa sempre piu' difficile trovare un file e visualizzare tutte le modifiche che lo riguardano. Ora, il problema di fatto è uno di quelli che mi ha spinto a lavorare su sistemi decentralizzati (git principalmente e mercurial per alcuni progetti), ma questa è un'altra storia...
Tornando ad SVN, come è possibile risalire all'ultima versione del repository che contiene un determinato file? Ebbene il comando da eseguire è semplice:

svn log -vq | tr -d '\n'| sed -r 's/-{3,}/\n/

Il comando di cui sopra visualizza i numeri di revisione e i percorsi modificati (opzioni -vq) senza il contenuto del testo di messaggio stesso, dopodiché rimuove tutti i fine linea e nuovamente li aggiunge ogni volta che trova una sequenza di piu' di due trattini (le linee di separazione usate nell'output del comando log stesso). I trattini rimossi sono da 3 in su per evitare di essere confusi da firme di mail (solitamente due trattini) o simili, volendo si potrebbe anche aumentare tale valore.
Ebbene tale comando, su un semplice repository di prova, produce un output simile al seguente:

r4 | luca | 2011-02-03 11:46:09 +0000 (Thu, 03 Feb 2011)Changed paths:   A /miao.txt
r3 | luca | 2011-02-03 11:44:09 +0000 (Thu, 03 Feb 2011)Changed paths:   D /ciao.txt
r2 | luca | 2011-02-03 11:43:54 +0000 (Thu, 03 Feb 2011)Changed paths:   M /ciao.txt
r1 | luca | 2011-02-03 11:43:38 +0000 (Thu, 03 Feb 2011)Changed paths:   A /ciao.txt


A questo punto si supponga di voler cercare il file ciao.txt, che esisteva nel repository e che poi è stato cancellato. Diventa immediato eseguire:

svn log -vq | tr -d '\n'| sed -r 's/-{3,}/\n/ | grep ciao.txt

e ottenere di conseguenza:

r3 | luca | 2011-02-03 11:44:09 +0000 (Thu, 03 Feb 2011)Changed paths:   D /ciao.txt
r2 | luca | 2011-02-03 11:43:54 +0000 (Thu, 03 Feb 2011)Changed paths:   M /ciao.txt
r1 | luca | 2011-02-03 11:43:38 +0000 (Thu, 03 Feb 2011)Changed paths:   A /ciao.txt


che corrisponde alla storia del file stesso, con in cima alla lista la cancellazione (D - delete) del file alla revisione r3. E' quindi possibile recuperare il file con:

svn update -r 2 ciao.txt

Da notare che la revisione dalla quale si fa il checkout del file è una in meno rispetto a quella della cancellazione, poiché se si eseguisse l'update alla versione della cancellazione il file risulterebbe ancora cancellato.
In realtà così facendo non si ha la possibilità di reinserire il file nel repository, a meno che non si usino trucchi di ridenominazione a aggiunta. Se quello che si vuole è annullare la cancellazione della revione si deve procedere come segue:

svn merge -c -3  file:///home/luca/tmp/test/

Questa volta il numero di revisione specificato è quello della revisione di cancellazione. Il segno negativo davanti al numero forza il sistema a confrontare le revisioni x e x-1, quindi la 3 e la 2 e ad annullare le modifiche della revisione 3. Occorre specificare il percorso del file del repository dal quale estrarre le versioni, poiché specificando il file stesso si otterrà un errore di file non sotto controllo di versione. Il sistema risponderà con quello che chiama "reverse merge" e il file ricomparirà nella versione corrente pronto per essere nuovamente committato:


--- Reverse-merging r3 into '.':
A    ciao.txt

Anche quando è in ritardo l'OpenSource è migliore del software commerciale

Un interessante articolo che spiega, qualora ce ne fosse ancora bisogno, di come l'OpenSource sia migliore del modello closed anche quando agisce in ritardo. E spesso il ritardo è dovuto a blocchi imposti da un precedente modello closed!

Come nota a margine: ma c'è ancora chi usa telnetd?

Dividere uno stream in blocchi

Capita spesso di dover spezzare un array di byte (o di altro tipo) in "pezzi" da inviare a client remoti in modo continuo fino all'esaurimento dei dati stessi. Il pattern per implementare questa funzionalità  è abbastanza semplice, e ovviamente esistono diverse implementazioni più o meno raffinate; propongo di seguito quella che sono solito usare:

int APPLICATION_BYTE_STREAM_SIZE = 1024;
byte[] myByteArray = ...;
OutputStream os = ...;


int chunksToSend = myByteArray.length / APPLICATION_BYTE_STREAM_SIZE;
for( int chunkNumber = 0; chunkNumber < chunksToSend; chunkNumber++ )
     os.write(  myByteArray,   
        APPLICATION_BYTE_STREAM_SIZE * chunkNumber,
        APPLICATION_BYTE_STREAM_SIZE );

// ho ancora dei chunk?
int lastChunkSize = myByteArray.length % APPLICATION_BYTE_STREAM_SIZE;
if( lastChunkSize > 0 )
    os.write(     myByteArray,
            myByteArray.length - lastChunkSize,
        lastChunkSize );

 
L'idea è abbastanza semplice: dato l'array di byte myByteArray che deve essere inviato in blocchi (chunk) da massimo APPLICATION_BYTE_STREAM_SIZE bytes si procede come segue:
 
1) si calcolano quanti chunk si devono spedire. Se la dimensione dell'array di byte è identica a quella di un singolo chunk si avrà, chiaramente, un chunk solo da spedire; se la dimensione è inferiore non si dovranno spedire chunk in questo primo step, altrimenti si spediranno i chunk necessari per avvicinarsi alla dimensione dell'array di byte.
 
2) nel caso in cui la dimensione dell'array di byte non sia multiplo di APPLICATION_BYTE_STREAM_SIZE si avrà  una rimanenza, che viene calcolata con una semplice divisione per resto: il risultato corrispondenrà  alla dimensione dell'ultimo chunk da inviare. Si procede quindi alla scrittura dell'ultimo chunk considerando che lo spiazzamento è pari ai byte rimanenti dal fondo dell'array.

Il procedimento di cui sopra puo' essere "compresso" in due modi: usando uno spiazzamento all'indietro oppure uno all'avanti (quest'ultimo risulta leggermente piu' chiaro). L'idea in entrambi i casi è quella di sapere in anticipo se ci sarà il chunk di chiusura (ossia quello dei byte rimanenti dovuto al fatto che l'array di byte non è un multiplo di APPLICATION_BYTE_STREAM_SIZE) e di usare un unico ciclo per la scrittura dei byte. Ovviamente occorre riconoscere il caso in cui si debba spedire un chunk intero o la parte di completamento.
Il ciclo all'indietro è il seguente:

int lastChunkSize =  myByteArray.length % APPLICATION_BYTE_STREAM_SIZE;
int chunksToSend = (myByteArray.length / APPLICATION_BYTE_STREAM_SIZE ) + ( lastChunkSize  > 0 ? 1 : 0 );

for( int chunkRemaining = chunksToSend; chunkRemaining > 0; chunkRemaining-- )
     os.write(  myByteArray,                       
             myByteArray.length-(APPLICATION_BYTE_STREAM_SIZE * (chunkRemaining - 1 ) )-lastChunkSize,                ( chunkRemaining != 1 ? APPLICATION_BYTE_STREAM_SIZE : lastChunkSize )
    );


Come si nota, il primo passo consiste nel calcolare la dimensione dell'ultimo chunk e considerare il numero di chunk da spedire incrementato di 1 nel caso tale dimensione non sia nulla (che significa che l'array ha dimensione multipla APPLICATION_BYTE_STREAM_SIZE). Il ciclo itera sul numero calcolato di chunk all'indietro, e quindi l'offset dal quale partire a scrivere è dato dalla differenza fra la dimensione totale dell'array, la dimensione di un singolo chunk (considerato quanti ne rimangono) e la dimensione dell'ultimo chunk, in modo che inizialmente l'offset sia nullo. La dimensione dei dati da scrivere dipende dal numero di chunk che rimangono: se ne rimane solo uno (l'ultimo) allora la dimensione è quella calcolata, altrimenti tutti i chunk hanno la stessa dimensione.
La versione con ciclo in avanti è leggermente piu' comprensibile:


int lastChunkSize =  myByteArray.length % APPLICATION_BYTE_STREAM_SIZE;
int chunksToSend = ( myByteArray.length / APPLICATION_BYTE_STREAM_SIZE );

for( int currentChunk = 0; 
     currentChunk < (chunksToSend + ( lastChunkSize  > 0 ? 1 : 0 ) );
     currentChunk++ )
        os.write( myByteArray,  
                  APPLICATION_BYTE_STREAM_SIZE * currentChunk,
           (currentChunk != chunksToSend ? APPLICATION_BYTE_STREAM_SIZE : lastChunkSize)            );           

In questo caso il numero di chunk viene considerato pari a quello dei chunk completi da inviare, anche se il ciclo itera su un eventuale chunk di completamento. Lo spiazzamento viene semplicemente calcolato in base ai chunk completi già spediti, mentre il numero di byte da scrivere cambia nel caso si siano spediti tutti i chunk completi.