• Non ci sono risultati.

10. IMPLEMENTAZIONE DI ELASTICDAEMON

10.3 Analisi del Codice sorgente

10.3.6 LoadBalancer.java

Ogni oggetto di tipo LoadBalancer rappresenta un'istanza attiva del load balancer e fornisce le funzioni necessarie per interagire con HAProxy. Un oggetto di questa classe è sempre associato ad uno di tipo ElasticClusterThread, che rappresenta il cluster di istanze su cui il load balancer bilancerà il traffico che riceverà in ingresso sul frontend.

Per inizializzare un oggetto di tipo LoadBalancer si devono fornire diversi parametri al costruttore della classe, quali indirizzo IP e porta di ascolto del frontend, il nome del cluster di istanze associato, oltre ai parametri per la gestione della session stickiness a livello 7. Il costruttore della classe, oltre a memorizzare i parametri ricevuti, crea il file di configurazione e una copia dell'eseguibile di HAProxy.

public LoadBalancer(String clusterName, String fe_ip, int fe_port,

String cookieName,int sessionTimeout,int balanceAlgorithm) throws IOException{ ...

createCfgFile(clusterName,cookieName,balanceAlgorithm);

/*prepare the executable file */

copyHAProxy("HAProxy_"+clusterName); }

Il metodo createCfgFile() costruisce un file di configurazione a partire da alcuni pattern preimpostati, personalizzandoli con i parametri forniti al costruttore della classe, mentre copyHAProxy() effettua una copia del file eseguibile con il nome: HAProxy_<nomecluster>.

I metodi più importanti di questa classe, che vengono invocati durante le scaling operation sono quelli che permettono di aggiungere o rimuovere un server dal file di configurazione, di riavviare il load

10.3. Analisi del Codice sorgente 163 balancer in modalità soft-restart, di interrogare il load balancer per sapere se su un determinato server sono ancora attive delle sessioni e infine per impostare un server in modalità “zombie”.

AddBeServer e removeBeServer

Questi due metodi effettuano operazioni opposte sul file di testo di configurazione di HAProxy, il primo legge il file di configurazione da disco, scandisce il contenuto fino a trovare la posizione in cui aggiungere l'entry del server e inserisce una nuova riga in quel punto; successivamente il file di configurazione viene salvato su disco, sovrascrivendo la versione precedente. Il secondo metodo effettua la stessa sequenza di operazioni, ma al posto di inserire una nuova entry, elimina quella relativa al server indicato come parametro del metodo. Verrà di seguito descritta solo l'implementazione del metodo addBeServer(), che risulta molto simile a quella di removeBeServer(), essendo le procedure svolte molto simili.

Per prima cosa il metodo invoca una funzione di supporto per ottenere i dati dal file di configurazione, restituisce un oggetto di tipo StringBuilder, contenente tutto il testo letto dal file. Si deve poi trovare la posizione corretta in cui inserire la nuova riga che descrive il nuovo server di backend da aggiungere, anche in questo caso viene utilizzata un regular expression che trova come corrispondenza le entry del backend.

publicvoid addBeServer(String srvId, String srvIp, int srvPort, String clusterName)throws IOException{ StringBuilder cfg = getCfgFile();

/* search for the correct backend section*/

String regexp = "\\t#"+clusterName + " backend servers:\\n(\\tserver \\S* " + REGEX_IP + ":\\d* cookie \\S*\\n)*";

Pattern pattern = Pattern.compile(regexp); Matcher backendSrv =pattern.matcher(cfg);

int numMatches=0; int end=0; while(backendSrv.find()){ numMatches++; end=backendSrv.end(); }

/* if there is no match, or more than one, show error message */ if(numMatches == 0 ||numMatches >1){

System.out.println("Error while finding backend servers for cluster "+clusterName); System.exit(1);

}

}

Una volta trovato un match nel file di configurazione si deve estrarre l'indice in cui si deve inserire la nuova riga, si utilizza il metodo end() della classe matcher, che ritorna l’indice dell'ultimo carattere che corrisponde alla regular expression. Il formato che si ricerca all'interno del file di configurazione è il seguente:

#Cluster1 backend servers:

server i-233243 192.168.20.20:8080 cookie i-233243 server i-456234 192.168.20.21:8080 cookie i-456234

Per facilitare il compito della regular expression è stato inserita una riga “sentinella” (#Cluster1 backend servers:) che permette di individuare più facilmente le entry dei server di backend all'interno del file di configurazione. A questo punto è sufficiente aggiungere la nuova entry con i parametri dell'istanza e salvare il nuovo file su disco.

/*insert the new server in the correct backend*/

cfg.insert(end, "\tserver "+srvId+ " " + srvIp +":" + srvPort + " cookie " + srvId+"\n");

/* write the cfg file to disk */

writeToCfg(cfg);

hasActiveSessions

Questo metodo viene invocato durante le procedure di scale-down, viene utilizzato per interrogare il load balancer e per sapere se sono presenti sessioni attive su un determinato server. Per interagire con il load balancer è necessario inviare una stringa del tipo: show table <nomebackend> data.act_session srv <nomeserver> sul socket unix su cui ascolta l'istanza del load balancer. I parametri che devono essere passati a questo metodo sono semplicemente il nome del server di cui si vogliono ricevere le informazioni e il nome dell'autoscaling cluster a cui è associato il load balancer. Per inviare tale comando al load balancer si utilizza il già citato metodo exec(), utilizzando però lo script di bash launcher.sh che è in grado di eseguire i comandi sfruttando le funzioni della shell, come il carattere speciale “pipe”.

publicboolean hasActiveSessions(String srvName, String clusterName)throws IOException{

/* launch the command on the socket to verify if there are still active sessions for the instance * example:

* echo "show table tomcat data.act_session srv web1" |sudo socat -s unix-connect:/tmp/HAProxy stdio */

StringBuilder input = CommandsHandler.runCommand("./launcher.sh " +

"echo show table "+clusterName + "_backend data.act_session srv " + srvName +" "

+ PIPE +" " +SOCAT_CMD + lbSocket + " stdio");

/*parse output*/

String regexp = "has still active sessions"; Pattern pattern = Pattern.compile(regexp); Matcher matcher =pattern.matcher(input);

if(matcher.find())

returntrue;

else

returnfalse; }

L'output viene poi analizzato e si verifica la presenza della stringa “has still active sessions”, se questa è presente il metodo ritorna true, altrimenti false.

SetZombie

Il metodo setZombie() viene invocato durante la scale-down operation, per comunicare al load balancer di non inoltrare ulteriori richieste a tal server. I parametri richiesti sono anche in questo caso il nome del

10.3. Analisi del Codice sorgente 165 server e il nome del cluster, necessari per identificare correttamente la coppia backend/server da impostare in modalità zombie. Il metodo deve inviare un comando al socket unix del tipo: disable server <namobackend>/<nomesserver>; si utilizza il metodo exec, sfruttando lo script bash launcher.sh come già descritto nel metodo hasActiveSessions().

publicvoid setZombie(String srvName, String clusterName) throws IOException{

StringBuilder cmdOut = CommandsHandler.runCommand("./launcher.sh " +"echo disable server "

+clusterName + "_backend/"+srvName +" "+PIPE+" " + SOCAT_CMD + lbSocket + " stdio"); String regexp = "\\w+"; //any output is an error message

Pattern pattern = Pattern.compile(regexp); Matcher matcher =pattern.matcher(cmdOut);

if(matcher.find()){

System.out.println("Error while disabling server: " + srvName +

" in cluster: " + clusterName);

System.exit(1); }

}

Il comando disable server non mostra nessun output se l'operazione va a buon fine, di conseguenza il metodo si limita a verificare che nell'output non sia presente alcun carattere, se accade tale evento viene inviato un messaggio di errore, altrimenti il metodo termina correttamente.