Università degli Studi di Salerno

Dipartimento di Informatica ed Applicazioni "R.M. Capocelli"

SEcS: Scalable Edge-computing Services
Che cosa è SEcS  

SEcS Tutorial. Programmazione

Sommario

Plugin Dispatcher
Creazione di un Plugin Remoto
Programmazione di Meg Remoti
Registrazione dei Plugin

 

  Plugin Dispatcher

All'interno della piattaforma SEcS è possibile distinguere due tipi di plugin, quelli che fungono da Dispatcher e quelli che implementano i servizi intermediari remoti (Plugin Remoti).
I Dispatcher vengono registrati come dei plugin standard di WBI, ma in realtà svolgono delle operazioni molto più complesse.
Diamo uno sguardo ai ruoli che svolge il Dispatcher e come sono state implementate.

Il Dispatcher si occupa dell'intera gestione della richiesta effettuata e, così come anche i Plugin Remoti, si interfaccia con il Registro Distribuito che si occupa di mantenere aggiornato lo stato del sistema.
Quando arriva una richiesta dal client per prima cosa si occupa di identificare l'utente e di conoscere la configurazione dei servizi (associata all'utente) da applicare.

Le schede degli utenti vengono mantenute in una directory particolare creata dal Plugin. Tale directory rispecchia lo stesso insieme di utenti per ogni macchina su cui sono attive delle sessioni locali. Ciò significa che quando un utente si registra, i dati vengono memorizzati non solo sul Dispatcher su cui avviene la registrazione, ma anche su tutti i proxy disponibili in quel momento, tutto tramite operazione di broadcast.
Una volta che l'utente si è identificato sul Dispatcher può iniziare la navigazione "personalizzata". Infatti da questo momento su tutte le richieste verranno applicati i servizi selezionati dall'utente in modo tale che siano eseguiti nel più breve tempo possibile.

Per applicare la configurazione, il Dispatcher legge dalla stringa dei servizi dell'utente, ne seleziona uno e tramite un registro distribuito va a scegliere tutti i proxy remoti che lo implementano. A tal punto, tramite una funzione di bilanciamento viene schedulato un proxy remoto e viene istanziato uno STUB che si occupa della comunicazione in remoto con il MEG che compone il servizio sulla sessione scelta.
Una volta terminata l'operazione il controllo torna al Dispatcher che a quel punto potrà comportarsi in uno dei seguenti modi:

  • Controlla se il servizio è terminato, cioè se non ci sono altri MEG richiesti per quel servizio. Se il servizio non è terminato allora istanzia lo STUB per il prossimo MEG che compone il servizio secondo il procedimento visto prima.
  • Controlla se nella lista dell'utente ci sono altri servizi da richiedere. In caso positivo si continua con il procedimento di invocazione remota.
  • Ritorna l'output al client.

In tutti i casi comunque, prima di continuare la transazione, va a prelevare dai dati di transizione contenuti nella richiesta i risultati del monitoring delle macchine remote per aggiornare la tavola delle informazioni sul carico.
Per meglio intenderci, su ogni macchina remota è attivo un thread che monitora il carico del computer ed i valori cha computa, vengono aggiunti alle richieste ogni volta che passano per un MEG remoto. Il Dispatcher ha il compito di prelevarli da ogni richiesta che ritorna da una transizione remota in modo da tenere sempre aggiornate le proprie informazioni sulla situazione in remoto (informazioni su cui si applica la funzione di bilanciamento).
Oltre a questi, i compiti del Dispatcher sono anche quelli di generare le pagine per la registrazione e la scelta di configurazione degli utenti. In tali operazioni sono richieste delle operazioni di broadcast delle informazioni che vengono però eseguite da appositi thread. Nel capitolo Esecuzione viene illustrato tutto il processo di utilizzo e scelta dei servizi.

Il sistema si può comporre di più Dispatcher che vengono invocati dai browser secondo una funzione hash contenuta in un ClientAutoconfigurationFile.

Per poter scrivere il Plugin è necessario estendere la classe HttpPlugin di WBI, situata nel package com.ibm.wbi.protocol.http.
Allo startup WBI istanzia un oggetto della classe Plugin che rappresenta il Dispatcher che persisterà  per tutto il ciclo di vita di WBI. Questo è utile in quanto è ossibile usare delle variabili che mantengono traccia di informazioni sulle transazioni (per esempio le informazioni sui remote loads).
La definizione di base della classe è la seguente:

package it.unisa.dia.ProxyServices;

import java.util.Date;
import java.io.*;
import java.util.*;
import java.net.*;
import com.ibm.wbi.*;
import com.ibm.wbi.util.*;
import com.ibm.wbi.markuplanguage.html.*;
import com.ibm.wbi.protocol.http.*;
import com.ibm.wbi.protocol.http.beans.*;
import com.ibm.wbi.persistent.*;

public class DynamicSwitch extends HttpPlugin {

final static int threshold=10;
final static int timeout=150;
public final static int number=0;
public final static N_P=20;

static int[]lastrequest=new int[N_P];
static long[]time=new long[N_P];

public static Hashtable nodes = new Hashtable();
Hashtable servs=new Hashtable();

Hashtable proxies=new Hashtable();
public Balancer balancer=new Balancer();

String login[]=new String[2];

HtmlTemplateGenerator htg;
HttpGenerator sfg, shg, shgw2m, dg, sse, se, fg, pag, g, pg;

Section home, users;

ProxyInfo proxyInfo;
InfoService info=new InfoService();
ThreadWaitProxies waitProxies;
ThreadWaitServices waitServices;
ThreadWaitUsers waitUsers;

public void initialize() {

...

}

}



Le istruzioni import danno accesso al Plugin ad alcune delle classi di WBI e di Java.

L'unico metodo da implementare e' initialize() di HttpPlugin che sarà  chiamato nel momento in cui il Plugin è caricato in WBI.
Con tale metodo vengono instanziati i Meg che si occupano della gestione degli utenti, delle richieste e dei Plugin remoti e vengono effettuate delle operazioni preliminari quali (apertura di Section, inizializzazione di strutture dati) non prendendo comunque nessun parametro in ingresso.

Se si vuole implementare (aggiungere) un nuovo dispatcher all'architettura basta duplicare uno dei Dispatcher cambiando solamente il valore del membro che rappresenta l'identificativo del Dispatcher. E' importante dire però che se più Dispatcher vengono lanciati sulla stessa macchina è necessario cambiare il numero di porte su cui metterli in ascolto. Il file all'interno del quale è possibile specificare le porte è in una sottodirectory di WBI: <wbihome>\etc\sublayers\ibm\HttpSublayer\home.prop.


DisplayRequestTimeStamp = false %B
ListenPorts = 8088 8089 8090

Questi è il contenuto di default del file.

 

  Creazione di un Plugin Remoto

La creazione di un SEcS Plugin consiste di tre passi principali:

Esaminiamo il processo di costruzione di un Plugin Remoto e dei servizi che lo compongono.
Come esempio ci riferiremo ai Plugin forniti nel Kit di SEcS. Il sorgente dei plugin completi è reperibile dalla distribuzione.


  Creazione

Il Plugin remoto è un WBI Plugin che istanzia Meg remoti, thread per il calcolo dei dati di bilanciamento e se necessario anche Meg locali (quelli standard di WBI). Anche in questo si deve estendere la classe di WBI HttpPlugin.
Il metodo initialize() contiene una forma di definizione per i MEG remoti che è diversa da quella di WBI (discussa nel prossimo paragrafo).

Il metodo initialize() appare nel seguente modo:

    void initialize(){
    try{
    mon=new MonitorResult();
    new ThreadMonitor();

    InetAddress Address=InetAddress.getLocalHost();
    host=Address.getHostName();
    FileInputStream file=new FileInputStream("/port.config");
    Properties props=new Properties();
    props.load(file);
    p=props.getProperty("proxy");
    System.out.println("Remote Proxy: " +p+" HOST: "+host);

    RemoteHttpGenerator time=new TimeGenerator();
    try{
    MegRemoteImpl rem1=new MegRemoteImpl(time,"Megtime",p,"Time1");
    }catch(Exception e){
    log("Error in ProxyOne Meg time: rmi\n");
    }
    time.setName("MegRem Time");
    time.setCondition( "(host=%null% | host~_demo2) & path=/info" );
    time.setPriority( 10 );
    addMeg(time );

    RemoteHttpGenerator audio=new TTWGenerator();
    try{
    MegRemoteImpl rem=new MegRemoteImpl(audio, "MegAudio", p, "#Audio0");
    }catch(Exception e){
    log("Error in ProxyZero MegAudio: rmi");
    }
    audio.setName("Audio Generator");
    audio.setCondition( "(host=%null% | host~_remote) & path=/info" );
    audio.setPriority( 11 );
    addMeg( audio);

    RemoteHttpGenerator probe=new ProbeGenerator();
    try{
    MegRemoteImpl rem=new MegRemoteImpl(probe,"MegProbe",p,"Probe0");
    }catch(Exception e){
    log("Error in ProxyZero MegProbe: rmi\n");
    }
    probe.setName("MegRem probe");
    probe.setCondition( "(host=%null% | host~_remote) & path=/info" );
    probe.setPriority( 10 );
    addMeg(probe );

    }catch(Exception ecc){
    log("Error in RemoteProxy: initialize");
    ecc.printStackTrace();
    }
    }

La prima operazione che il Remote Proxy deve svolgere è quella di lanciare il Thread che si occupa del monitoring del carico locale.

    public class ThreadMonitor implements Runnable{
    Thread t=null;

    public ThreadMonitor(){
    try{
    t=new Thread(this);
    log("RemProxy: Thread Monitor started...");
    t.start();
    }catch(Exception e){
    e.printStackTrace();
    }
    }

    public void run(){
    try{
    log("THREAD MONITOR, I'm retrieving...");
    while(true){
    Monitor monitor=new Monitor();
    monitor.PrelevaDati(mon);
    Thread.sleep(10000);
    }
    }catch(Exception e){
    e.printStackTrace();
    }
    }
    }

A tale scopo, la classe Monitor ad intervalli regolari viene istanziata dal Thread e si occupa di prelevare i dati e impacchettarli. Il risultato corrente (MonitorResult) viene inserito all'interno delle richieste transienti per essere letto dal Dispatcher che ha forwardato la richiesta.

La seconda operazione è quella di istanziare i Meg remoti e registrarli all'interno del Distributed Registry. Per quanto riguarda la definizione dei Meg remoti, seguiamo i seguenti due esempi.

 

  Programmazione di Meg Remoti


Gli oggetti remoti (Meg) che il proxy istanzia vengono registrati su un rmiregistry in ascolto su una porta predefinita in un file di configurazione (port.config). Durante il setup il proxy legge da un file di configurazione informazioni quali: l'hostname della macchina su cui è attiva la sessione, la porta su cui mettere in ascolto rmiregistry ed inoltre, al momento della loro istanziazione, tali oggetti devono comunicare al Distributed Registry che sono pronti per poter essere invocati da remoto.

I MEG remoti possono essere dei servizi completi oppure blocchi che compongono i servizi. L'attivazione avviene attraverso la chiamata al metodo remotehandleRequest (STUB ref) sul riferimento dell'oggetto remoto prelevato dal rmiregistry.
L'attivazione in locale, invece, avviene attraverso la chiamata al metodo handleRequest(RequestEvent e) così come specificato da WBI.
Quindi un MEG che esporta un metodo remoto può essere chiamato anche in locale e quindi entra a far parte del pool di MEG di WBI che operano sulle chiamate locali.

Prima di passare alla definizione della classi che implementano i Meg remoti, vediamo cosa succede durante l'initialize. Per esempio vediamo la registrazione del Meg remoto TimeGenerator:

    RemoteHttpGenerator time=new TimeGenerator();
    try{
    MegRemoteImpl rem1=new MegRemoteImpl(time,"Megtime",p,"Time1");
    }catch(Exception e){
    log("Error in ProxyOne Meg time: rmi\n");
    }
    time.setName("MegRem Time");
    time.setCondition( "(host=%null% | host~_demo2) & path=/info" );
    time.setPriority( 10 );
    addMeg(time );


La classe del servizio (in questo caso TimeGenerator) deve estendere un RemoteHttpGenerator che non è altro che una sottoclasse (nelle API di SEcS) di una delle quattro categorie standard di WBI: HttpRequestEditor, HttpEditor, HttpGenerator, HttpMonitor. L'implementazione è la seguente:

package it.unisa.dia.ProxyServices;

import java.io.*;
import java.rmi.*;
import com.ibm.wbi.*;
import com.ibm.wbi.protocol.http.*;
import com.ibm.wbi.protocol.http.beans.*;

public abstract class RemoteHttpGenerator extends HttpGenerator{

public abstract void remotehandleRequest(IConnectionStub stub) throws RequestRejectedException,IOException;
}

In questa classe viene solo definito un metodo astratto che prende come argomento il riferimento allo STUB. Lo STUB mantiene il RequestEvent (non serializzabile) modificandolo e leggendolo a seconda delle richieste di Dispatcher e Proxy Remoto.

Tornando ora all'inizializzazione, con la chiamata al costruttore di MegRemoteImpl si va a registrare il riferimento su rmiregistry e ad esportare il servizio sul Distributed Registry in modo che possa essere visibile ai Dispatcher.

    public MegRemoteImpl (RemoteHttpGenerator RHG,String name,String host,String typeService) throws RemoteException{

    Socket sock=null;
    G=RHG;

    try{
    Naming.rebind("rmi://"+host+"/"+name,this);
    System.out.println("MEG bind to the rmiregistry...");

    FileInputStream file=new FileInputStream("registry.config");
    Properties props=new Properties();
    props.load(file);
    String p=props.getProperty("registry");
    System.out.println("HOST: " + p);
    p=p.trim();
    sock=new Socket(p,port);
    ObjectOutputStream oos=new ObjectOutputStream(sock.getOutputStream());
    InfoService info=new InfoService(typeService,name,host,"isisLab","v1",0.5,1);
    oos.writeObject(info);

    ...

     

  Getting Started

Vediamo ora come si implementa uno di tali Meg. Iniziamo con un esempoi di base che non fa altro che remotizzare il TimeGenerator illustrato nella sezione Programming di WBI . Segue poi un esempio avanzato che implementa il servizio Text_To_Speech.

L'oggetto TimeGenerator viene istanziata durante l'initalize con le seguenti istruzioni:

    class TimeGenerator extends RemoteHttpGenerator{

    public void remotehandleRequest(IConnectionStub ref)throws RequestRejectedException, IOException {
    try{
    DateFormat dateFormat=DateFormat.getDateTimeInstance();
    String time=dateFormat.format(new Date());

    ref.setmess("*****time*****");
    ref.returnInfo(key,mon);

    String html="<html><h2>ORA E DATA CORRENTE</h2>\n"+
    "<h3>orario: "+time+"</h3>"+
    "</html>\n";
    ref.appendString(html);

    }catch(Exception e) {
    log("RemProxy: Errore in handle Time: "+e);
    }
    }

    public void handleRequest(RequestEvent e) throws RequestRejectedException,
    IOException{
    }
    }

Questo Generator non fa altro che aggiungere al flusso HTML la data e l'orario. ref non è altro che il riferimento allo STUB che si occupa di mediare la richiesta tra Dispatcher e Proxy Remoto. Usando lo STUB è possibile scrivere e leggere dati dalla richiesta corrente. Con ref.returnInfo(mr) vengono aggiunti alla richiesta come dati di transizione i dati relativi alla macchina remota chiamata per il servizio "time". Questa istruzione viene aggiunta in ogni Meg remoto. Invece, l'istruzione che incide sul flusso è ref.appendString(html) che aggiunge la stringa "html"al flusso già generato da altri Meg.

 

  Advanced

Vediamo ora un esempio più complesso che prende in considerazione ulteriori aspetti della programmazione: il TTWGenerator.

L'applicazione da noi implementata è contenuta all'interno di un MEG remoto che all'atto della chiamata, contatta l'origin server per ottenere il documento HTML, estrae il testo tramite il comando "lynx" e lo passa al sintetizzatore FreeTTS.

FreeTTS è un sintetizzatore vocale scritto interamente in Java che si basa su un piccolo motore sviluppato alla Carnegie Mellon University.
E' un motore per la sintesi vocale che supporta un certo numero di voci maschili e femminili a differenti frequenze; inoltre è supportatoda JSAPI 1.0 (Java Speech API) le quali forniscono il miglior metodo per controllare ed usare FreeTTS. Vediamo di seguito l'implementazione


    public void handle( IConnectionStub ref ) throws RequestRejectedException, IOException {
    try {
    String cmd[]=new String[3];
    String cmd2[]=new String[3];

    int numberOfInputBytes;
    byte[] inputByteData;

    FileInputStream fis=null;
    Runtime r=Runtime.getRuntime();
    Process p, p1, p2;
    TTW TtoW=null;

    PassingInfo info=ref.remoteDocumentInfo();
    URL url=new URL(info.getUrl());

    synchronized(this){
    i=i+1;
    System.out.println("RemProxy: Indice di richiesta: "+i);
    String output="OUTPUT"+i+".txt";

    cmd[0]="/bin/sh";
    cmd[1]="-c";
    cmd[2]="lynx -dump "+ur+">"+output;
    p=r.exec(cmd);
    p.waitFor();
    System.out.println("RemProxy: in textAnalizer...");
    textAnalyzer(output);
    String speech="speech"+i+".txt";
    TtoW = new TTW(speech,i);
    TtoW.ProduceAudio();


    Ottenuto l'output dal motore (file wave) usando l'applicazione "lame" viene trasformato in un file mp3 (per motivi di dimensioni) e viene incorporato alla pagina HTML tramite il tag embed e viene restituito il documento.

    TTW è la classe che si occupa di invocare il synthesizerper ottenere l'audio relativo al testo estratto dalla pagina HTML.


    cmd2[0]="/bin/sh";
    cmd2[1]="-c";
    cmd2[2]="/usr/local/bin/lame Audio"+i+".wav Audio"+i+".mp3";

    p1=r.exec(cmd2);
    p1.waitFor();
    System.out.println("Waiting mp3...");
    fis=new FileInputStream("/home/rafgri/wbij45/Audio"+i+".mp3");

    if(fis!=null){
    numberOfInputBytes=fis.available();
    inputByteData=new byte[numberOfInputBytes];
    fis.read(inputByteData,0,inputByteData.length);
    ref.setHeader();
    ref.writeAudio(inputByteData,i);

    Con il metodo dello STUB setHeader si inserisce all'interno di HttpResponseHeader il tipo di media che si sta restituendo.

    Con writeAudio, invece, è possibile scrivere all'interno della risposta i dati del servizio per fare in modo che una volta arrivati al Dispatcher possano essere restituiti. In particolare, il Dispatcher quando riceve i dati audio all'interno del TransactionData della richiesta, li preleva ed effettua l'embed del file mp3 alla risposta.


    Integer number=new Integer(i);
    ref.setTransactionDat("index",number);

    L'"index" è importante perchè lega richiesta e file audio.


    fis.close();
    }
    }
    }catch(Throwable t){
    System.out.println("I'm in outOfMemoryError........");
    removeAudioService();
    }
    }catch(Exception ex){
    ex.printStackTrace();
    }
    }
    }

P.S. Nella Piattaforma di SEcS va sempre istanziato un Meg remoto particolare: il Meg Probe.

Probe è una sorta di servizio interno che non ha nessuna attinenza con le scelte dell'utente. Il suo compito è quello di rispondere ad una richiesta dummy inserendo all'interno della risposta HTTP i dati di carico della macchina locale calcolati dal Monitor.
Tale applicazione è stata inserita in merito al fatto che le informazioni sullo stato dei computer non arrivano ad intervalli regolari, bensì in risposta alle richieste schedulate ai proxy attivi sul sistema.
Per tale motivo, se l'ultima richiesta porta informazioni sulla macchina relative ad un possibile picco di carico, allora i MEG remoti attivi su quel sistema non verranno schedulati finché qualche altro computer non supera quella soglia (cosa che può non accadere per lunghi periodi). Fatto sta, che la macchina dopo il picco di carico può rimanere inattiva a lungo in quanto le informazioni che si hanno sul suo stato sono negative.

Per evitare l'inopportuna esclusione della macchina dal sistema, quello che il Dispatcher fa, alla fine di ogni richiesta, è controllare che nessuna macchina abbia superato un certo delay di tempo per cui non ha partecipato alle richieste, oppure se la macchina non sia stata presa in considerazione per un certo numero di richieste superiore ad una certa soglia. Se accade uno dei due casi: soglia di richieste scaduta oppure timeout scaduto, il Dispatcher invia una richiesta dummy al proxy remoto per farsi inviare le informazioni di carico recenti.

A tal punto, se il carico è ancora elevato, allora il problema persiste ed in tal caso il proxy non verrà invocato, se invece il carico è diminuito allora i MEG su quella macchina ricominceranno a far parte della schedulazione. Riportiamo di seguito le semplici istruzioni del MEG Probe:

    class ProbeGenerator extends RemoteHttpGenerator{

    public void handle (IConnectionStub ref)throws RequestRejectedException, IOException {

    try{
    ref.setmess("*****probe*****");
    ref.returnInfo(key,mon);

    }catch(Exception e) {
    System.out.println("RemProxy 0: Errore in handle di probe: "+e);
    }
    }



  Registrazione dei Plugin

Una volta che il Plugin è stato compilato ed è senza errori, è pronto per essere registratoin WBI. La registrazione avviene tramite un file .reg che contiene i dettagli del plugin. Dalla WBI GUI è possibile registrare ed esporare i Plugin registrati.

Ogni Plugin (Dispatcher e Remote Proxy) necessita del proprio file .reg. L'ideale nella architettura di SEcS è registrare un Dispatcher ed un Proxy Remoto per ogni sessione WBI che si vuole instaurare in modo tale che l'architettura risultante sia la seguente:

Per meglio comprendere le specifiche del file .reg basta consultare la documentazione di WBI. Un esempio è il seguente:

 

<descriptiveName>Dispatcher</descriptiveName>
<description>Dispatcher</description>
<pluginClass>it.unisa.dia.SEcSSock.Dispatcher</pluginClass>
<pluginName>SEcSSock/Dispatcher</pluginName>
<majorVersion>1</majorVersion>
<minorVersion>2</minorVersion>

La fase di setup dei componenti è comunque molto delicata e richiede attenzione da parte dell’amministratore del sistema. Per poter svolgere tale fase senza incorrere in errori è necessario assicurarsi che il registro distribuito sia stato lanciato e bisogna fare attenzione alle porte su cui i Dispatcher vengono registrati.

Il modo in cui è stata implementata la registrazione fa si che proxy remoti e proxy locali possano registrarsi in qualunque ordine senza perdita di informazioni sui componenti.

L’unica particolarità a cui deve prestare attenzione l’amministratore durante la registrazione dei Dispatcher è che le porte di WBI siano concordi a quelle stabilite nel Client Autoconfiguration File (SuperProxy.pac). Per esempio:

function FindProxyForURL(url, host){
                 res=URLhash(url);
                 if ((res% 3)=2){  
                    return "PROXY agarthi.dia.unisa.it:8103; PROXY wonderland.dia.unisa.it:8203; PROXY avalon.dia.unisa.it:8303; DIRECT”
                 }

if ((res% 3)=1){   
                    return "PROXY wonderland.dia.unisa.it:8203; PROXY avalon.dia.unisa.it:8303; PROXY agarthi.dia.unisa.it:8103; DIRECT”
                 }
                 else{
                    return "PROXY avalon.dia.unisa.it:8303; PROXY agarthi.dia.unisa.it:8103; PROXY wonderland.dia.unisa.it:8203; DIRECT”
                 }
}

Per come è fatto lo script, se non viene trovata nessuna applicazione in ascolto sulle porte segnalate, la connessione non passa attraverso nessun proxy ma va direttamente all’origin server.

 

Copyright © 2003 IsisLab
All rights reserved.

Last updated: Thu Jun 12 05:13:20 2003 CEST