Ridondiamo un controller MooseFS con uCarp

Tra tutti i filesystem cluster, il mio preferito è senza dubbio MooseFS in quanto è sicuramente il più collaudato nell’aggiunta dei nodi e quello più facile da gestire in caso di problemi.

L’unica nota dolente di questo filesystem è la necessità di avere un controller che rappresenta un Single Point of Failure; gli sviluppatori di MooseFS sono ben consci del problema ed è in roadmap la funzionalità di ridondanza del controller per le prossime versioni; nel frattempo, come consigliato sul sito ufficiale, possiamo fare riferimento ad un qualsiasi sistema di configurazione Fault Tolerance per ridondare il Controller. In questo articolo ci addentreremo più nello specifico rispetto a quanto fatto in precedenza nella configurazione dell’alta affidabilità del server master e per farlo, come consiglato dal sito ufficiale, utilizzeremo uCarp che è un sistema molto più semplice rispetto ai più conosciuti Heartbeat, Pacemaker e Corosync, ma che proprio per questo rappresenta una valida alternativa nei sistemi specializzati come i controller MooseFS. A dire il vero, una caratteristica di uCarp, è quella di poter avere un numero qualsiasi di server slave che possono diventare master e quindi aumentare l’affidabilità passiva del sistema: in questo articolo proporremo un sistema con un totale di 3 server, proprio per dimostrare questa funzionalità.

Innanzi tutto, spieghiamo in parole semplici come è strutturato MooseFS: il sistema si compone di un controller (mfsmaster) al quale si collegano una serie di server storage (chunck server) ove risiedono i “chunk”. In pratica, sullo storage condiviso di ogni nodo, è presente un albero di directory contenenti dei file di dimensioni prestabilite (i cosidetti chunck) che contengono le informazioni dei file salvati sul filesystem; vediamo i chunck come dei blocchi di disco di livello più elevato. Analogamente ai filesystem classici, anche su MooseFS esiste un indice dove è memorizzata la struttura del filesystem (in pratica di quali chunk è composto un file) e questo è il compito principale del controller: questo indice dei file è memorizzato nel “Metadata” che è mantenuto nella memoria RAM del controller.
Nel momento in cui avviene un arresto “soft” del controller, il contenuto della RAM, viene trasferito su disco nel file metadata.mfs; nel momento in cui il demone mfsmaster viene farro ripartire, questo file viene letto in RAM e, quindi, viene ricostruita la struttura del filesystem distribuito.
Il fatto di memorizzare il metadata in RAM, rende MooseFS più veloce rispetto ad altri Filesystem distribuiti… ma lo rende più sensibile ai guasti.

In aiuto ci viene un terzo demone, mfsmetadata: questo si prende la briga di collegarsi periodicamente al demone mfsmaster e salvare un “changelog” per ricostruire la struttura del filesystem in caso di arresto non soft; data la delicatezza delle informazioni contenute nel metadata, è consigliabile utilizzare il demone mfsmetadata su più server in modo da avere più punti di recupero del sistema e riuscire a far ripartire il cluster anche in caso di guasto all’hard disk del server master.

Proprio sull’utilizzo di questo demone si basa il funzionamento del sistema di fault tolerance che andremo ad implementare e, grazie alla possibilità di uCarp di far partire uno script nel momento in cui un server diventa master, implementeremo tutte le procedure necessarie per ricosturire automaticamente il file metadata.

Server master: ip 172.16.20.1
Server slave 1: ip 172.16.20.2
Server slave 2: ip 172.16.20.3

IP condiviso: 172.16.20.100

Innanzi tutto, sui server master e slave, installiamo i demoni mfsmaster e mfsmetadata come descritto in questo articolo e in questo articolo.

Ora, per andare oltre rispetto alla precedente installazione, installiamo il demone uCarp sui tre server. Ammettendo di avere un server Debian o Ubuntu, usiamo apt-get:

apt-get install ucarp

Ora è arrivato il momento di configurare uCarp per gestire l’IP condiviso e i demoni di MooseFS; per fare ciò utilizzeremo quattro semplici script in bash:

<strong>/usr/local/sbin/ucarp_startup - server master</strong>
#!/bin/bash

ucarp -i eth1 -s 172.16.20.1 -v 10 -p secret -a 172.16.160.100 -u /usr/local/sbin/vip-up -d /usr/local/sbin/vip-down -B -z

<strong>/usr/local/sbin/ucarp_startup - server slave 1</strong>
#!/bin/bash

ucarp -i eth1 -s 172.16.20.2 -v 10 -p secret -a 172.16.160.100 -u /usr/local/sbin/vip-up -d /usr/local/sbin/vip-down -B -z

<strong>/usr/local/sbin/ucarp_startup - server slave 2</strong>
#!/bin/bash

ucarp -i eth1 -s 172.16.20.3 -v 10 -p secret -a 172.16.160.100 -u /usr/local/sbin/vip-up -d /usr/local/sbin/vip-down -B -z

Attraverso questo script, (una sola riga) avviamo sui server il demone uCarp. I parametri che impostiamo sono:

  • -i eth1: è la scheda di rete fisica dove “appoggiare” il demone uCarp

  • -s 172.16.20.1,2 e 3: sono gli indirizzi IP reali della scheda di rete

  • -v 10: è un numero di riferimento dell’interfaccia virtuale creata da uCarp; va bene un qualsiasi numero da 1 a 255, basta che sia identico sui due server

  • -p secret: è una password comune sui server

  • -a 172.16.20.100: è l’indirizzo IP “condiviso” tra i server

  • -u /usr/local/sbin/vip-up: è lo script che sarà eseguito quando il server diventerà master (vedi oltre)

  • -d /usr/local/sbin/vip-down: è lo script che sarà eseguito quando il server diventerà slave (vedi oltre)

  • -B: esegue il demone in background

  • -z: esegue lo script impostato al parametro -d quando viene chiuso il demone uCarp (per sicurezza)

    /usr/local/sbin/vip-up

    !/bin/bash

    exec 2> /dev/null

    ip addr add “$2”/24 dev “$1”
    /usr/local/sbin/metadata_recover &
    exit 0

Questo script viene eseguito quando il demone uCarp reputa di eleggere a master il server e viene lanciato con due parametri: l’interfaccia di rete ($1) e l’indirizzo IP condiviso ($2).
Lo script aggiunge quindi l’indirizzo ip condiviso (172.16.20.100) alla scheda di rete (eth1) e lancia un secondo script in background (/usr/local/sbin/metadata_recover) che vedremo successivamente.

<b>/usr/local/sbin/vip-down</b>
#! /bin/sh
exec 2> /dev/null

ip addr del "$2"/24 dev "$1"

Lo script qui sopra è analogo al precedente ma serve a declassare un server in slave; pertanto viene lanciato il comando per cancellare l’indirizzo ip condiviso dalla scheda di rete.

<b>/usr/local/sbin/metadata_recover </b>
#!/bin/bash
MFS='/usr/local/var/mfs'
sleep 3

if ip a s eth1 | grep 'inet 172.16.20.100'
then
    mkdir -p $MFS/{bak,tmp}
    cp $MFS/changelog.* $MFS/metadata.* $MFS/tmp/

    kill $(pidof mfsmetalogger)
    mfsmetarestore -a

    if [ -e $MFS/metadata.mfs ]
    then
        mv -p $MFS/sessions_ml.mfs $MFS/sessions.mfs
        mfsmaster
        mfscgiserv
        mfsmetalogger
    else
        kill $(pidof ucarp)
    fi
    tar cvaf $MFS/bak/metabak.$(date +%s).tlz $MFS/tmp/*
    rm -rf $MFS/tmp
fi

Questo è lo script che fa il grosso del lavoro e viene lanciato da vip-up quando un server diventa master.
Innanzi tutto setto la directory di lavoro del server (/usr/local/var/mfs) e attendo 3 secondi in modo da essere certo che l’IP condiviso sia settato sull’interfaccia di rete. Se è così procedo con lo script che sposta eventuali vecchi file changelog. e metadata. in una directory temporanea. ferma il demone del metalogger (non essendoci un master funzionante è meglio che questo demone sia temporaneamente fermato) e lancia lo script mfsmetarestore in grado di estrapolare i dati dai file changelog_ml. e creare un nuovo file metadata.mfs.
Se lo script riesce nel suo intento, viene ripristinato il file sessions.mfs contenete le sessioni dei client collegati al server e vengono avviati i demoni di mfsmaster, mfsmetalogger e mfscgiserv (l’interfaccia web di controllo): fino a questo momento, i server che montano il filesystem non ricevono risposta dal server; da questo momento in poi il sistema riprenderà a funzionare regolarmente.
Nel caso in cui lo script mfsmetarestore non dovesse riuscire a ricostrire il file metadata.mfs, lo script ferma il demone uCarp e quindi concede la possibilità ad un ulteriore server slave, se presente, di diventare master.
Ultima operazione, viene creato un file di backup con il contenuto dei vecchi file metadata.
e changelog.*… non si sa mai.

Nuovo pannello CRM

Si stanno per avvicinare le feste di fine anno, per cui abbiamo pensato di prepararvi un piccolo regalino di Natale aggiungendo una nuova funzionalità al nostro sistema: il pannello CRM.

Nel riquadro “Servizi” qui a sinistra, è ora presente la nuova voce “Pannello Clienti” attraverso la quale potrete accedere direttamente al nostro CRM per poter monitorare lo stato delle fatture, le scadenze dei servizi sottoscritti, eventuali appuntamenti con il nostro personale e lo stato di avanzamento dei Ticket Helpdesk.

Dal mese di Gennaio, in fattura, troverete indicata la casella email configurata come contatto per il cliente che corrisponde alla username di accesso; la password corrisponde alla Partita IVA dell’azienda, così come indicata in fattura; ovviamente, una volta effettuato l’accesso, potete modificarla a Vostro piacimento. Per ottenere i dati di accesso prima del recapito della prossima fattura, potete inviare una richiesta attraverso il modulo “Contattami” qui sopra.

Per l’apertura dei ticket, vi ricordiamo che è sufficiente che il contatto aziendale (indicato in fattura) invii una email ad uno dei seguenti indirizzi email a seconda della tipologia:

  • amm@azns.it per richieste di carattere amministrativo (informazioni su fatture, contratti, pagamenti, ecc.)

  • sales@azns.it per richieste di carattere commerciale (preventivi, modifiche di contratto, ecc.)

  • support@azns.it per richieste di carattere tecnico (assitenza su servizi, prodotti, segnalazione di malfunzionamenti, ecc.)

Le richieste di carattere amministrativo saranno prese in carico con cadenza settimanale, il lunedì; le richieste di carattere commerciale e tecnico saranno prese in carico con cadenza giornaliera e risposta assicurata entro le 36 ore lavorative successive alla richiesta. Per clienti con contratti con SLA inferiore alle 36 ore, il sistema scala automaticamente la richiesta in modo da rispettare la maggiore priorità.

Nel frattempo…

Buon Natale e Felice Anno Nuovo!

Cosa si intende per REST

In questo articolo facciamo un po’ di teoria: si sente spesso parlare di API, cioè la possibilità di comunicare con un determinato software attraverso delle istruzioni impartite dall’esterno.
Normalmente si utilizzano due protocolli simili: il SOAP e l’XML-RPC, anche se il secondo è senza dubbio il più conosciuto e utilizzato.
In questo articolo ci soffermiamo su un acronimo che spesso si accompagna alla parola API: REST.

Cosa sono le REST API, quindi?
REST è l’acronimo di REpresentational State Transfer ed indica il fatto che rappresentiamo l’azione da eseguire su un determinato oggetto in base al metodo di richiesta di trasferimento.
Detta così è difficile, però andando in profondità risulterà chiaro.
Pensiamo ad un form HTML “vecchia maniera” su un server web: i modi con cui è possibile colloquiare con un web server (pensiamo ai vecchi form, per semplicità) sono quattro: GET, POST, PUT e DELETE.
Fatalità, anche le operazioni base che possiamo fare su un record all’interno di un comune database relazionale sono quattro: Create, Read (Select), Update e Delete… e anche per questo esiste un acronimo: CRUD.
Il REST non fa altro che associare i quattro metodi dei form HTML con le quatto operazioni base degli oggetti: GET con Read (Select), POST con Create, PUT con Update e, ovviamente, Delete con Delete.
Quindi, ammettendo di avere una REST API che agisce su un determinato oggetto, invierò all’API una richiesta di tipo GET per leggere i dati e vedere quindi il contenuto dell’oggetto; oppure invierò all’API una richesta di tipo POST per aggiornarlo e così via.

Primi passi con #MongoDB

MongoDB è uno database appartente alla nuova tipologia “NoSQL”, particolarmente adatti per le applicazioni Cloud. Lo scopo di MongoDB è quello di essere facilmente scalabile, orientato ai documenti ed estremamente veloce.
Per MongoDB, difatti, tutte le informazini memorizzate sono dei documenti in formato JSON e, per memorizzarle, utilizza uno storage basato su GridFS che quindi è per definizione scalabile orizzontalmente attraverso l’utilizzo contemporaneo di più nodi MongoDB con l’Auto-Sharding, che possono funzionare sia per far scalare la dimensione dello storage, sia come repliche e quindi aumentare la tolleranza ai guasti e la velocità di I/O.
Tra gli utilizzatori di MongoDB citiamo il social network FourSquare, la Disney, bit.ly, SAP e SourceForge.
Per chi è abituato ad usare i classici database SQL (MySQL, Oracle, PostgreSQL) è un cambiamento radicale perchè MongoDB utilizza un linguaggio simile a Javascript e, quindi, la sintassi è completamente diversa rispetto al comune SQL. In qualsiasi caso, la sintassi non è tremendamente complessa, anzi, MongoDB punta a facilitare la fase di sviluppo rispetto ai comuni database relazionali.

In questo articolo proviamo a installare un server MongoDB e muovere i primi passi: queste informazioni sono liberamente tratte dal tutorial sul sito ufficiale.

Riguardo l’installazione, è parecchio semplice in quanto praticamente tutte le distribuzioni Linux hanno un pacchetto pronto da installare. Una volta installato il pacchetto, è sufficiente far partire il server (mongod) e attendere qualche istante per la prima inizializzazione.
MongoDB, al pari di altri database, ha una console per collegarsi al server e lanciare direttamente comandi: ovviamente gli stessi comandi possono essere lanciati anche dal nostro applicativo che si collegherà al database attraverso i driver disponibili per tutti i più diffusi linguaggi di programmazione.

Lanciamo quindi la console attraverso il comando “mongo”:

# mongo
MongoDB shell version: 2.0.1
connecting to: test
>

La console ci dice che ci siamo collegati al database “test”; per collegarsi ad un altro database esiste il comando “use”, simile all’analogo comando di MySQL.

> use mydb
switched to db mydb

Ecco una prima ma sostanziale differenza rispetto ai comuni database RDBMS: ci siamo collegati ad un database (mydb) senza averlo creato! Difatti, è proprio così: mydb non esisteva e, in realtà, non esiste ancora; mydb sarà creato dinamicamente nel momento in cui inseriremo dei dati. In MongoDB i database, gli schemi delle tabelle (anzi, in questo caso si chiamano “collection”) e gli indici sono creati dinamicamente all’occorrenza; quindi non dobbiamo preoccuparci di progettare al meglio il nostro database perchè anche lo schema (oltre al contenuto) crescerà insieme al nostro applicativo: una bella facilitazione!

Ora è arrivato il momento di inserire un po’ di dati: creiamo i due oggetti j e t e li salviamo all’interno della collection things.

> j = { name : "mongo" };
{"name" : "mongo"}
> t = { x : 3 };
{ "x" : 3  }
> db.things.save(j);
> db.things.save(t);
> db.things.find();
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
>

Usando la sintassi di JavaScript, è possibile usare i cicli direttamente nel motore database:

> for (var i = 1; i <= 20; i++) db.things.save({x : 4, j : i});
> db.things.find();
{ "_id" : ObjectId("4edcf2e743719b5c7e147c83"), "name" : "mongo" }
{ "_id" : ObjectId("4edcf2ea43719b5c7e147c84"), "x" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
has more

La shell ha un limite di 20 linee, per vedere le successive è possibile usare il comando “it”

> it
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Da questo comando notiamo anche un’altra particolarità di MongoDB: il fatto di essere “schema free”. Difatti ogni dato memorizzato è un oggetto formato da contenuti di tipo e quantità diversa.

I dati estrapolati da una query sono immagazzinabili in un oggetto particolare chiamato “cursore”. Ripetiamo la precedente query memorizzando il dato in una variabile e usando un ciclo per stamparla a video:

> var cursor = db.things.find();
> while (cursor.hasNext()) printjson(cursor.next());
{ "_id" : ObjectId("4edcf2e743719b5c7e147c83"), "name" : "mongo" }
{ "_id" : ObjectId("4edcf2ea43719b5c7e147c84"), "x" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Trattandosi di JavaScript, è possibile usare direttamente i metodi interni all’oggetto per ottenere il medesimo risultato:

> db.things.find().forEach(printjson);
{ "_id" : ObjectId("4edcf2e743719b5c7e147c83"), "name" : "mongo" }
{ "_id" : ObjectId("4edcf2ea43719b5c7e147c84"), "x" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Proviamo ora a tradurre la query SQL “SELECT * FROM things WHERE x=4” nel JavaScript di MongoDB:

> db.things.find({x:4}).forEach(printjson);
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "x" : 4, "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "x" : 4, "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "x" : 4, "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "x" : 4, "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "x" : 4, "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "x" : 4, "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "x" : 4, "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "x" : 4, "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "x" : 4, "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "x" : 4, "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "x" : 4, "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "x" : 4, "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "x" : 4, "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "x" : 4, "j" : 20 }

Il “SELECT *” non è mai buona cosa, per cui proviamo a tradurre la query “SELECT j FROM things WHERE x=4”

> db.things.find({x:4}, {j:true}).forEach(printjson);
{ "_id" : ObjectId("4edcf34643719b5c7e147c85"), "j" : 1 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c86"), "j" : 2 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c87"), "j" : 3 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c88"), "j" : 4 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c89"), "j" : 5 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8a"), "j" : 6 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8b"), "j" : 7 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8c"), "j" : 8 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8d"), "j" : 9 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8e"), "j" : 10 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c8f"), "j" : 11 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c90"), "j" : 12 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c91"), "j" : 13 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c92"), "j" : 14 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c93"), "j" : 15 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c94"), "j" : 16 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c95"), "j" : 17 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c96"), "j" : 18 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c97"), "j" : 19 }
{ "_id" : ObjectId("4edcf34643719b5c7e147c98"), "j" : 20 }

Per una traduzione dei comandi SQL in sintassi MongoDB vi rimando a questa pagina del sito ufficale.

Ultima cosa che cito senza approfondire in questa sede: in caso di interrogazioni complesse con dati provenienti da diverse collection, è presente una potentissima funzione chiamata MapReduce che sostanzialmente crea una collection temporanea con il risultato dell’operazione.

#OpenERP, personalizzazione dei report

OpenERP utilizza i report per generare i documenti da stampare: ad esempio gli ordini di vendita, le fatture e le note di credito sono dei report che quindi possono essere personalizzati.
Esistono molte possibilità per modificare il layout attraverso i moduli aggiuntivi di OpenERP (ad es. WebKit), però il metodo più efficace è senza dubbio l’installazione del plugin di OpenOffice.
Sia ben chiaro che questo metodo non è certo il più semplice in quanto, per personalizzare il contenuto del report aggiungendo nuovi campi, è necessario conoscere come è strutturato OpenERP, nonchè avere delle basi di programmazione in Python (il linguaggio con cui è stato sviluppato OpenERP).

Installazione del plugin
Innanzi tutto è necessario installare il modulo aggiuntivo di OpenERP “base_report_designer”; durante la fase di installazione del plugin, sarà richiesto di effettuare il download di un modulo aggiuntivo per OpenOffice (compatibile anche con LibreOffice). Nel caso in cui abbiate difficoltà a scaricare il plugin dalla vostra installazione di OpenERP, potete scaricarlo da qui.
Aprite quindi OpenOffice, scegliete “Gestione Estensioni”, aggiungete il file zip scaricato e riavviate il programma. A questo punto apparirà il nuovo menu “OpenERP Report Designer” che contiene le voci di menu per gestire la comunicazione con il gestionale.

Collegamento con il server
Innanzi tutto è necessario collegarsi con il server. Dal menu “OpenERP Report Designer” scegliete la voce “Server Parameters”. Inserite l’indirizzo IP del server, mentre la porta e il metodo di collegamento nella maggior parte dei casi non andranno modificati.
Alla schermata successiva, scegliete il database (l’azienda) e inserite i dati di autenticazione.

Modifica dei report e caricamento sul server
Il metodo più semplice per iniziare a lavorare con i report, è partire da un documento già pronto e modificarlo.
La vode di menu “Modify Existing Record” permette di collegarsi al server e scaricare un report già pronto per poi modificarlo. Potete scegliere quale report modificare (ad esempio Invoice – Invoices With Layout per modificare la fattura)
L’operazione inversa, cioè caricare sul server il report modificato, avviene attraverso la voce di menu “Send to the server”: nella finestra di conferma, è possibile scegliere di aggiungere al modulo le impostazioni di intestazione e piè di pagina di default che si impostano attraverso l’interfaccia di default di OpenERP in “Amministrazione”->”Aziende”->”Aziende”.

Come funziona il plugin
Dal punto di vista tecnico viene fatta una doppia/tripla conversione: OpenERP comprende un linguaggio per il report chiamto “rml” che assomiglia ad una sorta di XML. I modelli di report realizzati con questo linguaggio vengono memorizzati all’interno del database dell’azienda.
Quindi, quando si deve editare un modello di report, il plugin esegue una conversione da rml a sxw (il formato di OpenOffice) e viceversa quando si salva un modello; quando, ad esempio, si richiede la stampa di un report, avviene una conversione da rml a pdf.

Qualche dritta sulla personalizzazione dei template
Evitare di usare tabelle all’interno di tabelle: la conversione rml/pdf non riesce a fare il parsing corretto dei bordi e spesso ci si trova con linee “a caso” nel mezzo del report.
Per aggiungere un campo, è necessario conosce il nome della variabile: per risalire con facilità al nome della variabile si può fare riferimento alla guida contestuale dell’interfaccia web. Ad esempio, se vogliamo indicare in una fattura il valore della variabile contente la data di scadenza, andremo in “Contabilità”->”Fatture Clienti”->”Altre Informazioni” e posizioneremo il puntatore del mouse sul punto di domanda (?) a fianco di “Data Scadenza”. Apparirà una finestra contenete sia la descrizione dell’elemento, sia il nome del campo da includere nel report (in questo caso date_due).