• Non ci sono risultati.

10. IMPLEMENTAZIONE DI ELASTICDAEMON

10.3 Analisi del Codice sorgente

10.3.5 LaunchThread.java

Questa classe implementa tutte le procedure per l'esecuzione della scaling operation che ha il compito di aggiungere una nuova istanza al cluster. Quando un nuovo oggetto di tipo LaunchThread viene creato, sarà possibile poi utilizzarlo per avviare un nuovo thread, che avrà il compito di avviare una nuova istanza ed aggiungerla al cluster. Il costruttore riceve come parametri l'oggetto di tipo Istanza, che rappresenta la macchina virtuale che dovrà essere avviata, e l'oggetto di tipo ElasticClusterThread, dal quale potrà accedere a tutte le informazioni sul cluster.

public LaunchThread(Instance instance, ElasticClusterThread cluster) {

/* store private variables */

launchThread = new Thread(instance.getEmi());

this.instance=instance;

this.cluster = cluster;

System.out.println("Created Launch Thread for emi: " + launchThread.getName()); }

Il metodo principale di questa classe è run(), che viene invocato quando viene avviato il thread ad esso associato. In questo metodo sono realizzate tutte le procedure necessarie per garantire la corretta inizializzazione della nuova macchina virtuale.

Per prima cosa il metodo verifica se sul Eucalyptus sono disponibili risorse sufficienti per avviare una nuova macchina, invocando il metodo isAvailable(), il quale ritorna un valore booleano che indica se sia possibile avviare una nuova istanza. A questo punto viene invocato il metodo che si occupa del lancio dell'istanza su Eucalyptus, bisogna fornire come parametri il tipo di macchina da avviare e il nome dell'immagine da utilizzare. Quando il metodo launchInstance termina, è garantito che l'istanza è stata correttamente avviata e si trova nello stato “running”, si può procedere quindi alla modifica del file di configurazione del load balancer e a riavviarlo per rendere effettive le modifiche.

System.out.println("Launching thread started...");

/*if there are available resources on the cloud, start a new instance */ if(isAvailable()){

launchInstance(instance.getEmi(),instance.getType()); }else{

System.out.println("Not enough space for the new Instance of type: " + instance.getType());

return; }

/*get the load balancer of the cluster */

LoadBalancer lb = cluster.getLoadBalancer();

/* add the new instance to the load balance cfg file*/

lb.addBeServer(instance.getId(), instance.getIp(), 8080 , cluster.getClusterName());

/* restart the load balancer to route the traffic on the new instance */

lb.restart();

/* add the Instance to the cluster */

10.3. Analisi del Codice sorgente 159 Per eseguire le operazioni appena descritte si utilizzano i metodi forniti dall'oggetto di tipo LoadBalancer; prima viene invocato il metodo addBeServer(), che richiede come parametri l'indirizzo IP e la porta su cui ascolterà l'application server installato, insieme all'ID univoco dell'istanza e al nome del cluster a cui appartiene; una volta terminata l'esecuzione di questo metodo il file di configurazione è aggiornato e contiene tutte le informazioni sul nuovo server installato sull'istanza appena avviata. Infine si invoca il metodo restart della classe LoadBalancer, che riavvia HAProxy aggiornandone la configurazione in base al nuovo file appena creato.

isAvailable()

La realizzazione del metodo isAvailable() consiste in due fasi: invocazione del comando zones e l'analisi dell'output ottenuto. Per utilizzare il comando euca-describe-availability-zones viene invocato il metodo runCommand di una classe si supporto, che riceve come parametro una stringa contenente il comando da eseguire, e ritorna l'output ottenuto. Tale metodo di supporto utilizza la funzione exec della classe Process di Java.

StringBuilder cmdOut = CommandsHandler.runCommand("euca-describe-availability-zones "+

"verbose " +

" -a " + cluster.getLaunchConfiguration().getEc2AccessKey() +

" -s " + cluster.getLaunchConfiguration().getEc2SecretKey() +

" -U " + cluster.getLaunchConfiguration().getEc2Url());

String regexp = "AVAILABILITYZONE\\s+\\S+\\s+"+instance.getType()

+ "\\s+(\\d+) / \\d+\\s+";

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

int count=0; while(matcher.find()){ count++; if(Integer.parseInt(matcher.group(1))>0) returntrue; } if(count ==0){

System.out.println("Error in parsing euca-describe-availability-zones output"); System.exit(1);

}

returnfalse;

Dopo aver lanciato il comando, l'output ottenuto deve essere analizzato per verificare se sono disponibili le risorse necessarie, per fare questo si utilizza una regular expression che estrae il valore relativo al numero di istanze avviabili per un determinato tipo di macchina. Bisogna anche verificare questo valore su tutti gli eventuali cluster di nodi disponibili su Eucaluptus, per questo si è introdotto un ciclo che scandisce tutti i possibili match ottenuti dall'espressione regolare. Il metodo isAvailable() ritorna true se il numero estratto è maggiore di zero, false altrimenti. Un tipico output che si deve elaborare è il seguente:

bazzu@ubuntu-bazzu:~$ euca-describe-availability-zones verbose AVAILABILITYZONE cluster1 192.168.10.1

AVAILABILITYZONE |- vm types free / max CPU ram disk AVAILABILITYZONE |- m1.small 0007 / 0008 1 192 2 AVAILABILITYZONE |- c1.medium 0007/ 0008 1 256 5 AVAILABILITYZONE |- m1.large 0002 / 0004 2 512 10 AVAILABILITYZONE |- m1.xlarge 0001 / 0003 2 1024 20 AVAILABILITYZONE |- c1.xlarge 0001 / 0001 4 2048 30

In questo esempio il valore che viene estratto dall'espressione regolare, nel caso in cui il tipo di macchina richiesto sia m1.small è 0007, che indica la disponibilità di risorse per altre 7 istanze di quel tipo.

LaunchInstance

Questo metodo ha il compito di avviare una nuova istanza su Eucalyptus, succassevamente ne deve estrarre alcuni dei parametri univoci che il sistema le associa, come l'indirizzo IP privato e l'ID, infine deve verificare che lo stato dell'istanza passi da “pending” a “running”. LaunchInstance conclude la sua esecuzione quando verifica che l'avvio della nuova istanza è andato a buon fine. Per avviare una nuova istanza si esegue il comando euca-run-instances, fornendo tutti i parametri necessari per l'autenticazione e per la definizione dell'istanza che si deve avviare.

Il comando euca-run-instances, quando completa correttamente la sua esecuzione, fornisce in output l'ID univoco associato alla nuova istanza attivata; si deve quindi analizzare il risultato che si ottiene dal comando lanciato e estrarre da esso questo identificativo, che sarà fondamentale per impostare tutti gli altri parametri del sistema. Per estrarre l'ID dall'output viene invocato il metodo getIdFromLaunch() che riceve come parametro l'oggetto contenente l'output ottenuto, successivamente viene analizzato con una regular expression che estrae i dati richiesti, infine il valore dell'ID viene ritornato come stringa.

private String getIdFromLaunch(StringBuilder input){

String regexp = "INSTANCE\\s+(i-\\w*)\\s+" + instance.getEmi(); Pattern pattern = Pattern.compile(regexp);

Matcher matcher =pattern.matcher(input);

if(matcher.find())

return matcher.group(1);

else{

System.out.println("Error during launch of instance"); System.exit(1);

} }

/* command euca-run-instances */

StringBuilder cmdOut = CommandsHandler.runCommand("euca-run-instances "+

" -a " + cluster.getLaunchConfiguration().getEc2AccessKey() + " -s " + cluster.getLaunchConfiguration().getEc2SecretKey() + " -U " + cluster.getLaunchConfiguration().getEc2Url()+ " -k " + cluster.getLaunchConfiguration().getKeyName()+ " --addressing private" + " " +emi);

10.3. Analisi del Codice sorgente 161 L'ultima operazione che deve effettuare il metodo launchConfiguration() è quella di verificare che Eucalyptus completi le procedure di startup dell'istanza senza errori, si utilizza un ciclo che continua ad invocare il metodo isRunning(), che dice se l'istanza è nello stato “running” o meno.

Se l'istanza non è ancora entrata nello stato running, viene invocato il metodo checkStateAndGetIp(), che ha il compito di verificare lo stato dell'istanza, in più tale metodo deve estrarre dall'output ottenuto l'indirizzo IP che è stato assegnato all'istanza. Questa informazione deve essere ottenuta tramite il comando euca-describe-instances, infatti Eucalyptus assegna gli indirizzi ip alle macchine in un istante successivo al processo di inizializzazione, non è quindi possibile ottenere l'indirizzo di rete di una macchina se non quando è già stata avviata la sua procedura di startup. All'interno del ciclo è stata inserita una pausa, dell'ordine di qualche secondo, per evitare di sovraccaricare troppo il sistema e di inviare un numero eccessivo di richieste ad Eucalyptus.

while(!isRunning()){

checkStateAndGetIp();

Thread.sleep(CHECK_INTERVAL); }

Il metodo invocato nel ciclo appena descritto esegue due operazioni principali: esegue da linea di comando euca-describe-instances <id> utilizzando l'identificatore che è stato ottenuto precedentemente. Una volta ricevuto l'output da Eucalyptus ne elabora il contenuto utilizzando una regular expression, che verifica se lo stato della macchina è running e ne estrae l'indirizzo di rete ad essa assegnato.

privatevoid checkStateAndGetIp() throws IOException{

StringBuilder cmdOut = CommandsHandler.runCommand("euca-describe-instances "+instance.getId()+

" -a " + cluster.getLaunchConfiguration().getEc2AccessKey() +

" -s " + cluster.getLaunchConfiguration().getEc2SecretKey() +

" -U " + cluster.getLaunchConfiguration().getEc2Url());

getRunningStateAndIp(cmdOut); }

privatevoid getRunningStateAndIp(StringBuilder input){

String regexp = "INSTANCE\\s+"+ instance.getId() + "\\s+" + instance.getEmi() +

"\\s+((\\S*|\\.*)*)\\s+((\\S*|\\.*)*)\\s+(\\w*)";

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

if(matcher.find()){

if(matcher.group(5).equals("running")){ isRunning = true;

instance.setIp(matcher.group(3));

return;

}elseif(matcher.group(4).equals("terminated")){

System.out.println("Instance "+instance.getId() + " failed to startup"); System.exit(1);

} }

Il metodo launchInstance() introduce, prima di concludere la sua esecuzione, una pausa di qualche decina di secondi, in questo modo si consente al sistema operativo installato sulla macchina virtuale di completare il processo di boot. A questo punto l'avvio della nuova istanza è stato completato, si dispone di una nuova macchina virtuale in stato running, di cui il sistema conosce tutti i dati necessari per renderla effettivamente attiva nel cluster, ossia aggiungendola alle entry del load balancer, che inizierà ad inoltrarle le richieste degli utenti.

Operazioni finali di aggiornamento

Le ultime operazioni da svolgere per il LaunchThread sono quelle di aggiornamento del file di configurazione e del riavvio del server; l'implementazione di queste funzioni verrà descritta nel dettaglio quando verrà analizzata la classe LoadBalancer.java.

Come ultima istruzione il LaunchThread deve comunicare al Cluster la presenza di una nuova istanza attiva, che contribuirà a rispondere alle richieste inoltrate dal load balancer; per fare ciò viene invocato il metodo addInstance(Instance i) della classe ElasticClusterThread, bisogna sottolineare che l'aggiornamento della lista delle istanze avviene in mutua esclusione, al fine di garantire che non vi siano errori durante l'esecuzione degli altri thread concorrenti, come quello di monitoring