Connessione a DB2 iSeries con PDO e driver nativi

db2iseriesconnectorLa connessione verso DB di sistemi iSeries è solitamente realizzata tramite ODBC, che ha un ottimo supporto nel mondo Microsoft Windows, ma non è altrettanto ben supportato quando is parla di sistemi Linux/Unix. In particolare la versione a 64bit dei driver ODBC per Linux di IBM iSeries ha dei bug noti ma di cui non è stata pianificata la risoluzione. Una buona alternativa può essere quella di utilizzare i driver nativi di DB2 e sostituire la connessione PDO ODBC esistente con una connessione PDO IBM DB2.
Questo richiede che lato AS400 sia disponibile una licenza per la connessione di Application Server a DB2. Se questa licenza è disponibile, il primo passo è quello di ottenere i driver dal sito ufficiale IBM. Questi driver sono gli IBM Data Server Driver Package (DS Driver) e sono disponibili a questo link.

Una volta scaricata la versione che vi interessa (32 o 64 bit), occorre decomprimerla in una posizione sul filesystem:

 tar -xvzf ibm_data_server_driver_package_linuxx64_v11.1.tar.gz
 mv dsdriver /opt/ibm/
 cd /opt/ibm/dsdriver
 ./installDSDriver

Il sistema procederà alla creazione dei file necessari, cioè le librerie che saranno utilizzate dai sorgenti PECL per la creazione dei moduli necessari.
Per poter eseguire correttamente l’installer di PECL occorre impostare la variabile di ambiente IBM_DB_HOME:

 export IBM_DB_HOME=/opt/ibm/dsdriver

A questo punto possiamo procedere con l’istallazione del modulo ibm_db2 tramite PECL.

 pecl install ibm_db2

Durante l’installazione vi verrà richiesto di specificare la cartella radice delle librerie DB2, che è sempre IBM_DB_HOME.

Una volta installato il modulo ibm_db2, passiamo al modulo PDO_IBM. L’installazione tramite PECL di questo modulo richiede come dipendenza l’installazione di pear/PDO, che però è ormai incluso nel gruppo di pacchetti dei PHP delle distribuzioni Centos e Ubuntu, quindi scarichiamo i sorgenti del modulo PDO_IBM e compiliamo manualmente il modulo:

 pecl download PDO_IBM 
 tar -xvzf PDO_IBM-1.3.4.tgz 
 cd PDO_IBM-1.3.4 

 phpize --clean 
 phpize  
 ./configure --with-pdo-ibm=/opt/ibm/dsdriver 
 make  
 make install

A questo punto, andremo a configurare il PHP per l’utilizzo di questi moduli, per Centos andiamo a creare un file sotto /etc/php.d che si chiama ibm_db2.ini, al cui interno andremo a scrivere:

extension=pdo.so
extension=ibm_db2.so
extension=pdo_ibm.so

Ora, possiamo scrivere un piccolo script PHP per testare la connessione verso il nostro sistema DB2 su AS400:

<?php
$db = new PDO("ibm:DRIVER={IBM DB2 ODBC DRIVER};DATABASE=<nome db>;".
"HOSTNAME=<ip del server>;PORT=446;PROTOCOL=TCPIP;", 
"<utente>", "<password>");

Se l’oggetto PDO risulta creato senza problemi, allora vuol dire che la configurazione è corretta, in caso contrario riceverete una eccezione. Ulteriori dettagli sulla configurazione delle librerie di sistema in caso di dipendenze non soddisfatte si possono trovare a questo link .

Per verificare se i moduli .so del PHP hanno delle dipendenze non soddisfatte potete utilizzare il comando ldd, esempio:

# ldd /usr/lib64/php/modules/ibm_db2.so        
 linux-vdso.so.1 =>  (0x00007ffc9c9bf000)        
 libdb2.so.1 => /opt/ibm/dsdriver/lib/libdb2.so.1 (0x00007fd46f702000) 
 libc.so.6 => /lib64/libc.so.6 (0x00007fd46f32f000)  
 libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fd46f0f7000)
 ...

 

Annunci

Soluzioni open source per il backup: BackupPC

backuppc-howtoVediamo oggi una valida soluzione open source per i backup. Il software in questione è BackupPC e consente di effettuare backup centralizzati e programmati automaticamente via rete. Il sistema non ha le funzionalità di prodotti come Veeam o Nakivo, ma comunque è in grado di soddisfare necessità basilari di creare repliche di filsystem, gestire backup incrementali e ripristinare singoli file sulla macchine di origine. Inoltre, utilizzando i protocolli FTP, SSH e SMB non necessita di un agent.

Vediamo ora l’installazione su Ubuntu/Debian. Per questi sistemi il pacchetto è disponibile nel repository base, mentre per Centos è necessario abilitare il repository Nux Desktop.

Avviamo l’installazione tramite:

sudo apt-get update
sudo apt-get install backuppc

L’installazione vi chiederà ora quale web server utilizzare, tipicamente la scelta default su Apache andrà bene nella quasi totalità dei casi:

Cattura
A questo punto il sistema vi suggerisce quale sarà l’utente per accedere all’interfaccia web di BackupPC:

Cattura2
Assicuratevi di cambiare la password.
Possiamo effettuare il primo accesso, all’url:

http://<server-name-or-ip>/backuppc

Da qui si accede all’interfaccia principale:

Cattura3
Per aggiungere un host, andiamo sulla sinistra sul link Edit Hosts. Si aprirà una finestra, in cui è possibile aggiungere tutti gli host di cui si intende effettuare il backup.

Una volta aggiunto, cliccando su Host Summary è possibile vedere lo stato di tutti i backup in corso o completati.
Cliccando sul nome dell’host, si accede al pannello di gestione, dove è possibile configurare alcuni parametri:

  • Xfer: il metodo di trasferimento dati, sono disponibili diversi protocolli tra cui FTP, SMB e RSYNC
  • Schedule: definisce i tempi di esecuzione delle procedure di backup
  • Email: notifiche sulla riuscita o eventuali errori che si sono verificati durante la procedura
  • Backup Settings: impostazioni generali

Lato client, occorre configurare la connessione in ingresso.
Per i sistemi Windows, occorre creare un utente backuppc che abbia i diritti di lettura (ed eventualmente scrittura per le operazioni di restore).
Per i sistemi Linux, occorre autorizzare l’utente backuppc sul server che effettua i backup ad accedere come utente root sui client.
Accediamo sul server di backup con l’utente corretto, con il comando:

sudo su - backuppc

Assicuriamoci quindi di avere un certificato SSH valido, generandone uno con il comando:

ssh-keygen

Fatto ciò, autorizziamo l’utente sulla macchina remota:

ssh-copy-id root@<client-name-or-ip>

Verifichiamo che l’accesso sia ora abilitato senza richiesta password con il comando:

ssh root@<client-name-or-ip>

Una volta impostati i parametri per la connessione al cliente, è possibile avviare immediatamente il backup dal pannello di gestione dell’host cliccando Start Full Backup
I backup vengono memorizzati nella seguente posizione:

/var/lib/backuppc/pc/ip_address_of_client/#_of_backup

Se il server di backup non dispone di spazio di archiviazione sufficiente, è possibile collegare una unità USB esterna o montare uno share su Samba o NFS e creare un link simbolico al percorso di memorizzazione.

Script archiviazione log in Python

python-logUna delle operazione più comuni è quella di archiviazione e compressione dei log. I file di log sono una risorsa fondamentale per il controllo delle procedure, ma un software che scrive log in maniera incontrollata (ad esempio per un bug della procedura che lo manda in loop infinito) può essere pericoloso. A volte può non bastare impostare un filsystem quota, in quanto anche riempiendo tutto lo spazio a disposizione, lo script risulterebbe comunque bloccato e potrebbe compromettere altri script che girano con lo stesso utente.
Vediamo quindi una semplice procedura per comprimere ed archiviare i file di log applicativi (Nota: per i log di sistema continueremo ad usare syslogd).

Per rendere più generale lo script faremo in modo di passargli come argomento la directory in cui si trovano i log, la directory di archiviazione e il numero di mesi per cui intendiamo conservare i log in formato non compresso. Possiamo utilizzare il modulo argparse in questo modo:

import argparse
parser = argparse.ArgumentParser(description='Log rotate utility')
parser.add_argument(dest='logdir',metavar='logdir')
parser.add_argument(dest='archivedir',metavar='archivedir')
parser.add_argument(dest='monthsback',metavar='monthsback')
args = parser.parse_args()
LOGDIR = args.logdir
ARCHIVEDIR = args.archivedir
MONTHSBACK = -int(args.monthsback)

Il modulo provvederà automaticamente a comunicare all’utente che è necessario specificare tutti gli argomenti.
Ora recuperiamo l’elenco delle cartella che vogliamo comprimere:

subdirectories = os.listdir(LOGDIR)

Attenzione che il comando in oggetto vi restituirà l’elenco dei file nella cartella, se volete selezionare solo le cartelle dovete verificare singolarmente se si tratta di una directory o di un file.
In questo particolare caso le cartelle sono suddivise per mese e anno, e sono nominate nel seguente modo “2015-09”. Quindi, trasformiamo la stringa in un oggetto di tipo datetime per usarlo successivamente per controllare se è dentro il range di periodo per l’archiviazione o meno:

log_folder_datetime = datetime.strptime(folder, "%Y-%m")

Ora, utilizziamo il modulo dateutil per calcolare l’oggetto datetime relativo all’inizio del periodo di archiviazione indicato:

log_rotate_datetime = datetime.today() + relativedelta(months=MONTHSBACK)

Ora, dopo aver controllato se la cartella in oggetto deve essere archiviata, procediamo alla compressione:

shutil.make_archive(os.path.join(ARCHIVEDIR, folder), 'zip', os.path.join(LOGDIR, folder))

Dopo aver creato il file compresso nella nuova posizione, possiamo rimuovere la cartella originale e tutti i file in essa contenuti:

shutil.rmtree(os.path.join(LOGDIR, folder), ignore_errors=True)

A fine procedura, tutti i file esterni al periodo indicato sono stati inseriti in un archivio (uno per cartella) e spostati nella destinazione finale di archiviazione.

Ecco il codice completo dello script:

__author__ = 'Andrea Trivisonno'

import argparse
import os
import shutil
from datetime import datetime
from relativedelta import relativedelta

parser = argparse.ArgumentParser(description='Log rotate utility')
parser.add_argument(dest='logdir',metavar='logdir')
parser.add_argument(dest='archivedir',metavar='archivedir')
parser.add_argument(dest='monthsback',metavar='monthsback')
args = parser.parse_args()

LOGDIR = args.logdir
ARCHIVEDIR = args.archivedir
MONTHSBACK = -int(args.monthsback)
WARNING = 1
ERROR = 2
OK = 0

try:
    # check if log dir and archive dir exists
    if os.path.exists(LOGDIR) and os.path.exists(ARCHIVEDIR) :
        # create a date object based on how many months old the log archive should be
        log_rotate_datetime = datetime.today() + relativedelta(months=MONTHSBACK)
        # check if folder is older or not than log rotate date limit
        print "Archiving log created before %s " % log_rotate_datetime
        # list all folder in the log dir
        subdirectories = os.listdir(LOGDIR)
        # for each subdir splt name in year and month
        for folder in subdirectories:
            # create a date object based on folder date
            log_folder_datetime = datetime.strptime(folder, "%Y-%m")
            if log_folder_datetime < log_rotate_datetime :
                # ok we should archive and delete folder
                print "Compressing folder %s" % os.path.join(LOGDIR, folder)
                shutil.make_archive(os.path.join(ARCHIVEDIR, folder), 'zip', os.path.join(LOGDIR, folder))
                print "Created archive %s in folder %s " %(folder+".zip", ARCHIVEDIR)
                shutil.rmtree(os.path.join(LOGDIR, folder), ignore_errors=True)
                print "Removed folder %s" % os.path.join(LOGDIR, folder)
            else :
                # no we should ignore the log folder
                print "Ignoring folder %s " % os.path.join(LOGDIR, folder)
    else:
        print "Error, logdir o archivedir not found."
        exit(ERROR);
except Exception, e:
    print "some error occured: %s" % e
    exit(WARNING)

Systemd e i file temporanei

imageDa qualche tempo, alcune distribuzioni hanno cominciato ad utilizzare Systemd per la gestione dei servizi di sistema. Non tutti si sono trovati d’accordo con l’introduzione di questo sistema e in alcuni casi ha portato alla scissione del team in alcune distribuzioni principali, come è successo ad esempio per Debian con Devuan.
Preferenze personali a parte, una grande novità introdotta con Systemd riguarda la gestione dei file temporanei. Infatti, è possibile assegnare una directory temporanea privata ad un servizio, il vantaggio è che in questo modo si riducono le possibilità di sovrascrivere un file temporaneo generato da altre aplicazioni. Inoltre, il fatto che la directory temporanea sia incapsulata in un chroot fa si che diminuiscano le possibilità di utilizzarla come punto di partenza per una escalation al sistema.
Ad esempio, ipotizziamo di creare un file temporaneo in PHP:

<?php 
$tmpfname = tempnam("/tmp", "FOO"); 
$handle = fopen($tmpfname, "w"); 
fwrite($handle, "writing to tempfile"); 
fclose($handle); 
        // do here something 
unlink($tmpfname); 
?>

Se il processo relativo a questo codice, ad esempio il web server Apache, è configurato per la gestione privata dei file temporanei, questo file sarà in realtà creato in una directory del tipo: /tmp/systemd-private-XXXXXXX/tmp
Ogni processo per il quale è stata richiesta la funzionalità avrà un direcotry separata. Occorre fare attenzione al fatto che, per il processo Apache, la directory sarà sempre /tmp e quindi dobbiamo fare attenzione a non gestire questo percorso al di fuori del processo in esecuzione, in quanto non sarà accessibile diversamente.
Le configurazioni dei servizi si trovano nella cartella /usr/lib/systemd/system, ad esempio per il servizio HTTPD:

[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true

[Install]
WantedBy=multi-user.target

La direttiva che ci interessa è PrivateTmp, in questo caso è impostata a True e quindi il processo Apache utilizzerà la struttura per i file temporanei privati. Ovviamente è possibile disabilitare questa funzione impostando a False il valore della direttiva.

HAProxy con terminazione SSL e certificati multipli

logo-medHAProxy è una soluzione open source per la realizzazione di architetture ad alta affidabilità in load balancing, in particolare per quanto riguarda applicazioni web.
Vediamo quindi come configurarlo per ottenere una architettura multi server web, in grado di gestire anche la terminazione SSL sul proxy. La connessione risulterà quindi protetta fino al proxy, le comunicazioni al di là di esso saranno connessioni standard, ma confinate all’interno della rete privata tra i server che eseguono l’applicazone web.
Installiamo HAProxy tramite il gestore dei pacchetti, nel nostro caso APT su Ubuntu 14.04:

$ sudo apt-get install haproxy

Una volta installato passiamo alla configurazione. Ci sono tre sezioni, una generale, una dedicata al frontend e una al backend. Per frontend intendiamo come si presenta il proxy verso l’esterno, le porte su cui deve essere in ascolto, i protocolli che gestisce e le regole di smistamento ai backend. Per backend, intendiamo le connessione a valle del proxy verso i server che eseguono fisicamente l’applicazione web.
Vediamo la configurazione generale:

global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon

# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private

# Default ciphers to use on SSL-enabled listening sockets.
# For more information, see ciphers(1SSL).
ssl-default-bind-ciphers kEECDH+aRSA+AES:kRSA+AES:+AES256:RC4-SHA:!kEDH:!LOW:!EXP:!MD5:!aNULL:!eNULL
ssl-default-bind-options no-sslv3

defaults
log global
mode http
option forwardfor
option http-server-close
option httplog
option dontlognull
timeout connect 5000
timeout client 350000
timeout server 350000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http

Qui viene definita la modalità di comunicazione generale, in questo caso per il protocollo HTTP, con aggiunta dell’opzione di forward per inserire negli header HTTP l’indirizzo IP del richiedente originale. Questo header aggiuntivo è utile nel caso in cui sia necessario leggere l’indirizzo IP del client, in quanto i server web dietro al proxy vedono sempre come IP sorgente quello del proxy stesso.
Vediamo quindi come configurare la sezione frontend:

frontend http-frontend
bind 127.0.0.1:80 # -- può essere modificato con l'IP pubblico del server
option tcplog
mode http
default_backend wwwbackend

frontend https-frontend
bind 127.0.0.1:443 ssl crt /etc/haproxy/mysite.net.pem crt /etc/haproxy/othersyte.net.pem
option tcplog
mode http
default_backend wwwbackend_s

In questo caso, abbiamo definito due porte, una web normale e una web SSL, abbiamo definito due percorsi differenti, poi vedremo perchè nella configurazione del backend. Ogni configurazione definisce su quale backend devono essere instradate le connessioni in ingresso.
La sezione relativa alla porta SSL ha in più la parte di configurazione relativa ai certificati, che devono essere nel formato PEM. I file possono essere creati a partire dal certificato e dalla relatiiva chiave privata, nel seguente modo:

$ sudo cat /etc/ssl/mysite.net/mysite.net.crt /etc/ssl/mysite.net/mysite.net.key \
| sudo tee /etc/ssl/mysite.net/mysite.net.pem

Definiamo ora i backend. Per questa particolare configurazione ho duplicato i backend e messo il server web Apache in ascolto su due porte, 81 e 82. Questo trucco mi consente di definire due diverse sezioni VirtualHost, ad esempio se voglio costruire un redirect automatico dal sito http a quello https:

backend wwwbackend
mode http
balance roundrobin
server 1-www 192.168.0.1:81 check # host web1
server 2-www 192.168.0.2:81 check # host web2

backend wwwbackend_s
mode http
balance roundrobin
server 1-www 192.168.0.1:82 check # host web1
server 2-www 192.168.0.2:82 check # host web2

Il bilanciamento in questo caso è round-robin, quindi ogni server web riceverà una richiesta a turno.
A questo punto non ci resta che riavviare il servizio HAProxy:

$ sudo service haproxy restart

Possiamo testare il corretto funzionamente con un semplice script in PHP:

<?php print gethostname() . "\n"; ?>

Richiamando lo script tramite broowser, noterete che verrà stampato il nome del primo server web, ad esempio “web1”. Ricaricando la pagina con F5, verrà invece stampato il nome del secondo server, ad esempio “web2”. Le successive richieste al server web saranno alternate tra un server e l’altro.

Connessione a database SQL Server da Linux con Python

python-logoPer connetterci ad un database SQL Server da Linux abbiamo due strade.
La prima è quella di utilizzare ODBC e di installare un driver adatto alla connessione con SQL Server, ad esempio FreeTDS.
Per installarlo, usiamo APT (valido su Ubuntu e altre distribuzioni basate su questo package manager):

$ sudo apt-get -y install freetds-bin tdsodbc unixodbc

A questo punto verifichiamo il contenuto del file /etc/odbcinst.ini (possono essere presenti altre sezioni, nel caso aveste installato altri driver ODBC):

[FreeTDS]
Description = FreeTDS Driver
Driver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so

Configuriamo il file /etc/freetds/freetds.conf (percorso valido per Ubuntu 14.04, potrebbe variare leggermente su altre distibuzioni):

[DATABASE]
host = IP_OR_HOSTNAME_DB_SERVER
port = 1433
tds version = 8.0

La porta TCP 1433 è la porta di default per le connessioni dirette ad SQL Server. Potrebbe essere necessario abilitare in modo esplicito l’apertura di questa porta.
Non ci resta che aggiungere il datasource all’interno del file /etc/odbc.ini:

[DSN_DATABASE]
Description = Descrizione del DSN
Driver = FreeTDS
Servername = DATABASE
Database = DATABASE_NAME

A questo punto il DSN è utilizzabile, quindi utilizziamo il Python per creare una connessione:

import pyodbc
user = 'sa'
password = 'password'
conn =  pyodbc.connect('DSN=DSN_DATABASE;UID='+user+';PWD='+password);
cursor = conn.cursor()
cursor.execute("""SELECT TOP 10 * FROM TABLE """)
multiple_rows = [dict(zip([column[0] for column in cursor.description], row)) for row in cursor.fetchall()]    
for row in multiple_rows:
	if row is not None:
		print row['ID']

Come si può vedere, prima di utilizzare il set di risultati ottenuti, trasformiamo l’array con indice numerico in array con indice testuale, per accedere alla colonna direttamente tramite il proprio nome.

Problemi di codifica durante connessione ODBC a iSeries

odbc-icon-13481La connessione ODBC verso iSeries a volte può dare dei problemi relativi ad una non corretta interpretazione della codifica utilizzata nella tabella sorgente. In questi casi, il driver ODBC di IBM può cominciare a comportarsi in modo imprevedibile, dando errori di esaurimento memoria o addirittura dei segmentation fault.
Nel mio caso sono riuscito a risolvere il problema, specificando all’interno del file di configurazione presente in /etc/odbc.ini l’esatta codifica utilizzata dalla connessione, specificando il CCSID appropriato:


[AS400_CONNECTION]
Description = iSeries Access ODBC Driver
Driver = IBM i Access ODBC Driver
System = 192.168.0.1
Naming = 0
DefaultLibraries = XXX_DAT,XXX_OBJ,QGPL
Database =
ConnectionType = 0
CommitMode = 2
ExtendedDynamic = 1
DefaultPkgLibrary = QGPL
DefaultPackage = A/DEFAULT(IBM),2,0,1,0,512
AllowDataCompression = 1
LibraryView = 0
AllowUnsupportedChar = 0
ForceTranslation = 0
Trace = 0
CCSID = 280

Il CCSID corrisponde alla codifica IBM280 secondo le tabelle di conversione di iconv. Per recuperare la stringa in formato UTF-8 è quindi necessario fare una ulteriore conversione, mediante il comando iconv:

$db_conn = odbc_connect("AS400_CONNECTION",ODBC_USER,ODBC_PWD);	
$resquery = odbc_exec($db_conn,"select * from table");
while ($ro = odbc_fetch_array($resquery )) 
{
      $stringa = iconv("IBM280","UTF-8", $ro['SHIPTNT'])
}

A questo punto otterrete la variabile con il valore corretto, senza rischiare problemi di segmentation fault.