un approccio per la software fault injection in sistemi ...e' necessario comprendere che...
TRANSCRIPT
Facoltà di IngegneriaCorso di Studi in Ingegneria Informatica
tesi di laurea specialistica
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Anno Accademico 2008/2009
relatoreCh.mo prof. Domenico Cotroneo
correlatoreIng. Roberto Natella
candidatoStefano Ragomatr. 885/215
1
Alla mia grande famiglia
2
Indice Introduzione................................................................................................................................... 1
Organizzazione della trattazione............................................................................................... 3Capitolo 1: Introduzione alla Software Fault Injection................................................................... 4
1.1 Concetti sulla dependability dei sistemi informatici............................................................. 41.1.1 Attributi della dependability......................................................................................... 61.1.2 Minacce alla dependability.......................................................................................... 71.1.3 Mezzi per ottenere e stimare la dependability............................................................. 8
1.2 Software Faults............................................................................................................. 101.3 Stato dell'arte nell'iniezione di Software Faults............................................................. 18
Capitolo 2: Coflight - un caso di studio di sistema Fault Tolerant ............................................... 252.1 Introduzione a CARDAMOM ........................................................................................ 262.2 Fault Tolerance Service................................................................................................ 322.3 Load Balancing Service ............................................................................................... 342.4 COFLIGHT ................................................................................................................... 362.5 Modellazione a stati del sistema................................................................................... 40
Capitolo 3: Progettazione di campagne di testing basate su Software Fault Injection ...............513.1 Obiettivi, definizioni e metriche di valutazione.............................................................. 51
3.1.1 Il caso di studio COFLIGHT...................................................................................... 543.2 Misure e raccolta dati ................................................................................................... 563.3 Analisi dei dati............................................................................................................... 61
3.3.1 Il caso di studio COFLIGHT...................................................................................... 63Capitolo 4: Valutazione sperimentale basata sullo stato dell'arte................................................ 69
4.1 Entrambi i facade faulty.................................................................................................... 694.1.1 Copertura degli stati.............................................................................................. 69
4.2 Facade primario faulty (esplorazione del modello in senso orizzontale e verticale).........744.2.1 Copertura degli stati (esplorazione in senso orizzontale)...................................... 744.2.2 Copertura degli stati (esplorazione in senso verticale).......................................... 77
4.3 Facade primario faulty (workload con diversificazione delle richieste).............................804.3.1 Copertura degli stati ............................................................................................. 81
4.4 Processing server faulty.................................................................................................... 834.5 Vulnerabilità scoperte....................................................................................................... 85
Capitolo 5: Iniezione di guasti di concorrenza ............................................................................ 875.1 Definizione del modello dei guasti ............................................................................... 915.2 Iniezione dei guasti .................................................................................................... 1135.3 Attivazione dei guasti ................................................................................................. 1155.4 Implementazione......................................................................................................... 118
5.4.1 Algoritmo per la determinazione dei percorsi.......................................................... 1195.4.2 Tecnica di controllo dello scheduling....................................................................... 121
Capitolo 6: Valutazione sperimentale basata sull'iniezione di guasti di concorrenza ..............1246.1 Copertura degli stati ................................................................................................... 1246.2 Vulnerabilità scoperte................................................................................................. 138
Conclusioni e sviluppi futuri....................................................................................................... 139Appendice A: Modellazioni alternative del sistema................................................................... 142Bibliografia................................................................................................................................. 146
III
Indice delle figureIllustrazione 1: Tecniche per la Fault Tolerance................................................................................. 9Illustrazione 2: Una classificazione dei Software Faults.................................................................. 11Illustrazione 3: Trend dei Bohrbugs e degli Heisenbugs.................................................................. 12Illustrazione 4: I bugs e le relative contromisure.............................................................................. 14Illustrazione 5: Spettro delle metodologie di analisi dei difetti software...........................................16Illustrazione 6: La tecnica G-SWFIT................................................................................................. 23Illustrazione 7: I Pluggable Services di Cardamom.......................................................................... 28Illustrazione 8: Struttura di Cardamom............................................................................................. 31Illustrazione 9: Fault Tolerance in Cardamom.................................................................................. 33Illustrazione 10: Load Balancing in modalità round robin................................................................. 35Illustrazione 11: I tipi di dato di un FDP definiti dal progetto Avenue............................................... 37Illustrazione 12: Architettura di Coflight............................................................................................ 40Illustrazione 13: Modello a stati finiti del sistema............................................................................. 47Illustrazione 14: Un esempio di sequenza di messaggi applicata al modello..................................49Illustrazione 15: Sequence Diagram di un caso d'uso..................................................................... 50Illustrazione 16: Predicati e modello di comportamento................................................................... 56Illustrazione 17: Tool utilizzato per analizzare i log del sistema....................................................... 67Illustrazione 18: Workload ad esplorazione orizzontale................................................................... 70Illustrazione 19: Classificazione dei test........................................................................................... 71Illustrazione 20: Distribuzione dei fallimenti nello scenario con entrambi i Facade faulty................72Illustrazione 21: Classificazione dei test per lo scenario con entrambi i Facade faulty....................73Illustrazione 22: Distribuzione dei fallimenti nello scenario con singolo Facade faulty ed esplorazione orizzontale................................................................................................................... 75Illustrazione 23: Classificazione dei test per lo scenario con singolo Facade faulty ed esplorazione orizzontale........................................................................................................................................ 76Illustrazione 24: Workload ad esplorazione verticale....................................................................... 77Illustrazione 25: Distribuzione dei fallimenti nello scenario con singolo Facade faulty ed esplorazione verticale....................................................................................................................... 78Illustrazione 26: Classificazione dei test per lo scenario con singolo Facade faulty ed esplorazione verticale............................................................................................................................................ 79Illustrazione 27: Distribuzione dei fallimenti nello scenario con singolo Facade faulty e diversificazione delle richieste.......................................................................................................... 81Illustrazione 28: Classificazione dei test per lo scenario con singolo Facade faulty e diversificazione delle richieste.......................................................................................................... 82Illustrazione 29: Le semplificazioni rimosse dal modello.................................................................. 87Illustrazione 30: Nuova versione del modello a stati finiti................................................................. 88Illustrazione 31: Processo di definizione del fault model................................................................ 113Illustrazione 32: Esempio di percorso per l'attivazione di un conflitto............................................ 120Illustrazione 33: Esmpio di scheduling per l'attivazione di un conflitto........................................... 121Illustrazione 34: Risultati della campanga di concurrency fault injection.......................................135
IV
Introduzione
Questo lavoro di tesi si colloca nel contesto dei sistemi software critici e della software
fault injection come strumento per studiare e validare tali sistemi. I sistemi critici sono
presenti in numerosi ambiti, fra cui quello militare, dell'aeronautica, dei sistemi per cure
mediche, dei sistemi per la gestione economica. Questi sistemi, notevolmente differenti
dal punto di vista delle funzionalità offerte, condividono un insieme di caratteristiche che
da un lato li rendono particolarmente sofisticati e complessi e dall'altro comportano la
necessità di massima affidabilità.
Con il termine dependability, più avanti esemplificato in questa trattazione, si intende un
insieme di attributi di qualità di un sistema ed è il punto di partenza di numerosi studi e
ricerche effettuati negli ultimi anni. La dependability è il requisito principale dei sistemi
critici, che, malgrado gli sforzi più attenti, sono soggetti alla presenza di difetti, o fault
nella terminologia di questo contesto.
Uno dei mezzi per studiare e migliorare la dependability di un sistema è la fault tolerance,
che, contemplando l'occorrenza di determinati tipi di fault, si occupa di contrastarne le
conseguenze, facendo in modo che il sistema, seppur affetto da tali difetti, risponda
comunque ai requisiti previsti.
Anche la fault tolerance, tuttavia, è soggetta a difetti per cui diventa cruciale verificare che
i meccanismi di fault tolerance di un sistema funzionino nella maniera prevista.
Un modo per validare la fault tolerance di un sistema è la fault injection che emulando, a
tempo di esecuzione, la presenza di opportuni fault, scelti sulla base di statistiche e studi
approfonditi, si occupa di favorirne l'attivazione, al fine di studiare la reazione del sistema
in tali circostanze.
In questo lavoro sono state studiate e valutate due tecniche di fault injection: una tecnica
allo stato dell'arte ed una tecnica innovativa. Tali tecniche sono state applicate ad un caso
di studio particolarmente incline a questo tipo di lavoro: un sistema per la gestione dei
piani di volo, parte di un sistema ATC (Air Traffic Control).
1
Un contributo di questo lavoro è stato quello di fornire una modellazione del sistema
basata sugli stati al fine di supportare lo studio e la valutazione di entrambe le tecniche.
Fra le diverse alternative analizzate, è stato adottato un modello a stati finiti che tenesse in
conto le variabili di maggior interesse. Modellare il sistema in questo modo permette di
comprendere le condizioni di funzionamento maggiormente sensibili all'attivazione di
fault, poiché generalmente le conseguenze dei fault non hanno gli stessi effetti in
qualunque condizione. Questo tipo di discernimento è utile per concentrare gli sforzi e
approfondire meglio i casi che più lo richiedono, poiché studi di questo tipo comportano
tempi e costi notevoli.
La tecnica allo stato dell'arte, nota come G-SWFIT (Generic Software Fault Injection
Technique) è stata approfonditamente valutata, applicandola alle entità del sistema
maggiormente critiche. Sono stati proposti differenti scenari d'uso del sistema (workload)
attraverso in quali è stato possibile evidenziare i diversi meriti e difetti di una tale tecnica.
Il risultato fondamentale è che questa tecnica non permette di testare efficacemente tutti
gli stati previsti dal modello. Inoltre, tale tecnica sembra maggiormente idonea
all'emulazione di una tipologia di guasti che sono già sufficientemente affrontati e rimossi
durante la fase di sviluppo e testing del sistema, diversi dal tipo di guasti che la fault
tolerance contempla e che, quindi, si vuole emulare con la fault injection. L'obiettivo,
infatti, è quello di riprodurre quei difetti che sfuggono alla fase di testing e che si attivano,
con conseguenze più o meno gravi, nella fase operativa di un sistema.
Da queste premesse è nata la tecnica proposta, nello sforzo di emulare una categoria di
guasti maggiormente rappresentativi e utili allo scopo. Dapprima è stata individuata, sulla
base di lavori precedenti, la tipologia di guasti più frequente all'interno di questa categoria
– quella degli errori di programmazione concorrente – dopodiché è stata individuata una
metodologia in grado non solo di emularli, ma di fare in modo che potessero essere attivati
in maniera deterministica e controllata.
Questo tipo di guasti, infatti, hanno una modalità di attivazione che li rende
particolarmente difficili da riprodurre, poiché spesso, proprio nel tentativo di
comprenderne le cause, “scompaiono”, ossia, vengono alterate le condizioni che ne
2
favoriscono la manifestazione. Lo sviluppo della tecnica proposta è stato motivato anche
da queste considerazioni: se la tecnica precedente ha lasciato degli stati scoperti,
presumibilmente il sistema ha attraversato quegli stati affetto da tali difetti, ma questi non
si sono manifestati.
La tecnica proposta ha permesso di completare la copertura di tutti gli stati del modello,
dimostrandosi complementare all'altra tecnica. Inoltre, collateralmente alla valutazione
delle tecniche, sono state scoperte delle vulnerabilità nei meccanismi di fault tolerance del
sistema testato, illustrate nel corso della trattazione.
Organizzazione della trattazione
Il resto di questo testo è organizzato come segue. Il Capitolo 1 introduce la Software Fault
Injection, descrivendone gli aspetti principali e le tecniche presenti in letteratura, mentre il
Capitolo 2 illustra il caso di studio attraverso il quale sono state valutate le tecniche di
fault injection. Le due tecniche di fault injection sono illustrate seguendo una struttura
simile: prima da un punto progettuale e implementativo (rispettivamente nei Capitolo 3 e
5) e poi sono presentati i risultati (rispettivamente nei Capitoli 4 e 6).
3
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Capitolo 1Introduzione alla Software Fault Injection
1.1 Concetti sulla dependability dei sistemi informatici
Negli ultimi anni, la necessità e la richiesta di sistemi informatici complessi, mission e
safety critical, sono cresciute molto rapidamente. Contestualmente sono aumentati gli
ambiti in cui tali sistemi sono richiesti e la criticità degli stessi. Di conseguenza il requisito
fondamentale per questo tipo di sistemi è l'affidabilità.
Come constatato da Gray in [4] e da altri autori in diversi lavori ([16], [20], [21]), la
maggior parte dei problemi riscontrati nei moderni sistemi viene attribuita al software. Ciò
è dovuto, in parte, alla maggiore consolidazione che ha raggiunto la tecnologia hardware,
in parte alla natura stessa del software e al maggiore spettro di possibilità che offre. Sono
numerosi gli esempi in cui problemi di tipo software hanno dato luogo a fallimenti di
sistema, in ambiti che vanno dai sistemi per cure mediche a sistemi per la gestione
economica, a sistemi militari, per menzionarne solo alcuni. Tali fallimenti comportano
conseguenze di tipo diverso, che possono essere più o meno gravi a seconda dei criteri
adottati per classificarle, ma che tuttavia indicano, nella loro stessa definizione,
l'occorrenza di un evento inammissibile. Il duplice intento dello studio della dependability
è appunto quello di capire, da un lato, quanto si può essere confidenti che, dato un certo
sistema, determinati eventi non accadano e, dall'altro, come conferire al sistema
caratteristiche che consentano una certa confidenza.
Avendo menzionato una classe di eventi “inammissibili” è il caso di specificare ed
esemplificare tale classe. E' necessario comprendere che produrre software privo di errori
è molto difficile. L'attività di sviluppo del software, infatti, è spesso condizionata, oltre
che dalle difficoltà tecniche, da vincoli di mercato, che costringono a contenere, o a
ridurre, i tempi di sviluppo e da vincoli economici. Tutto ciò comporta dunque notevoli
difficoltà nel garantire l'assenza di difetti.
4
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Ciò che si vuole ottenere, con lo studio della dependability, è la possibilità di ammettere la
presenza di difetti, eventualmente quantificandoli e prevedendoli, ma fare in modo che
questi non impediscano al sistema di rispondere a determinati requisiti.
In quest'ottica, il ciclo di sviluppo dei sistemi software si modifica, includendo delle fasi
incentrate allo studio della dependability del sistema, sia in stadio progettuale, sia in stadio
implementativo.
Formalmente, il concetto di dependability ha visto attribuirsi, in letteratura, diverse
definizioni. Una delle prime, fra le più accreditate, è dovuta a Laprie che, nel 2001, definì
la dependability come “la capacità di fornire un servizio che possa essere considerato, a
buona ragione, affidabile”. Lo stesso Laprie, tre anni dopo, diede una nuova definizione
del termine, introducendo un concetto di misurabilità della dependability: “la capacità di
evitare fallimenti che siano più frequenti e più gravi di quanto si possa accettare”. Queste
definizioni scaturiscono da approfondite discussioni su termini e definizioni, che sono
documentate in un libro del 1992 edito dallo stesso Laprie [6]. Tale libro contiene, tra
l'altro un glossario dei termini con le rispettive traduzioni in lingua Italiana, Francese,
Tedesca e Giapponese.
Un altro lavoro che ha contribuito alla definizione di dependability e all'organizzazione e
chiarificazione dei concetti ad essa collegati è l'articolo di Avizienis et al. [7]. In questo
lavoro, il concetto di security viene integrato maggiormente tra gli aspetti della
dependability, assumendo maggiore importanza rispetto al lavoro di Laprie [6].
Evidentemente, il differente contesto storico-tecnologico in cui è stato concepito il lavoro
di Avizienis ha indotto il concetto di security ad assumere un'importanza ed una maturità
tale da essere inclusa all'interno dello studio della dependability.
Di seguito si riportano le definizioni di alcuni concetti basilari per poter, in seguito,
definire i diversi aspetti della dependability, le minacce alla stessa e i mezzi per ottenerla:
• sistema: è un'entità che interagisce con altre entità
• servizio: è ciò che fa il sistema, così come percepito dall'utilizzatore. L'utilizzatore può
5
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
essere a sua volta un sistema.
Un servizio è corretto se conforme alle specifiche del sistema
• interfaccia: è il confine tra il sistema e i suoi utilizzatori
1.1.1 Attributi della dependability
La dependability è un attributo di qualità di un sistema composto dai seguenti sotto-
attributi:
• availability: disponibilità a fornire un servizio corretto. In altre parole è la probabilità
che, in un certo istante, il sistema fornirà un servizio corretto
• reliability: continutità nel fornire un servizio corretto. In altre parole è la probabilità
che, in un certo intervallo di tempo, il sistema fornirà un servizio corretto
• safety: assenza di conseguenze gravi per gli utilizzatori e per l'ambiente circostante il
sistema. Il concetto di gravità si basa su attività di risk analysis
• integrity: assenza di alterazioni improprie del sistema
• maintainability: capacità di essere sottoposto a modifiche e correzioni
• performability: metrica per valutare le prestazioni di un sistema in presenza di
fallimenti
• security: è, a sua volta un attributo composto, che comprende:
◦ availability: in questo contesto è la disponibilità di servizio per i soli utilizzatori
autorizzati a fruirne
◦ confidentiality: assenza di divulgazione non autorizzata di informazioni
◦ integrity: in questo contesto è l'assenza di alterazioni non autorizzate del sistema
In letteratura sono stati formulati una serie di parametri sintetici che caratterizzano
determinate misure della dependability, fra cui il Mean Time To Failure (MTTF), che è il
tempo medio che trascorre prima che il sistema sia soggetto ad un failure, il Mean Time
Between Failure (MTBF), ossia il tempo medio che trascorre tra due failure, il Mean Time
To Repair (MTTR), tempo impiegato mediamente a ripristinare il sistema in uno stato di
corretto funzionamento, il Mean Down Time (MDT), ossia il tempo medio in cui un sistema
6
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
non è funzionante, in un determinato intervallo di riferimento.
1.1.2 Minacce alla dependability
Un servizio corretto si ha quando il servizio implementa la funzione del sistema in
conformità alle specifiche. In corrispondenza di un evento in cui un servizio cessa di
essere corretto, si ha un system failure. L'intervallo di tempo in cui il servizio fornito non è
corretto viene detto service outage, mentre il ritorno alla correttezza del servizio è detta
service restoration. Un servizio può divenire non corretto in diversi modi, chiamati failure
modes. Si definisce errore la parte dello stato del sistema che non risulta corretta, mentre
la causa di un errore, addotta o ipotizzata, viene detta fault.
Esiste una catena di propagazione delle minacce che mette in correlazione fault, errori e
failure. Secondo tale catena, quando il sistema fornisce un servizio corretto, può contenere
un fault (o più di uno) che, finchè non viene attivato, viene detto dormiente. Il fault viene
attivato nel momento in cui produce un errore, che può essere, a sua volta, in uno stato di
non attivazione e viene detto latente, oppure rilevato. Il tipo di fault di cui si è parlato
finora è di tipo interno, in contrapposizione al caso in cui il fault venga “iniettato” nel
componente dall'esterno (ad es. come input da un altro componente). L'attivazione di un
fault consiste in una combinazione di input al sistema (o ad un suo componente), non
necessariamente univoca, che va a sollecitare il fault. Quando un errore si propaga
all'interfaccia del sistema (o di un componente di esso) si ha un failure. La propagazione di
un errore può, peraltro, ingenerare ulteriori errori.
Il tempo che intercorre prima che il fault si attivi viene detto fault dormancy, mentre
l'intervallo di tempo tra l'attivazione e il rilevamento dell'errore è chiamato latency
dell'error detection. Si noti che, poiché un fault può essere introdotto in una qualunque
delle fasi di sviluppo, possono esistere dei fault anche nelle specifiche del sistema, che
consistono nell'omissione o l'alterazione di determinati requisiti, effettivamente voluti da
chi commissiona lo sviluppo del sistema. Questo è un punto ancor più delicato se si
considera che un fallimento viene considerato tale sulla base delle specifiche di sistema.
7
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
1.1.3 Mezzi per ottenere e stimare la dependability
Nell'intento di far fronte alla necessità di sviluppo di sistemi critici affidabili, sono state
realizzate numerose tecniche e metodologie, che possono essere distinte in quattro
categorie:
• fault prevention: finalizzate alla prevenzione dei fault
• fault tolerance: contemplano la possibilità della presenza di fault, nell'intento di evitare
che degenerino in failure
• fault removal: hanno lo scopo di eliminare i fault o ridurne la gravità delle
conseguenze
• fault forecasting: si propongono di stimare l'incidenza dei fault, quantificandoli e
prevedendone le conseguenze
Le prime tre categorie sono accomunate dall'intento di conferire al sistema un certo grado
di confidenza, mentre la restante è finalizzata a quantificare tale confidenza, motivandola.
Alternativamente si può effettuare una classificazione di questi approcci a seconda che
questi tollerino o meno la presenza di fault:
• fault intolerance: hanno l'obbiettivo di ridurre l'eventualità di occorrenza di fault.
Come evidenziato in precedenza, seppur con sforzi notevoli, è molto difficile garantire
la totale assenza di fault, che possono, prima o poi, causare dei failure
• fault tolerance: reagiscono all'occorrenza di fault mascherandoli, completamente o
parzialmente, e comunque facendo in modo che questi non possano, entro certi limiti,
provocare dei failure.
La fault tolerance e i meccanismi di supporto ad essa sono particolarmente rilevanti in
questo lavoro poiché lo studio effettuato si è concentrato sulla valutazione di diverse
tecniche per la fault injection, che è finalizzata, a sua volta, proprio allo studio e alla
valutazione dei meccanismi di fault tolerance. Come accennato, un sistema fault tolerant è
progettato per tollerare l'occorrenza di guasti, purchè questi rientrino nell'insieme di guasti
contemplati in fase di progetto. Le tecniche più diffuse si basano sul concetto di
ridondanza che può essere intesa sia in senso spaziale che temporale. Un esempio di
8
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
ridondanza temporale consiste, a fronte di un'operazione che ha prodotto risultati non
previsti, nel ripetere l'operazione. La ridondanza spaziale, invece, consiste nel replicare
entità o moduli del sistema per far fronte al fallimento di una copia principale, oppure per
comparare i risultati di copie differenti.
Come evidenzia la Figura 1, preliminari alle azioni per tollerare un guasto, vi sono le
attività di detection degli errori (conseguenti ai guasti) e di recovery. La detection si
occupa di verificare la presenza di uno o più errori mentre la recovery è finalizzata a
ripristinare lo stato corretto del sistema e prevenire la successiva riattivazione di un fault
[7].
Fra i mezzi per migliorare la dependability che sono stati citati, la fault removal merita un
maggiore approfondimento poiché è, insieme alla fault tolerance, il punto di partenza per
la fault injection.
Infatti quest'ultima, se da un lato mira a validare i meccanismi di fault tolerance, dall'altro
9
Illustrazione 1: Tecniche per la Fault Tolerance
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
può essere utilizzata per fornire un feedback per la fase di sviluppo evidenziando le
vulnerabilità del sistema al fine di correggerle opportunamente. Ad ogni modo la fault
removal comprende anche altre attività, non ultima quella di testing che tradizionalmente
ha da sempre costituito la tecnica basilare per la produzione di software di qualità.
Un importante concetto, legato a quelli appena esposti, è quello della fault coverage, che
consiste nella probabilità che il sistema gestisca correttamente il guasto dato un fault.
Esiste un'interessante relazione che connette i diversi approcci per la dependability e che
evidenzia la necessità di un approccio combinato. Poiché, infatti, non è possibile credere
di poter produrre sistemi assolutamente privi di difetti anche a mezzo di notevoli sforzi
(fault prevention) si rende necessaria l'eliminazione di tali difetti (fault removal). Ma tale
attività è essa stessa soggetta ad imperfezioni, per cui bisogna essere in grado di stimare
quantitativamente la probabilità di occorrenza di eventuali fault “superstiti” e, altrettanto
importante, di capire quali conseguenze tali fault possano avere (fault forecasting). A
questo punto, si vuole avere, comunque la possibilità che tali fault non diano luogo a
failure, per cui si ricorre alla fault tolerance. Tuttavia le tecniche di fault tolerance sono, a
loro volta, suscettibili agli errori, e anche per queste sono quindi necessarie tutte le
considerazioni fatte per il sistema di partenza.
1.2 Software Faults
Secondo la classificazione di Avizienis et al. in [7], esistono 13 classi di software faults, di
cui solo 5 riguardano fault introdotti in fase di sviluppo del sistema. Lo studio della
software dependability è concentrato proprio su queste classi di fault, che vengono
comunemente chiamati difetti o bug. Il motivo di ciò è, come accennato in precedenza, la
preponderanza di questi fault fra le cause di failure dei sistemi. I moderni sistemi, inoltre,
sono spesso basati sull'utilizzo di COTS (Components Off-The-Shelf) che sono sviluppati
da terze parti e di cui spesso non si ha a disposizione né il codice sorgente, né alcun tipo di
parametro in merito alla dependability del componente stesso. Inoltre, spesso i sistemi
10
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
integrano, fra l'altro, componenti o sottosistemi legacy, che eventualmente, così come i
COTS, sono stati sviluppati senza tenere in conto la possibilità di integrazione con altri
sistemi. Tutto ciò conferisce al sistema complessivo un grado di impredicibilità, in termini
di dependability, che rende necessario uno studio particolarmente attento.
E' opportuno chiarire che, sebbene le cause di software failure sono sempre permanenti,
per la natura stessa dei difetti software, il processo che conduce al failure dipende spesso
da una quantità di fattori tale che può essere considerato non deterministico. Le condizioni
dell'ambiente in cui si trova ad operare il sistema possono essere, in molti casi,
difficilmente riproducibili a causa, ad esempio, della programmazione concorrente, race
conditions e altri problemi simili.
Tuttavia i difetti software possono essere permanenti anche nella modalità con cui si
manifestano: la maggior parte di questi difetti vengono scoperti in fase di testing del
sistema ed eliminati. I difetti di questo tipo quindi, detti solid o hard faults, difficilmente
raggiungono la fase operazionale del sistema, in quanto, essendo facilmente riproducibili,
è altrettanto semplice studiare il contesto in cui si manifestano e risalire alla soluzione del
problema. Quando invece i fault hanno una modalità di manifestazione non deterministica,
11
Illustrazione 2: Una classificazione dei Software Faults
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
raramente vengono scoperti e, quand'anche ciò accade, poiché non si riescono a riprodurre
le condizioni per la loro manifestazione, il lavoro di eliminazione del difetto si complica
notevolmente. Per tale ragione i fault di questo tipo vengono chiamati elusive fault. Come
si riscontra dalla figura 3, il numero di elusive fault scoperti, in percentuale, cresce
gradualmente, dalla fase di sviluppo, in cui sono praticamente assenti, fino alla fase
operazionale, in cui, poiché il sistema è in esecuzione, eventualmente in un un gran
numero di istanze e nelle più disparate circostanze di utilizzo, aumenta la probabilità di
manifestazione del difetto. Inversamente, i solid bug che, all'inizio della fase di sviluppo
sono, percentualmente, molto maggiori degli elusive bug, sottoposti ad attività di testing,
revisione ed eventualmente di patching, decrescono asintoticamente. In realtà, nel caso di
patching, si ha un andamento a dente di sega per la curva relativa agli hard bug:
solitamente una patch introdotta per eliminare un fault, ne introduce altri. Si noti che le
curve descritte sono unicamente di tipo qualitativo, in quanto non è possibile disporre di
dati concreti a riguardo.
In letteratura, gli hard fault sono anche detti bohrbug, con riferimento al modello atomico
12
Illustrazione 3: Trend dei Bohrbugs e degli Heisenbugs
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
di Bohr, che è chiaro e ben conosciuto. Analogamente, gli elusive bugs sono anche
chiamati mandelbug con riferimento a Benoît Mandelbrot, noto ricercatore nel campo
della geometria frattale. Gli elusive bug sono stati definiti anche heisenbug, in allusione al
Principio di Indeterminazione di Heisenberg, che afferma che non si può calcolare allo
stesso tempo e con certezza la quantità di moto e la posizione di una particella. Questa
definizione richiama il fatto che le condizioni di manifestazione degli heisenbug sono
talmente complesse che anche il solo fatto di cercare di riprodurle e registrarne le
conseguenze ne preclude l'attivazione.
Con riguardo ai modelli a stati finiti, un sistema affetto da mandelbug può essere
modellato attraverso una macchina a stati deterministica, mentre se è affetto da heisenbug
si può modellare come una macchina a stati non deterministica.
Vi è un'altra categoria di bug software che è il caso di menzionare ed è quella degli aging
related bugs. Sebbene questi fault siano di tipo permanente, vengono modellati come un
sottoinsieme dei guasti transienti e la loro modalità di manifestazione è correlata
all'uptime del sistema. E' noto che spesso i sistemi software in esecuzione in maniera
continuata, all'aumentare dell'uptime (tempo di attività continuata) tendono ad esibire un
peggioramento delle prestazioni ed un aumento del tasso di fallimento. Questo è un
fenomeno noto col nome di software aging ed è recentemente diventato un importante
campo di studio e ricerca. I sintomi che questo tipo di fault produce sono particolarmente
difficili da studiare, in quanto i failure a cui danno luogo sono frutto di un'accumulazione
di condizioni d'errore. Normalmente, infatti, secondo la catena di propagazione dei fault,
una volta che questi sono attivati dando luogo ad uno o più errori, possono, prima o poi,
degenerare in failure. Per gli aging related bug l'intervallo di tempo che intercorre tra
l'attivazione del fault e il failure, è di solito talmente lungo che diventa molto complicato
risalire alle condizioni di attivazione del fault stesso perchè, in quell'intervallo di tempo,
gli eventi relativi al sistema, inclusi gli input e le transizioni tra i vari stati possono
facilmente deviare la diagnosi. Per tali motivazioni, gli aging related bug rientrano nella
13
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
categoria degli heisenbug. Le cause più frequenti di software aging sono il mancato
rilascio di porzioni di memoria precedentemente allocata, il mancato rilascio di risorse di
sincronizzazione precedentemente acquisite, errori di round off, etc. Per combattere i
software fault appartenenti alle categorie citate si hanno differenti metodologie, sia in fase
di sviluppo che in fase operativa, e vengono sintetizzate nella figura 4.
Come accennato in precedenza, la fase di testing, che appartiene allo stadio di sviluppo del
sistema, tende a rimuovere, prevalentemente, i bohrbug, poiché raramente si riescono ad
attivare, in questa fase, le condizioni di attivazione di elusive fault.
Per quanto riguarda, invece, gli heisenbug, la difficile riproducibilità di questi, se da un
lato rappresenta un elemento di difficoltà per la rimozione del bug, al contempo
rappresenta una caratteristica di cui è possibile servirsi per tollerare l'occorrenza del bug:
se si attiva il fault in un dato momento, difficilmente, eseguendo nuovamente il flusso di
esecuzione che ha attivato il fault, ciò accadrà di nuovo. A maggior ragione, riavviando
l'applicazione faulty oppure l'intero sistema, sarà ancora meno probabile l'occorrenza di
una nuova attivazione. Queste ultime due tecniche sono valide anche per scongiurare
14
Illustrazione 4: I bugs e le relative contromisure
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
l'occorrenza di failure dovuti ad aging related bug: in questo caso si parla di software
rejuvenation.
Uno tra i maggiori contributi al tentativo di conferire un approccio scientifico alla
classificazione dei software fault è la metodologia che va sotto il nome di ODC
(Orthogonal Defect Classification) [9], nata con l'obbiettivo di fornire agli sviluppatori un
feedback efficace in tempi contenuti. Allo stato dell'arte, prima dell'introduzione
dell'ODC, le tecniche tradizionali difficilmente avevano la capacità di fornire un livello di
dettaglio sufficiente per una comprensione a grana fine del processo di sviluppo, per il
quale, in molti casi, le decisioni erano guidate dall'intuizione e dall'esperienza, piuttosto
che da misure, analisi ed altri approcci tipicamente ingegneristici. In questo l'ODC
possiede il principale elemento di innovazione, assistendo i processi decisionali sulla base
di misurazioni e studi analitici. Lo sforzo principale di questa metodologia consiste nel
ricavare da un insieme di informazioni molto ampio e ricco dal punto di vista semantico
(vengono raccolti i difetti software in tutte le fasi del sistema, dallo sviluppo alla fase
operativa), poche informazioni essenziali effettivamente utili, incentrate sul concetto di
difetto.
A differenza dell'hardware, un difetto software ha una natura piuttosto amorfa e c'è una
difficoltà intrinseca nel dare a questo una definizione chiara, univoca e non ambigua.
Tuttavia, per gli scopi dell'ODC, il concetto di difetto software viene inteso in maniera
molto semplice e sintetica: è la necessità di una modifica al software. Posto in quest'ottica,
il difetto si rende più facilmente identificabile e tracciabile di quanto non possa essere un
fault, un errore o un failure.
Nello spettro delle metodologie di analisi dei difetti, dunque, l'ODC si colloca al centro tra
due estremi opposti: da un lato i metodi puramente quantitativi (matematici, statistici) che
hanno, ad esempio, lo scopo di stimare il tempo di completamento o i costi di garanzia;
dall'altro i metodi di analisi puramente qualitativa, come la Root Cause Analysis. La figura
seguente riassume questo concetto.
15
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Secondo tale approccio, i fault sono raggruppati in classi ortogonali, ossia non
sovrapposte, in base a due attributi fondamentali: i defect type e i defect trigger.
I defect type, sono definiti in un'ottica molto vicina a quella dello
sviluppatore/programmatore, da cui la consistente utilità del feedback dei risultati, in
particolare vengono definiti in base alla correzione necessaria a risolverli. L'ODC classica
prevede i seguenti defect type:
• Function. Un difetto di questo tipo condiziona significativamente le capacità del
sistema, le interfacce di programmazione a livello applicativo (API), strutture dati
globali, caratteristiche di interfacciamento con l'utente o con l'architettura hardware.
Richiede una modifica formale al progetto.
• Assignment. A differenza del caso precedente, questo tipo di difetto riguarda poche
linee di codice, come l'inizializzazione di strutture dati o istruzioni di controllo.
• Interface. Riguarda l'interazione con altri componenti, moduli, driver di periferiche,
etc.
• Checking. Rientra in questa classe la mancata validazione di dati e valori prima che
questi vengano utilizzati.
• Timing/Serialization. Questi difetti vengono corretti migliorando la gestione di risorse
condivise e real-time.
• Build/Package/Merge. Sono difetti nelle libreria di sistema, nella gestione delle
modifiche o nel controllo delle versioni.16
Illustrazione 5: Spettro delle metodologie di analisi dei difetti software
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
• Documentation. Tali difetti riguardano la documentazione del prodotto, le note di
rilascio o le note per la manutenzione.
• Algorithm. Riguardano problemi di efficienza o correttezza degli algoritmi e possono
essere risolti modificandoli o implementandoli nuovamente, senza comportare una
modifica formale del progetto.
Se il concetto di defect type non è una peculiarità dell'ODC, in quanto è presente già nella
letteratura precedente, quello di defect trigger è stato introdotto ex-novo, per rispondere
alle esigenze della particolare metodologia. In questo contesto, il termine trigger può
essere reso in Italiano con l'espressione causa scatenante in quanto rappresenta ciò che
permette l'attivazione di un fault. Da questo punto di vista, conoscere i defect trigger è
utile per migliorare la comprensione degli aspetti di verifica del processo di sviluppo.
Poiché, infatti, i software fault più problematici sono quelli che sfuggono alle attività di
testing in fase di sviluppo, tali attività potrebbero beneficiare notevolmente delle
informazioni circa i defect trigger perchè queste consentono di capire le modalità con cui
testare il sistema al fine di scoprire il difetto. Per modalità si intende un concetto più
ampio della semplice enumerazione degli input o dei diversi flussi di esecuzione,
includendo anche le condizioni dell'ambiente in cui si esegue il sistema. I defect trigger
sono raggruppati in tre classi, corrispondenti ai diversi processi di testing che avvengono
nel ciclo di sviluppo:
• review – è un processo passivo, in cui si verifica il sistema quando non è in
esecuzione, mediante la documentazione ed il codice
• unit/function test – viene controllata attivamente l'implementazione, mediante
esecuzione del codice
• system test – si simula l'utilizzo effettivo da parte del cliente
Nel processo di review, i trigger consistono nell'attività da parte di un soggetto che riflette
sul prodotto, esamina il progetto, discute l'implementazione. Quando invece il prodotto
viene testato, il trigger consiste propriamente nella causa per cui è stato scritto il test case,
l'idea che ha portato a considerarlo.
17
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Per lo scopo di questo lavoro, i trigger più interessanti sono quelli che appartengono al
processo di system testing. Questi possono essere esemplificati come segue:
• Recovery/Exception handling. Un tale evento comporta l'esecuzione di codice di
“emergenza” causato dagli input provenienti dal workload.
• System startup and restart. Questi trigger riguardano le fasi di avvio o di spegnimento
del sitema, in condizioni ordinarie.
• Workload volume/stress. In questi casi il sistema è sottoposto a notevoli sforzi, sia in
termini di risorse che di capacità prestazionali.
• Hardware/software configuration. Questi trigger sono legati a cambiamenti che
avvengono nell'ambiente operativo del sistema.
• Normal mode. I trigger di questo tipo avvengono in condizioni ordinarie del sistema.
1.3 Stato dell'arte nell'iniezione di Software Faults
Come evidenziato in precedenza, le tecniche di fault tolerance utilizzate per migliorare la
dependability di un sistema, sono, a loro volta, suscettibili di errori e incorrettezze. Si pone
dunque il problema di verificare che tali tecniche rispondano in maniera adeguata ai
requisiti. Nel testing dei meccanismi di tolleranza ai guasti ci si pone l'obbiettivo di
verificare che, in presenza di determinati fault, questi siano tollerati opportunamente,
ossia, non degenerino in failure, essendo mascherati, corretti o, ove possibile, ignorati. Se
ciò non è possibile, l'obbiettivo diventa comprendere l'incidenza dei danni conseguenti al
mancato successo delle tecniche. Quando il testing consiste nella deliberata alterazione del
sistema in modo da provocare artificialmente l'occorrenza di fault, per studiare la reazione
del sistema, si parla di fault injection.
Poiché i sistemi in considerazione sono spesso notevolmente complessi, le fault location,
ossia le parti del sistema in cui è possibile iniettare guasti, possono riguardare i più
disparati aspetti del sistema stesso. Una prima, importante distinzione, riguarda l'iniezione
di tipo hardware o software. Nel primo caso, si può agire sia a livello fisico, intervenendo
18
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
direttamente sulle componenti elettroniche del sistema, soprattutto se queste sono
progettate tenendo in conto l'obbiettivo della verificabilità (design for testability), sia a
livello logico (che è la tecnica più diffusa), ad esempio emulando gli hardware fault via
software (SWIFI – Software Implemented Fault Injection).
Negli ultimi anni però, vista la maggiore complessità del software e la preponderanza dei
guasti software come cause di fallimenti, la tendenza si è spostata e gli studi si sono
concentrati maggiormente sul software. Anche restringendo le location al solo livello
software, si presentano diverse tecniche alternative, ognuna delle quali presenta differenti
caratteristiche.
In [1] viene fatta una distinzione tra le tecniche basate sull'esecuzione e quelle basate sulla
simulazione.
Nel primo caso, gli esperimenti riguardano il sistema nel suo stadio finale, quando è
considerato ultimato e pronto per l'utilizzo. Con tali tecniche è possibile valutare
concretamente gli attributi di dependability del sistema, ma difficilmente i risultati ottenuti
possono guidare attività di revisione o modifica.
Nel secondo caso invece, viene formulato un modello del sistema, utile a condurre
simulazioni di esperimenti. I fault, infatti, vengono introdotti nel modello piuttosto che nel
sistema stesso, e permettono di comprendere gli effetti degli stessi nei riguardi del
comportamento operativo del sistema. Queste tecniche, intrinsecamente più lente, hanno il
vantaggio di fornire un feedback per la fase di sviluppo.
Un'ulteriore alternativa consiste nel concentrare l'attività di fault injection su un prototipo
del sistema. Tale modalità permette di testare il sistema senza che questo sia
necessariamente completato, e soprattutto senza fare le assunzioni sul sistema necessarie, a
causa delle astrazioni di alto livello, per le tecniche basate su modelli.
Un altro criterio per diversificare le tecniche di fault injection software è il momento in cui
avviene l'iniezione: a tempo di compilazione oppure a runtime. Nel primo caso, il codice
del programma (sorgente o macchina) deve essere modificato prima che l'immagine di
19
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
questo sia caricata ed eseguita. Questo metodo non richiede software aggiuntivi a runtime,
né causa perturbazioni al sistema al momento dell'esecuzione. Inoltre, poiché il fault è
cablato nel codice, può essere utilizzato per emulare fault permanenti. Lo svantaggio è che
non è possibile controllare l'iniezione a tempo di esecuzione.
Nel secondo caso, invece, di iniezione a runtime, si rende necessario un meccanismo di
triggering del fault, che faccia cioè innescare l'attivazione del fault (per un
approfondimento sul concetto di trigger si veda il paragrafo sull'ODC). Le tecniche di
triggering includono strumenti di time-out, che possono ad esempio generare degli
interrupt per invocare l'iniezione. Questa tecnica non richiede modifiche all'applicazione
target, né al workload dell'esperimento. Poiché questo metodo inietta i fault su una base
temporale piuttosto che in corrispondenza di eventi specifici o stati del sistema, produce
effetti e comportamenti non predicibili, ma è utile per emulare fault transienti ed
intermittenti.
Alternativamente ai meccanismi di time-out, si può ricorrere ad una tecnica basata sulle
eccezioni (o trap), che, via hardware o via software, possono essere utilizzate per trasferire
il controllo ad un modulo di iniezione.
L'ultima delle alternative di iniezione a runtime, simile alla tecnica di modifica del codice,
consiste nell'inserire istruzioni aggiuntive al programma target che permettono
l'esecuzione di codice per l'iniezione, prima di una particolare istruzione. A differenza del
metodo delle interruzioni, il modulo di iniezione può essere parte del programma target ed
essere eseguito in user mode piùttosto che in system mode.
Un'altra tecnica diffusa nel testing di componenti COTS e dei sistemi operativi, consiste
nell'iniettare i fault fornendo input eccezionali o erronei al componente, ed è chiamata
robustness testing [8]. I fault introdotti con la tecnica di robustness testing rappresentano
possibili conseguenze di fault software reali nei componenti che interagiscono con il
componente target. La questione di stimare la rappresentatività dei fault così iniettati è
stata affrontata in uno studio di Madeira et al. [2], dove si dimostra che, sebbene questa
20
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
tecnica possa ritenersi utile nel rivelare vulnerabilità nei componenti (intesa nei riguardi di
input eccezionali piuttosto che nell'accezione utilizzata nell'ambito della sicurezza), non è
utilizzabile per riprodurre fault che siano rappresentativi dei fault interni ai componenti, e
l'impatto causato dalle due classi di fault è sostanzialmente diverso.
In un altro importante lavoro [3], Madeira ha condotto uno studio sperimentale
sull'emulazione dei fault software mediante la fault injection. Uno dei problemi principali
della fault injection in generale, e della software fault injection in particolare, è la
rappresentatività dei fault iniettati, al fine di ottenere risultati significativi.
In letteratura sono presenti numerosi lavori che si occupano di studiare i fault hardware,
classificandoli ed ottenendo statistiche sulle tipologie e frequenze di occorrenza. Non si
può dire altrettanto sui fault software che superano le attività di testing. La scarsa presenza
in letteratura di lavori in merito a questa problematica è dovuta alla difficile comprensione
di questo tipo di fault, che sono spesso difficili da riprodurre e frequentemente, quando il
sistema è in fase operazionale, non vengono riportati, proprio a causa delle difficoltà
intrinseche che ciò comporta. Fra i pochi lavori in merito a questa problematica si
annoverano [15] e [17].
Sebbene altri lavori abbiano cercato di trovare una soluzione al problema di simulare fault
software attraverso la fault injection, il contributo di Madeira [3] consiste nel determinare
in che misura ci si può servire di tool di iniezione di guasti generici per l'emulazione dei
fault software e quanto questi fault siano accurati comparandoli con i fault software reali.
Il risultato a cui si perviene è solo parzialmente positivo: non tutti i software che si
riscontrano nella realtà possono essere simulati con questa tecnica. Da queste
considerazioni nasce l'esigenza di una tecnica differente, finalizzata alla simulazione dei
fault software. Proprio Madeira, in uno studio successivo [5] ha studiato l'utilizzo di una
tecnica pensata specificamente per l'iniezione di guasti software. Tale tecnica, denominata
G-SWFIT (Generic Software Fault Injection Technique), consiste nell'ispezionare il
codice binario dell'applicazione (è dunque una tecnica indipendente dal linguaggio di
21
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
programmazione), al fine di trovare delle fault location in cui vengono iniettati errori,
piuttosto che fault, purchè questi siano rappresentativi di guasti software. Inoltre, questi
stessi guasti software sono tenuti, a loro volta, ad essere rappresentativi dei software fault
realmente presenti nei moderni sistemi, e con le stesse distribuzioni probabilistiche, sia in
termini di percentuali relative che in riferimento ai diversi moduli del sistema. Per gli
scopi di questo lavoro, l'aspetto più interessante del lavoro di Madeira consiste nella
metodologia di raccolta e classificazione dei dati (field data) utili per caratterizzare
statisticamente e quantitativamente i software fault che, come già discusso, sono una
tipologia di dati praticamente assente in letteratura.
Al fine di rispettare il requisito della rappresentatività dei software fault, vengono
collezionati numerosi fault presenti in sistemi reali, attraverso la raccolta di patch
applicate ad un discreto numero di programmi open source. Il codice di tali modifiche è
stato ispezionato manualmente per comprendere appieno e classificare correttamente il
relativo fault. In totale vengono raccolti 668 fault di applicazioni che appartengono a
disparate categorie di software: text editing, graphical editing, ambiente di shell, kernel di
sistema, multiplayer games ed altri. L'approccio utilizzato per classificare questi fault è
composto di diversi stadi. Il primo di questi consiste in una classificazione elementare
basata sull'ODC (cfr. paragrafo sull'ODC). Poiché, com'è stato discusso, questa
metodologia nasce con lo scopo di fornire un feedback per la fase di sviluppo, si rende
necessario un'ulteriore raffinamento nella classificazione che permetta di associare i fault
ad un determinato di tipo di costrutto del linguaggio di programmazione. Per costrutto si
intende un componente sintattico elementare del linguaggio di programmazione [5] e può
essere un'espressione, una valutazione, una chiamata a funzione etc. Madeira indica, nel
suo lavoro, tre di queste tipologie:
• costrutto mancante
• costrutto erroneo
• costrutto non necessario
22
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Chiaramente quest'estensione all'ODC fornisce un ausilio per l'attività di emulazione dei
fault, poiché emulare un costrutto mancante è sostanzialmente diverso dall'emulare un
costrutto erroneo. L'ultimo stadio della classificazione introdotto, anch'esso, al fine di
facilitare l'attività di fault injection, serve per formulare un insieme di cosiddetti fault
emulation operators, ognuno dei quali emula uno specifico tipo di fault, basandosi non
solo sul tipo del fault ma anche sul contesto in cui si colloca la fault location.
Come si nota dalla seguente figura, l'attività di iniezione dei fault riceve come input i fault
operators ed il codice binario e produce, in uscita, una serie di versioni mutate
dell'eseguibile dell'applicazione, ognuna contenente uno specifico fault.
I fault operator ricordano i cosiddetti mutation operators utilizzati nella tecnica di
software mutation. Tuttavia si differenziano da questi in quanto sono basati su dati
osservati sperimentalmente, piuttosto che essere generati sulla base di considerazioni ed
analisi del codice. Inoltre, la software mutation ha lo scopo di generare il miglior insieme
di casi di test, mentre qui lo scopo è emulare i fault. I fault operators, infatti, modificano il
codice eseguibile introducendo le alterazioni che sarebbero prodotte da un compilatore se,
nel codice sorgente, fosse presente un determinato fault. Nella tabella seguente vengono
23
Illustrazione 6: La tecnica G-SWFIT
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
esemplificati gli operatori che, in base allo studio di Maderia, sono relativi ai fault più
frequenti.
Operatore Fault emulato
OMFC Chiamata a funzione mancante
OMVIV Mancata inizializzazione di una variabile con un valore
OMVAV Mancata assegnazione di un valore ad una variabile
OMVAE Mancata assegnazione di una variabile mediante un'espressione
OMIA Costrutto if mancante in vicinanza di espressioni di valutazione
OMIFS Costrutto if ed espressioni di valutazione mancanti
OMIEB Costrutto if , espressioni di valutazione e parola chiave else mancanti
OMLAC Espressione in AND mancante in una condizione di valutazione
OMLOC Espressione in OR mancante in una condizione di valutazione
OMLPA Assenza di una parte ridotta e localizzata di un algoritmo
OWVAV Assegnazione di un valore erroneo ad una variabile
OWPFV Parametro errato in una chiamata a funzione
OWAEP Espressione aritmetica errata fornita come parametro in una chiamata a funzione
24
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Capitolo 2: Coflight - un caso di studio di sistema Fault
Tolerant
Questo lavoro di tesi si è concentrato su un particolare sistema fault-tolerant che è, al
momento, in fase di prototipazione. Questo sistema, chiamato COFLIGHT, si colloca
nell'ambito dell'Air Traffic Management (ATM), recentemente diventato ragione di studio
e ricerca a causa della crescente domanda di utilizzo rivolta a questo tipo di sistemi. Le
previsioni, infatti, indicano che gli attuali sistemi ad ausilio dell'ATM non saranno in
grado di rispondere alle esigenze del prossimo futuro. Le stime di EUROCONTROL,
Organizzazione Europea per la Sicurezza del Traffico Aereo, dimostrano questo trend
[10].
IFR Movements (000s)
2006 2007 2014 2020 2025 2030
A: Global Growth 14,119 17,532 19,890 22,086
B: Business as Usual 12,930 15,553 17,763 19,549
C: Regualtion & Growth 9,439 9,916 12,930 14,955 16,724 18,170
D: Fragmenting World 11,773 13,460 15,062 16,507
Average Annual Growth
AAGR2030/2007
TrafficMultiple2030/20072006 2007 2014
2020/
2015
2025/
2021
2030/
2026
A: Global Growth 5.2% 3.8% 2.6% 2.1% 3.5% 2.2
B: Business as Usual 3.9% 3.1% 2.7% 1.9% 3.0% 2.0
C: Regualtion & Growth 3.9% 5.1% 3.9% 2.5% 2.3% 1.7% 2.7% 1.8
D: Fragmenting World 2.5% 2.2% 2.3% 1.8% 2.2% 1.7
Tuttavia, il problema della capacità di servizio dei sistemi ATM non è l'unico movente di
tali ricerche. I requisiti di tali sistemi, infatti, comprendono la necessità di interoperabilità
che è tipica dei sistemi complessi moderni, che devono essere in grado di interagire con
sistemi legacy o sviluppati da organizzazioni diverse, e di essere facilmente estendibili ed
integrabili.
25
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Queste problematiche estendono ulteriormente la complessità di tali sistemi, già di per sé
complicati a causa della quantità di sottosistemi di cui sono composti, della loro
dislocazione, e della loro alta criticità.
I sistemi ATM, infatti, rientrano nella categoria dei sistemi Distributed Real-time
Embedded (DRE), una classe di particolare importanza in numerosi settori, fra cui quello
medico, finanziario, dei trasporti, tutti accomunati dal requisito che i diversi componenti
del sistema siano dislocati, eventualmente in aree notevolmente distanti, cooperando in
maniera affidabile per fornire uno o più servizi con tempistiche definite in maniera molto
rigida. Spesso, tuttavia, al requisito di real-time, si sostituisce quello meno stringente di
near real-time. Con tale requisito, si richiede che un sistema fornisca un servizio con una
tempistica che rientri in un intervallo ben definito di possibili valori.
Inoltre, i sistemi ATM, rientrano nell'ambito dei sistemi CCIS (Command, Control and
Information Systems), utilizzati in ambito militare. Questi sistemi stanno subendo,
recentemente, profonde modifiche strutturali, rese necessarie dalla rivoluzione
informativo-tecnologica a cui ha dato luogo l'avvento di Internet [11].
2.1 Introduzione a CARDAMOM
Cardamom è una piattaforma middleware di nuova generazione, nata con l'obbiettivo di
supportare lo sviluppo di sistemi dotati delle caratteristiche appena illustrate.
La caratteristiche tipiche di un middleware tradizionale consistono nel fornire trasparenza
a vari livelli di astrazione, fra cui:
• trasparenza del sistema operativo
• trasparenza del linguaggio di programmazione
• trasparenza della locazione
• trasparenza della migrazione
26
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Per ulteriori approfondimenti in merito alle tecnologie middleware si rimanda a [23] e
[24].
Oltre alle caratteristiche tipiche di un middleware, vi è un insieme di caratteristiche
innovative, che rendono Cardamom una soluzione efficiente per le diverse problematiche
sopra esposte. Fra queste, le principali sono:
• conformità alla specifica CORBA dell'Object Management Group (OMG). L'OMG è
un consorzio internazionale non-profit per l'industria informatica, ad iscrizione aperta,
a cui fanno capo un notevole numero di aziende leader del settore. Questo è un
notevole sforzo in direzione della standardizzazione per l'interoperabilità, necessario
per sistemi complessi.
• supporto per il Modello a Componenti di Corba (CCM). I principali vantaggi che tale
modello comporta comprendono la facilità di deployment, l'alto grado di portabilità, la
separazione tra l'aspetto funzionale, ossia la logica applicativa, e gli altri aspetti non
funzionali, legati alla piattaforma d'esecuzione;
• conformità alla specifica CORBA Fault Tolerance. Come ampiamente evidenziato, i
sistemi in questione sono altamente critici e quindi necessitano di soluzioni che
forniscano la tolleranza ai guasti, garantendo il corretto funzionamento del sistema
anche in presenza di questi. La specifica CORBA Fault Tolerance è nata appunto con
lo scopo di standardizzare questi aspetti.
Al di là degli aspetti di Cardamom, strettamente legati alla tipologia di sistemi in
questione, vi sono una serie di caratteristiche che ne fanno un prodotto innovativo ed
appetibile in una serie di differenti contesti:
• è un prodotto open-source. I vantaggi che ciò comporta sono ben noti, ed è il caso di
menzionare il fatto che questo stesso lavoro si è avvalso di questa caratteristica.
• fornisce una piattaforma d'esecuzione a valore aggiunto. Tale valore aggiunto consiste
in una serie di servizi necessari ai sistemi in questione, come ad esempio la QoS.
27
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Questo è uno degli aspetti che la specifica CORBA non copre e a cui Cardamom
sopperisce. Soluzioni di questo tipo vengono formalizzate e prototipizzate, quindi
sottoposte all'OMG.
• supporto alla Model Driven Architecture dell'OMG. Questo è un approccio al processo
di sviluppo che si concentra sul dominio applicativo prescindendo dall'ambiente
d'esecuzione finale, facilitando notevolmente quest'attività.
La figura 7 riassume la struttura ed i servizi forniti da Cardamom. Questi vengono detti
servizi pluggable ossia attivabili, a seconda della specifica applicazione. Di seguito
28
Illustrazione 7: I Pluggable Services di Cardamom
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
vengono illustrati i principali fra questi servizi.
• System Management
E' uno dei servizi maggiormente importanti, in quanto si occupa di gestire e
monitorare tutti gli elementi del sistema. Per elementi del sistema, nella terminologia
di Cardamom, si intendono processi, componenti, applicazioni, nodi o interi
sottosistemi. Le attività principali del System Management sono:
◦ configurazione del sistema, in fase iniziale ed in fase operativa
◦ gestione dell'avvio (eventualmente ordinato) e della terminazione delle diverse
parti del sistema
◦ monitoraggio delle risorse dei nodi
◦ detection di elementi faulty
◦ riconfigurazione manuale o automatica del sistema, in seguito all'attivazione di
fault o all'occorrenza di failure
◦ notifica ad eventuali parti interessate di cambiamenti di stato o di configurazione
• Life Cycle service
Questo servizio si occupa di allocare e deallocare gli oggetti CORBA e gestire il ciclo
di vita dei servant attraverso strategie opportunamente definite, basate sulle policy dei
corrispondenti Portable Object Adapters (POA).
• Repository service
Fornisce un struttura di memorizzazione di per i riferimenti e gli attributi degli oggetti.
• Event service
Fornisce un supporto alla comunicazione asincrona mediante il modello push di
scambio dei messaggi
• Data distribution service
Supporta la creazione e la sincronizzazione di dati replicati sui diversi nodi, mediante
il modello publish-subscribe.
• Time management service
29
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Permette l'esecuzione periodica di determinate operazioni ad istanti di tempo definiti
dall'utente o basati su time-out. Questo servizio è conforme alla specifica OMG
“Enhanced View of Time”
• Persistence service (non ancora implementato)
Fornisce un supporto alla persistenza dello stato degli oggetti CORBA mediante
tecniche di memorizzazione (file, database relazionali o object oriented)
• Transaction service (non ancora implementato)
Fornisce delle interfacce di supporto alle transazioni tra oggetti distribuiti
• Recording service (non ancora implementato)
Supporta la registrazione di eventi ed informazioni per l'analisi tecnica del sistema.
• Traces and performance service
Permette l'analisi a run-time delle prestazioni della piattaforma, così come
dell'applicazione.
Due servizi di Cardamom sono stati omessi in questa trattazione in quanto meritano un
maggiore approfondimento, presente nel seguito di questo capitolo:
• Fault-tolerant service
• Load-Balancing service
La figura 8 mostra che, oltre ai pluggable services, vi sono una serie di servizi di base
(core services) necessari a qualunque sistema, con i quali interagiscono i pluggable
services per fornire i loro servizi.
30
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
I principali core services sono:
• Cardamom Foundation. E' un insieme di software ausiliari, che viene incluso in ogni
processo utente e fornisce pattern, protocolli e algoritmi comuni a tutti i servizi di
Cardamom. Inoltre, fornisce un'astrazione rispetto al sistema operativo e l'ORB
sottostanti in modo da rendere il sistema indipendente dalla specifica implementazione
di CORBA e dalla specifica piattaforma d'esecuzione.
• Configuration. La configurazione del sistema è demandata a due insiemi di file:
◦ Generation description – contengono, per ogni eseguibile dell'utente, i servizi e i
profili utilizzati, il compilatore, il sistema operativo e l'ORB utilizzati, etc.
◦ Deployment description – contengono informazioni circa il deployment del
sistema, come ad esempio la distribuzione dei processi sui diversi host.
E' utile, a questo punto, definire il concetto di oggetto Cardamom, poiché questo si
discosta dal concetto di oggetto CORBA comunemente conosciuto. Un oggetto
Cardamom incapsula un oggetto CORBA, che è in relazione biunivoca con esso, per cui
possiede tutte le proprietà di un tale oggetto, e ne estende le potenzialità fornendo dei
meccanismi utili all'interazione con i servizi Cardamom, come la persistenza, la fault
31
Illustrazione 8: Struttura di Cardamom
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
tolerance, il load balancing etc.
2.2 Fault Tolerance Service
Il servizio Fault Tolerance di Cardamom è stato sviluppato in conformità alla specifica
CORBA Fault tolerance, presente in [24].
Tale specifica definisce un'architettura e un insieme di servizi e meccanismi per la fault
tolerance che siano di supporto ai sistemi distribuiti con caratteristiche di alta affidabilità e
availability (per una definizione si rimanda al paragrafo 1.1), e si basa sui concetti di
ridondanza, fault detection e fault recovery. La ridondanza viene attuata mediante la
replicazione degli oggetti CORBA, che vengono associati ad un object group, ed ognuno
di essi fa capo ad un'interfaccia comune. I membri di un object group possono essere
referenziati mediante un Interoperable Object Group Reference (IOGR) che è l'estensione
del concetto IOR utilizzato per referenziare i singoli oggetti al caso di un gruppo. Tutto ciò
è implementato, nell'ottica del middleware, in modo da essere completamente trasparente
ai client di tali oggetti che li referenziano come se fosse un unico oggetto. La specifica
CORBA Fault Tolerance definisce anche dei componenti che monitorino le diverse
repliche notificando eventi d'interesse come crash di applicazioni o di nodi.
Nella sua versione attuale, il servizio fault tolerance di Cardamom supporta la sola
modalità di replicazione warm passive, che consiste nel mantenere una replica di un'entità
costantemente aggiornata, ma in stato di inattività fino al momento in cui non viene
rilevato il failure della copia primaria.
Un processo Cardamom che ospita delle repliche di oggetti viene detto Fault Tolerance
Location (FT Location) ed è gestito da un oggetto di tipo LocationManager, che a sua
volta include un oggetto MembersAdmin responsabile di gestire le repliche.
La fault tolerance di Cardamom e attuata a livello di processo, nel senso che tutti gli
oggetti di un processo sono allo stesso tempo copie primarie o di backup, e inoltre tutti i
processi replicati devono contenere gli stessi oggetti. Le entità principali su cui si basa la
32
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
fault tolerance sono il Fault Tolerance Manager e i Location Manager.
Come si nota dalla figura 9, il Fault Tolerance Manager è composto da tre elementi:
• Fault detector. Rileva la presenza di fault nel sistema e genera dei fault reports
• Fault notifier. Inoltra i fault reports alle entità interessate, preventivamente registrate al
servizio
• Replication Manager. Permette alle applicazioni di gestire i gruppi di oggetti e i
membri di questi. Inoltre, in quanto registrato al servizio di notifica dei fault report,
avvia il processo di recovery quando viene notificato un fault. In particolare, con la
33
Illustrazione 9: Fault Tolerance in Cardamom
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
tecnica di replicazione warm passive, se la replica primaria di un gruppo viene
dichiarata faulty, il Replication Manager procede all'elezione di una nuova copia
primaria.
I Location Manager, invece, sono entità locali ai nodi che ospitano oggetti replicati e
possiedono un'interfaccia per permettere al Replication Manager di aggiornare le FT
Locations modificando il gruppo di repliche. Come accennato in precedenza, con la
replicazione warm passive, un oggetto viene attivato quanto è eletto primario, mentre la
precedente copia primaria viene disattivata dal rispettivo Location Manager.
2.3 Load Balancing Service
Il concetto di load balancing nasce dalla duplice necessità di suddividere il carico su più
server ed evitare la presenza di un single point of failure. In presenza di un unico server,
infatti, incaricato di svolgere una determinata funzione per un dato sistema, l'occorrenza di
un failure pregiudica completamente la disponibilità di quel servizio.
Quando invece il servizio è offerto da più di un server, la probabilità che ciò accada è
inversamente proporzionale al numero di server: in presenza di n server il sistema è in
grado di tollerare i fallimenti di n - 1 server, fornendo comunque il servizio desiderato,
seppure con performance ridotte, nel qual caso si parla di graceful degradation.
Il servizio di Load Balancing di Cardamom permette di smistare le richieste di
un'applicazione client ad un gruppo di server, in maniera da bilanciare il carico ad essi
demandato. A tal fine, il servizio fornisce un meccanismo per stabilire da quale server
deve essere servita ogni singola richiesta. E' basato sulla specifica CORBA, e prevede
diverse strategie di smistamento, fra cui la strategia round robin e la strategia random. In
particolare, la strategia round robin, utilizzata nell'implementazione di COFLIGHT,
discussa in seguito, consiste nello smistamento delle richieste secondo una modalità
circolare, nel senso che i server sono collegati logicamente a formare un cerchio, che viene
34
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
percorso in un senso stabilito, e ogni richiesta viene servita dal nodo successivo nel
cerchio.
L'entità incaricata di mantenere l'elenco dei server disponibili è chiamata Load Balancing
Object Manager. La logica di load balancing può essere configurata staticamente oppure
può essere definita dall'applicazione che utilizza il servizio. In conformità con quanto
detto circa la trasparenza alla locazione, obbiettivo del middleware, lo smistamento
avviene senza che il client sia a conoscenza dei dettagli, sebbene, per questioni di
performance, il servizio viene collocato dal lato del client stesso. Infatti, il principio alla
base dell'implementazione di questo servizio è, come visto per il servizio di Fault
Tolerance, l'IOGR. Questo fa sì che il client ha un solo riferimento all'oggetto che
implementa l'interfaccia desiderata, ed è compito del servizio di load balancing tradurre
quest'indirizzo virtuale in un riferimento reale, relativo all'oggetto servente opportuno.
Questo servizio è implementato da quattro moduli fondamentali:
• LBCommon: fornisce dei servizi di ausilio alla gestione degli object group reference e
alla configurazione del servizio
• LBGroupManager: include, fra l'altro, la classe che implementa la vera e propria
attività di load balancing, che comprende la creazione di un gruppo, l'aggiunta o la
rimozione di membri e la scelta delle strategie35
Illustrazione 10: Load Balancing in modalità round robin
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
• LBInit: classi di ausilio all'inizializzazione del servizio
• LBStrategies: interfacce per la definizione delle strategie di bilanciamento, ed alcune
implementazioni.
2.4 COFLIGHT
Come anticipato in precedenza, il sistema Coflight si colloca nell'ambito dei sistemi di
supporto all'Air Traffic Management. Più in dettaglio, tale sistema fa parte della categoria
dei Flight Data Processing System (FDPS), un componente basilare di un sistema ATM,
che si occupa della gestione dei piani di volo, chiamati, nella terminologia dell'ambiente,
Flight Data Plan (FDP). Un FDP è una struttura dati che contiene una serie di
informazioni in merito ad un volo aereo. Tra le principali rientrano gli identificativi degli
aeroporti di partenza e di arrivo, la tipologia del volo (IFR o VFR), il numero di
passeggeri, aeroporti di arrivo alternativi, ed alcune informazioni più dettagliate, di tipo
tecnico, che sono il prodotto dell'attività di flight planning, prevista per ogni volo, in cui si
effettua il calcolo della rotta per il volo, in modo da evitare collisioni e ottimizzare lo
spazio aereo che, come accennato, sarà sempre più intensamente trafficato con gli anni a
venire. Questa definizione di massima dell'FDP nasconde, in realtà una struttura dati molto
complessa e dalle dimensioni tutt'altro che trascurabili, a tal punto che si è reso necessario,
per essa così come per altre realtà di questo contesto, un processo di standardizzazione,
che è stato guidato dal progetto Avenue, con il quale sono state definite una serie di
interfacce ed un data dictionary con il consenso di tutti gli stakeholder [12].
36
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
La complessità di questa struttura dati lascia intuire la corrispondente complessità del
sistema incaricato di gestirle, il FDPS, soprattutto se si tiene conto del fatto che questo
tipo di dati viene acceduto in scrittura con una frequenza che è stata stimata, nei sistemi
attualmente utilizzati, pari al 100 operazioni al secondo (in un campione di 8000 FPL),
che, considerata la dimensione dei dati acceduti è un carico notevole. Ciò non è dovuto
solo alla numerosità media dei voli attuali, ma anche al numero delle entità del sistema che
accedono a queste informazioni, fra cui i Processing Server, incaricati di processare i piani
di volo, le Controller Working Position (CWP), attraverso le quali si possono inserire,
aggiornare o cancellare piani di volo, ed altre.
Per rispondere ai requisiti di dependability precedentemente illustrati, non ultimi quello di
high availability e safety, Coflight è stato progettato prevedendo la presenza di più
Processing Server (tre nella versione attuale), gestiti secondo una politica di load
balancing. Per motivi prestazionali, inoltre, i tre Processing Server non vengono acceduti
in maniera diretta dai client, ma tramite l'intercessione di un componente che fa da
intermediario, chiamato Facade. Questo ha il compito di gestire le richieste che
provengono da tutti i client, inoltrandole ai processing server secondo la politica di load
balancing prevista (nel caso in esame la politica è round robin), facendo in modo che un
certo piano di volo sia processato, nello stesso momento, al più da un solo processing
server, per questioni di coerenza dei dati. Per questo motivo, il Facade ha anche il compito
37
Illustrazione 11: I tipi di dato di un FDP definiti dal progetto Avenue
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
di accodare le richieste per piani di volo già in processamento, inoltrandole solo quando
possibile.
Questo aspetto del Facade fa intuire la necessità, da parte di quest'entità, di mantenere uno
stato dell'attività di processamento. Il concetto di stato di processamento verrà discusso
ampiamente nel paragrafo successivo, in quanto cruciale per l'attività di questo lavoro. In
questa trattazione introduttiva al sistema, è sufficiente tenere presente che il Facade tiene
costantemente traccia degli identificativi dei piani di volo correntemente in processamento
e i rispettivi identificativi dei client che ne hanno fatto richiesta. Questa seconda
informazione è necessaria a causa della politica di sincronizzazione con cui si gestiscono
le richieste, che prevede un disaccoppiamento tra client e facade al momento della
richiesta in modo che i client non rimangano bloccati in attesa della risposta: è il facade ad
inviare la notizia di processamento avvenuto al client che ne aveva fatto richiesta,
mediante il noto meccanismo delle callback.
L'importanza del facade, che già si va delineando, risulta ancora più chiara se si tiene
conto del fatto che rappresenta un single point of failure. Come già discusso, questo tipo di
problema va evitato nei sistemi safety-critical come questo, ed è per questo motivo che il
Facade è, in questo sistema, oggetto di replicazione: il servizio fault tolerance di
Cardamom permette di mantenere una copia di backup del facade primario costantemente
aggiornata con le informazioni circa i piani di volo, in maniera tale che se dovesse essere
rilevato un failure della copia primaria, da parte dell'infrastruttura di Cardamom preposta,
la copia di backup viene attivata e sostituisce l'unità faulty, garantendo la continuità del
servizo. Il punto appena esposto è di particolare importanza per questo lavoro, in quando
l'attività di testing del sistema, descritta nei capitoli successivi, riguarda appunto
quest'aspetto del sistema. Data la necessità di rendere possibile lo scambio di informazioni
sugli FDP tra un ampio numero di entità coinvolte, Coflight utilizza un paradigma di
comunicazione di tipo publish-subscribe in grado di disaccoppiare la comunicazione tra le
diverse entità. La tecnologia utilizzata per questo scopo è quella dei Data Distribution
38
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Service (DDS), uno standard OMG che definisce un middleware per lo scambio di
informazioni, di tipo data-centric. Il disaccoppiamento tra le diverse entità interessate
consiste nella possibilità, da parte dei publishers di informazioni, di disinteressarsi
completamente delle entità che le riceveranno, così come i subscriber non hanno necessità
di sincronizzarsi direttamente con le entità che pubblicano le informazioni: un canale
d'informazione comune raccoglie le pubblicazioni e le sottoscrizioni garantendo che i
subscriber ricevano notifica delle pubblicazioni a cui sono interessati, nella versione più
aggiornata.
Nel caso particolare di Coflight il DDS viene utilizzato per lo scambio dei FDP, in
particolare, quando un client effettua una richiesta circa un certo FDP, questa viene
intercettata dal Facade, che come illustrato, la inoltra eventualmente ad un processing
server. Quest'ultimo, al fine di processare l'FDP in questione, ne preleva la versione più
aggiornata dal DDS. Una volta effettuato il processamento, il processing server restituisce
l'intero FDP aggiornato al facade, che provvederà a pubblicarlo sul DDS e a notificare il
client del completamento della richiesta.
L'architettura del sistema Coflight, quindi, prevede la separazione del sistema in più livelli
di astrazione: il livello applicativo, che risponde ai requisiti funzionali del sistema, si
poggia su un duplice livello middleware formato da Cardamom e dal DDS, che astraggono
i meccanismi di interazione tra le diverse entità. La figura che segue riassume questi
concetti.
39
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Allo stato attuale, il prototipo prevede la possibilità di richiedere ai Processing Server,
attraverso la mediazione del Facade, le seguenti operazioni
1. Inserimento di un piano di volo
2. Cancellazione di un piano di volo
3. Aggiornamento di un piano di volo
4. Modifica dell'SSR (Secondary surveillance radar) di un piano di volo
Dato lo stadio prototipale del sistema le operazioni illustrate hanno un carattere fittizio, nel
senso che i dati realmente istanziati o modificati non hanno né gli attributi né i valori
collegati in qualche modo con i dati reali.
2.5 Modellazione a stati del sistema
L'approccio di studio della dependability utilizzato in questo lavoro si basa sulla
modellazione del sistema mediante un formalismo basato sugli stati. Ciò è stato dettato
dalla volontà di analizzare gli aspetti relativi alla dependability del sistema,
differenziandone le caratteristiche in base a diverse situazioni operative del sistema. Tali
40
Illustrazione 12: Architettura di Coflight
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
situazioni, possono essere definite formalmente una volta introdotto un modello che tenga
in conto determinati aspetti. Poiché il processo di modellazione di un sistema difficilmente
comprendere tutti gli aspetti di questo, è sempre necessaria una cernita degli elementi da
considerare, che è comunque guidata dagli obbiettivi del modello stesso.
Nel caso in esame, l'obbiettivo è quello di guidare la validazione dei meccanismi di fault
tolerance del sistema, andando a testare un certo numero di specifiche condizioni di
funzionamento.
Il formalismo utilizzato è quello delle Finite State Machine (FSM), in base al quale si sono
analizzate diverse alternative per la definizione degli eventi e degli stati del sistema in
esame.In questa fase di definizione si sono affrontate una serie di problematiche, che
vengono qui riassunte ed in seguito analizzate:
• granularità nella definizione degli eventi d'interesse del sistema
• livello di dettaglio degli stati
• complessità del modello.
E' opportuno chiarire che, essendo il Facade e i Processing Server le entità critiche del
sistema, sia dal punto di vista della dependability che dal punto di vista della business
logic, si è deciso di definire stati e transizioni sulla base degli eventi di queste due entità.
Per quanto riguarda la definizione degli eventi è stata valutata l'opportunità di considerare
le system call effettuate dal Facade e dai Processing Server. Questo metodo fornisce la
possibilità di ottenere una consistente granularità nella modellazione del sistema, tale però
da risultare eccessiva per il caso in esame. Un'alternativa a questo metodo, che è quella
che è stata poi adottata, consiste nel considerare come simboli i messaggi di I/O del
Facade e dei Processing Server. Questo metodo, in confronto a quello precedente,
permette una minore granularità nella modellazione del sistema, non permettendo di
considerare particolari stati intermedi, tuttavia è sufficiente agli scopi di questo lavoro.
Con riferimento alla descrizione del funzionamento del sistema (par. 2.4), si riporta di
41
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
seguito una tabella indicativa dei messaggi considerati.
Messaggi prodotti dal Facade
01 SSR Update invoked on Facade by some client 02 Facade sent an SSR update request to LB_Group 03 FDP Update invoked on Facade by some client 04 Facade sent an FDP update request to LB_Group 05 Request return invoked on Facade by some Processing Server 06 Request return invoked on some client by Facade 07 Insert operation requested to Facade 08 Insert operation requested to LB_Group by Facade 09 Delete operation requested to Facade 10 Delete operation requested to LB_Group by Facade 20 Delete confirmation sent to Facade by some Processing Server 11 Delete confirmation sent to some client by Facade
Messagi prodotti dai Processing Server12 SSR Update invoked on Processing Server by Facade 13 Processing Server sent an SSR update confirmation to Facade 14 FDP Update invoked on Processing Server by Facade 15 Processing Server sent an FDP update confirmation to Facade 16 FDP insert operation requested to Processing Server by
Facade 17 Processing Server sent a Insert FDP confirmation to Facade 18 Delete operation requested to Processing Server by Facade 19 Delete confirmation sent to Facade by Processing Server
Come risulta dalla tabella, ad ogni messaggio è stato associato un codice numerico
univoco, al quale va aggiunto l'identificativo del piano di volo a cui il messaggio si
riferisce. Di seguito si riporta, a titolo di esempio, un estratto di una sequenza di messaggi
scambiati dalle entità del sistema.
42
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
0102: richiesta da parte del Client per il piano di volo 020202: richiesta da parte del Facade per il piano di volo 020101: richiesta da parte del Client per il piano di volo 010201: richiesta da parte del Facade per il piano di volo 011202: un Processing Server ha ricevuto una richiesta per il piano di volo 021201: un Processing Server ha ricevuto una richiesta per il piano di volo 011301: un Processing Server ha inviato una conferma di processamento per il piano di volo 010501: il Facade ha ricevuto una conferma per il piano di volo 011302: un Processing Server ha inviato una conferma di processamento per il piano di volo 020502: il Facade ha ricevuto una conferma per il piano di volo 020602: il Facade ha inviato una conferma al client per il piano di volo 020601: il Facade ha inviato una conferma al client per il piano di volo 01
Un'analisi altrettanto approfondita è stata fatta per la definizione degli stati del sistema. In
questa fase si sono presentate diverse possibilità, ognuna, come nel caso della definizione
dei messaggi, con differenti caratteristiche. Bisogna tenere in conto che l'obiettivo di
quest'attività è scegliere una soluzione che rappresenti un compromesso tra il livello di
dettaglio circa lo stato e la complessità ed il numero di stati che ne consegue.
Considerato il sistema in esame, e in particolare le tecniche di fault tolerance da esso
utilizzate, l'informazione minima che bisogna mantenere nel modellare lo stato è il numero
di richieste accodate ed il numero di richieste in processamento. Volendo aumentare il
dettaglio di informazione sullo stato si potrebbero aggiungere gli identificativi dei piani di
volo a cui queste richieste si riferiscono, tuttavia questa informazione aggiuntiva non è
realmente utile agli scopi di questo lavoro per cui è stata omessa.
Questa, inoltre, avrebbe comportato un consistente aumento del numero degli stati, infatti,
supponendo di limitare il numero di piani di volo ad un intero P, il numero massimo delle
richieste in coda a Q, si avrebbe un numero di stati pari a
A tale formula si perviene tenendo conto che, per la presenza di tre processing server,
ognuno di questi può processare uno qualunque dei P piani di volo, purchè questo non sia
43
P1!P1−3!
⋅∑i=0
Q
Pi =P1!
P1−3!⋅1−PQ1
1−P
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
già in processamento presso un altro processing server. Da queste considerazioni deriva il
primo termine del prodotto. Il secondo termine, invece, è dovuto al fatto che, potendo il
Facade accodare Q richieste, ognuna relativa ad uno qualunque dei P piani, si può
rappresentare quest'informazione come la somma delle possibili combinazioni di P simboli
al variare del numero di richieste in coda tra 0 e Q.
Per P = 5 e Q = 3 il numero di stati diventa pari a 25 920. Escludendo questa
informazione, invece il numero di stati, nelle stesse ipotesi fatte nel caso precedente,
sarebbe pari a 44.
Per questo motivo, invece che tenere conto della situazione di accodamento e
processamento dei singoli piani di volo – quest'informazione viene quindi persa -, si è
deciso di mantenere un'informazione più sintetica che comprende il totale di questi valori
e si distinguono gli eventi a seconda che questi riguardino un piano di volo non ancora in
processamento, già in processamento, in coda etc.
Un'ulteriore informazione aggiuntiva sarebbe la tipologia delle richieste in coda ed in
processamento. Questa informazione potrebbe effettivamente fornire una visione più
approfondita del sistema e quindi una maggiore possibilità di scelta sul triggering della
fault injection, aiutando quindi a capire meglio quali sono le fasi che attraversa il sistema
che sono più vulnerabili alla presenza di fault. Tuttavia includere quest'informazione nella
modellazione dello stato del sistema comporta necessariamente un'esplosione del numero
di stati con una conseguente complicazione sia della fase di analisi del modello, sia di
quella di fault injection.
Infatti, anche volendo limitare superiormente il numero di richieste in coda e di
processamento, considerando tre tipologie di richieste, il numero di possibili stati rende
questo metodo impraticabile.
Volendo modellare formalmente questa situazione, si avrebbero le seguenti variabili:
• up (numero di richieste di update in processamento)
44
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
• dp (numero di richieste di delete in processamento)
• ip (numero di richieste di insert in processamento)
• uq (numero di richieste di update in coda)
• dq (numero di richieste di delete in coda)
• iq (numero di richieste di insert in coda)
Queste variabili sono legate dalle seguenti relazioni:
up , dp , ip≥0
uq , dq , iq≥0
updpip≤3
updpip≤Q
Le ultime due relazioni esprimono rispettivamente il vincolo che non ci possono essere più
di tre richieste processate contemporaneamente ed il vincolo che non ci possono essere più
di Q richieste accodate. Ognuna di queste due relazioni comporta un numero di
combinazioni che è dato dal numero di punti a coordinate intere presenti all'interno di un
quarto di un cubo di lato rispettivamente 3 e Q.
45
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Tali quantità valgono rispettivamente:
Una dimostrazione di queste relazioni è presente nell'Appendice A.
Infine, moltiplicando tra loro queste due quantità, si ottiene il numero di possibili stati di
questo modello. Anche con un numero massimo di richieste in coda relativamente piccolo,
si ha un numero di stati notevole (ad es. per Q = 4 si hanno 700 stati). Per questo motivo
quest'informazione è stata omessa.
Tenendo presente il funzionamento del sistema, ed in particolare la tecnica di load
balancing da esso utilizzata per fornire la funzionalità di processamento delle richieste,
emerge un'ulteriore informazione che risulta opportuno considerare ed è il numero di
richieste sospese. Poiché infatti, il sistema in esame prevede tre Processing Server distinti
che possono processare contemporaneamente altrettanti piani di volo distinti, esiste la
possibilità che al Facade giunga una richiesta per un ulteriore piano di volo, distinto dai tre
attualmente processati. Tale richiesta, ed altre analoghe richieste, vengono sospese, ossia
non vengono inoltrate dal Facade finchè un Processing Server non notifica ad esso il
completamento di un'operazione di processamento, rendendosi quindi disponibile per
processare ulteriori richieste. Per lo scopo di questo lavoro quest'informazione è utile e
non comporta, peraltro, significative complicazioni ed è stata dunque inclusa nella
modellazione dello stato del sistema, pervenendo dunque ad un modello, in forma di Finite
State Machine, in cui gli stati sono caratterizzati da:
1. numero di richieste in processamento
2. numero di richieste in coda
3. numero di richieste sospese
46
∑i=1
4
[4−i−1⋅i ]
∑i=1
Q1
[Q1−i−1⋅i ]
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Un generico stato è rappresentato da una stringa suddivisa in tre parti, secondo questa
convenzione:
In questo modello le transizioni vengono attivate in corrispondenza dei relativi messaggi,
per cui è possibile identificare le une con gli altri.
Una versione semplificata del modello FSM presenta sei tipologie di transizione (ovvero
messaggi) che sono presentate nella seguente tabella:
47
Illustrazione 13: Modello a stati finiti del sistema
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
CODICE MITTENTE DESTINATARIO DESCRIZIONEFR Facade Processing
ServerIl Facade inoltra al processing server una richiesta di un client
FRQ Facade Processing Server
Il Facade inoltra al processing server una richiesta che era stata precedentemente accodata
PSC Processing Server
Facade Un Processing Server conferma al Facade il processamento di un piano di volo
CR Client Facade Un client invia una richiesta al FacadeCRQ Client Facade Un client invia una richiesta al Facade per un piano di
volo già in processamentoPSC+ Processing
ServerFacade Un Processing Server conferma al Facade il
processamento di un piano di volo quando c'è più di una richiesta sospesa
Si noti che le diverse tipologie di richiesta e di conferma (update, insert, delete) sono state
sintetizzate in una sola tipologia. Ad esempio, le richieste del Facade al gruppo di
Processing Server sono identificate indistintamente dal messaggio FR.
La distinzione tra transizioni di tipo FR ed FRQ si rende necessaria poiché quando il
Facade riceve una notifica del completamento di una richiesta, inoltrando un'ulteriore
richiesta può transitare in uno stato diverso a seconda dei due casi. Ad esempio,
supponendo che il sistema si trovi nello stato 1:0:0, se il messaggio è di tipo FR significa
che il Facade sta inoltrando una richiesta che non era stata accodata, dunque il sistema
transita nello stato 1:1:0. Se invece il messaggio è di tipo FRQ il Facade sta inoltrando
una richiesta precedentemente accodata e dunque transita nello stato 0:1:0.
Un discorso analogo è valido per le richieste di tipo CR e CRQ. In particolare va
sottolineato che le richieste di tipo CR sono state incluse nel modello solo a scopo
esemplificativo, ma non hanno rilevanza pratica in quanto non determinano transizioni di
stato. Ad ogni modo, volendo seguire visivamente e concettualmente un percorso nel
modello degli stati, tali transizioni trovano un senso logico. Ad esempio, supponendo che
il sistema si trovi nello stato 0:0:0, quando un client sottopone una richiesta di
processamento, si hanno rispettivamente le transizioni CR, FR, PSC come risulta
dall'illustrazione 14.
48
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
I messaggi di tipo CRQ fanno transitare il sistema in uno stato in cui il valore delle
richieste in coda è incrementato di un'unità. I messaggi di tipo PSC fanno transitare il
sistema in uno stato in cui il numero di richieste in processamento è decrementato di
un'unità. A questo fanno eccezione le transizioni di tipo PSC che partono dagli stati x:3:+.
In corrispondenza di tali eventi, infatti, il processing server che si è appena liberato,
notificando l'evento, avvia immediatamente il processamento della richiesta che era stata
sospesa. Per lo stesso motivo, i messaggi di tipo PSC+, non danno luogo a transizioni di
stato, poiché lo stato di partenza è caratterizzato dalla presenza di ulteriori richieste
sospese.
Di seguito si riporta un sequence diagram esemplificativo della logica di funzionamento
del sistema e delle interazioni tra le diverse entità, in cui sono inclusi gli stati attraversati
(figura 15).
49
Illustrazione 14: Un esempio di sequenza di messaggi applicata al modello
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
50
Illustrazione 15: Sequence Diagram di un caso d'uso
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Capitolo 3: Progettazione di campagne di testing basate su
Software Fault Injection
Nel Capitolo 1 è stato evidenziato che la software fault injection può essere utilizzata per
validare i meccanismi di fault tolerance di un sistema. Tale tematica viene ripresa e
approfondita in questo capitolo, che costituisce il fondamento di concetti e definizioni su
cui si basa il lavoro sperimentale effettuato, descritto nei capitoli successivi. E' importante
notare che le metodologie di validazione sono soggette esse stesse ad incorrettezze, di
conseguenza risulta di massima importanza organizzare le attività di testing evitandone
l'introduzione. In letteratura sono presenti diversi approcci al problema della progettazione
di attività di testing basate su software fault injection, fra cui il Dependability
Benchmarking Project [13], nato con l'obbiettivo di definire un framework per il
benchmarking di sistemi COTS-based, che possa essere usato, fra l'altro, per la
comparazione della dependability di diverse soluzioni tecnologiche. Ogni approccio,
comunque, si basa su un insieme di concetti, comuni a tutti, che sono descritti nel seguito.
3.1 Obiettivi, definizioni e metriche di valutazione
Gli obiettivi della fault injection riguardano sia la fase di sviluppo che quella operazionale,
in quanto, come già illustrato, questa è una tecnica che può essere applicata nelle diverse
fasi del ciclo di vita di un sistema.
Uno tra gli obiettivi principali, in generale e in particolare in questo caso di studio, è
quello di definire un insieme di fault da iniettare che siano rappresentativi dei fault reali.
Ciò è vero non solo per la software fault injection, ma anche nel caso dell'hardware.
Tuttavia, da un punto di vista logico, i fault hardware sono relativamente più facili da
trattare, perchè spesso si tratta di emulare l'inversione di un bit o la corruzione di un'area
di memoria, che, con le tecniche attuali, non presentano grosse difficoltà. Nel caso del
51
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
software, invece, emulare un fault può comportare analisi più complesse, sia dal punto di
vista del triggering, sia dal punto di vista della natura stessa del fault, per cui il problema
della rappresentatività è molto sentito ed è tuttora oggetto di studio e ricerca. Chillarege et
al. hanno studiato dettagliatamente questo problema, in particolare in uno studio del 1996
[14], affrontandone i diversi aspetti e basandosi su dati raccolti sperimentalmente da
sistemi reali. Analizzare le statistiche sui fault trovati nei sistemi reali, infatti, è necessario
per definire il faultload, ossia l'insieme di fault da iniettare, anche chiamato fault library,
mediante tipologie di fault realistici, rispettando le distribuzioni probabilistiche che questi
presentano nella realtà. Il problema della rappresentatività non riguarda solo il faultload,
ma anche il profilo operazionale del sistema, ossia l'insieme di condizioni operative e di
utilizzo presentate dal sistema testato. Anche in questo caso si può fare riferimento a
statistiche ed analisi per riprodurre, in fase di testing, le opportune condizioni.
E' opportuno notare, tuttavia, che ci sono dei casi in cui la rappresentatività può non essere
un requisito fondamentale. E' possibile, ad esempio, ed è il caso della robustness testing,
che si voglia utilizzare la fault injection per testare semplicemente le reazioni del sistema
in presenza di un arbitrario tipo di fault, a prescindere dalla sua natura e, in questo caso,
non è necessario che i fault iniettati abbiano caratteristiche di rappresentatività.
Il lavoro di Chillarege affronta anche un altro problema che va tenuto in conto nella
progettazione delle campagne di fault injection e riguarda le locazioni in cui iniettare in
guasti. Nei sistemi software complessi, infatti, la modularità delle applicazioni implica la
necessità di contemplare l'iniezione differenziando i diversi moduli. Come constatato in
[15], ad esempio, i moderni sistemi software presentano una percentuale più alta di fault
nei moduli di interfacciamento con l'utente, rispetto ai sistemi più datati.
I risultati statistici di questo lavoro, confrontati con altri lavori che hanno analizzato la
stessa tipologia di sistema [16], hanno una caratteristica di generalità e dimostrano che
l'approccio della definizione del faultload basato su dati reali, quando questi sono presenti,
è una valida soluzione al problema della rappresentatività per la tipologia di sistema in
52
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
esame, che è quella dei sistemi operativi di grosse dimensioni e con caratteristiche di
robustezza.
Un ulteriore problema da considerare nel progetto di attività di fault injection è la durata
degli esperimenti e, conseguentemente, dell'intera campagna sperimentale. Come è noto, i
fault possono richiedere tempi diversi per essere attivati, e degenerare in failure. Il
problema della rappresentatività introduce un ulteriore vincolo sull'attivazione dei fault,
poiché alterare la corretta tempificazione di iniezione può alterare la rappresentatività del
fault.
Il lavoro di Chillarege [14] propone una soluzione a questo problema, che consiste
nell'iniettare gli errori conseguenti ai fault, piuttosto che i fault stessi, in maniera da
accelerarne la catena di propagazione.
Come si è accennato in precedenza, l'attività di iniezione dei fault può essere intrusiva, per
cui un ulteriore obiettivo, in fase di progetto, è minimizzare le alterazioni al sistema da
parte delle tecniche utilizzate. Le diverse alternative, illustrate nel Capitolo 1, presentano
diversi vantaggi e svantaggi, e ognuna può essere preferibile a seconda degli scopi.
In termini formali, una campagna sperimentale di fault injection, intesa come una
sequenza di test, può essere descritta mediante quattro domini:
• F. sono i Fault da iniettare. Consiste nel dominio degli input dell'attività.
• A. è l'insieme delle Attivazioni, ossia delle condizioni che sollecitano il sistema,
favorendo l'attivazione dei fault.
• R. sono i cosiddetti Readout, ossia le osservazioni di determinati comportamenti del
sistema a fronte di una certa iniezione
• M. sono le Misure ricavate dai readout
In ogni esperimento, viene preso in considerazione un elemento dell'insieme F e,
contestualmente, viene definita l'attività che il sistema deve svolgere durante l'esperimento
(scelta di un elemento di A). Come illustrato, tali scelte possono essere fatte sulla base di
considerazioni statistiche. Infine, sulla base di precisi criteri, si raccolgono i risultati
53
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
dell'esperimento, associandoli ad elementi dell'insieme R. Un esperimento, quindi, può
essere visto, in senso lato, come un punto all'interno dello spazio cartesiano FxAxR.
3.1.1 Il caso di studio COFLIGHT
In questo paragrafo viene esemplificato il modo in cui le problematiche generali esposte
nel paragrafo precedente sono state affrontate nello studio del sistema in esame.
Nel caso di studio COFLIGHT, il sistema testato si trova in fase di prototipazione e la
fault injection ha lo scopo di rivelare eventuali falle dei meccanismi di fault tolerance,
testando diversi stati del sistema.
Nel paragrafo 1.3 è stata illustrata la tecnica G-SWFIT introdotta da Madeira in [5] per la
definizione e l'iniezione di faultload rappresentativi per la fault injection in sistemi
software. La tecnica utilizzata nella prima campagna sperimentale di questo caso di studio
[25] non si discosta molto da quella proposta da Madeira: la definizione del faultload è
basata su quel lavoro, utilizzando gli stessi fault operators, peraltro già illustrati
precedentemente in questa trattazione (paragrafo 1.3). Tuttavia la tecnica di iniezione
propriamente detta si differenzia per il momento dell'iniezione: al fine di determinare le
possibili fault location, piuttosto che ispezionare il codice binario nella ricerca di pattern
relativi a sezioni di codice opportune, viene ispezionato direttamente il codice sorgente,
che, una volta determinati i fault, viene modificato mediante i fault operators.
Ovviamente, tale tecnica necessita del codice sorgente dell'applicazione, cosa che non
accade per la tecnica G-SWFIT, particolarmente adatta ai sistemi COTS-based. Entrambe
le tecniche necessitano di un adattamento allo specifico linguaggio di programmazione
con cui è implementato il sistema, tuttavia, la tecnica utilizzata in questo lavoro ha il
vantaggio di superare una serie di problemi di accuratezza e di corrispondenza tra le
modifiche agli eseguibili e i fault che rappresentano, che è un problema di cui soffre
l'approccio G-SWFIT (per approfondimenti si consulti [5]). Questo vantaggio ha il costo
di un aumento del tempo necessario alla generazione delle varianti faulty del sistema,
poiché si rende necessaria una separata ricompilazione del codice per ogni fault da
54
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
iniettare. Il tool proposto in [25] produce a partire dall'input (il codice sorgente) un
insieme di patch relative, ognuna corrispondente ad un fault. In questo lavoro, il codice
che è stato sottoposto a quest'attività è quello del facade, che, come è stato ampiamente
discusso, rappresenta l'entità del sistema che più di tutte necessita di analisi e
approfondimenti. Il processo di compilazione è stato automatizzato mediante script,
prevedendo la raccolta dei diversi eseguibili per un successivo utilizzo. Nel caso in esame,
il tool ha generato 537 patch relative al codice sorgente del facade, rappresentative di
altrettanti fault.
Si noti che quest'approccio risponde, tra l'altro, alla problematica della rappresentatività
dei fault, che è stata illustrata nel paragrafo precedente.
Analogamente, il problema della rappresentatività del profilo operazionale e del necessario
contenimento dei tempi necessari agli esperimenti sono stati affrontati progettando un
opportuno workload che ha, inoltre, un requisito peculiare della tecnica proposta, che è
quello di testare i diversi stati del sistema (si veda, a tal proposito, il paragrafo 2.5).
A tale scopo è stato progettato ed implementato un client che effettuasse una successione
ben precisa e ripetibile di richieste in modo da sollecitare il sistema nella maniera
desiderata. Contestualmente si sono instrumentate le diverse entità del sistema in maniera
da permettere un maggiore controllo sulle transizioni degli stati. In particolare ai normali
parametri di scambio delle funzioni utilizzate nello scambio di messaggi (id del piano di
volo, callback del chiamante, etc.) si è aggiunto un parametro che permettesse al client di
gestire, indirettamente, il tempo di processamento dei processing server, che in questo
prototipo di sistema è un processamento fittizio.
E' il caso di chiarire che, nonostante questa tecnica, non tutte le transizioni sono
direttamente controllabili mediante il workload. In altre parole, con riferimento al modello
del sistema illustrato al paragrafo 2.5, tramite il client è possibile dare luogo ad un
messaggio di tipo CR o CRQ ma i conseguenti messaggi di tipo FR, FRQ, PSC seguono
spontaneamente dal comportamento del sistema. Tuttavia, tramite il parametro introdotto è
55
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
possibile gestire con relativa precisione la tempistica dei messaggi PSC o PSC+ (a meno
di notevoli ritardi nello scambio di messaggi, dovuti – ad esempio – a problemi nella rete).
3.2 Misure e raccolta dati
I readout costituiscono un insieme di informazioni utili a caratterizzare lo stato del
sistema, mediante le quali vengono verificati una serie di predicati, finalizzati ad astrarre
le specifiche del sistema ed in particolare dei meccanismi di tolleranza ai guasti. Esempi di
predicati possono essere “fault attivato”, “fault attivato ed errore rilevato”, “errore rilevato
& servizio fornito correttamente”, etc. Tali predicati possono essere utilizzati per costruire
un modello del comportamento del sistema in presenza di fault, come è il caso
dell'esempio, in forma di grafo, riportato nella figura seguente.
La transizione 1 corrisponde all'evento di attivazione del fault, mentre la transizione 2
contempla il caso, plausibile, in cui un fault non venga attivato in un certo esperimento. La
transizione 3 comporta il rilevamento di un errore, che è, di norma, una transizione
necessaria per poter tollerare il guasto (transizione 6). Tuttavia può accadere (transizione
4) che l'errore non venga rilevato, ma viene tuttavia tollerato. Le transizioni 5 e 7, infine,
sono rappresentative rispettivamente di fallimenti da parte del meccanismo di detection
(così come per la transizione 4) e del meccanismo di fault tolerance. Un grafo di questo
56
Illustrazione 16: Predicati e modello di comportamento
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
tipo può aiutare a modellare la misurazione di determinati parametri d'interesse, nel
momento in cui, sulla base dei dati sperimentali ottenuti, si associano alle transizioni
appena descritte, delle percentuali di occorrenza.
Le misure di dependability di un sistema possono essere classificate in base a diversi
criteri. Si possono infatti avere misure quantitative o qualitative, in quanto, ad esempio,
una campagna di test può essere finalizzata alla sola identificazione di determinate
caratteristiche del sistema, o di sue vulnerabilità, per cui non è necessario ottenere misure
a valori numerici.
Inoltre, le misure possono essere relative alla sola dependability oppure in congiunzione
alle performance del sistema. Un'altra distinzione può essere fatta tra misure comprensive
e misure specifiche: le prime riguardano il sistema nel suo complesso, al livello di servizio
fornito, prendendo in considerazione gli eventi che ne condizionano il comportamento; le
ultime dettagliano particolari aspetti di un sistema, come ad esempio le capacità dei
meccanismi di fault tolerance, la manutenibilità etc.
Ad ogni modo, le misure d'interesse per un certo sistema sono strettamente dipendenti dal
sistema stesso, e in particolare dall'implementazione di questo, incluse le caratteristiche di
controllabilità e osservabilità. Ad esempio, le misure d'interesse relative alla dependability
di un sistema operativo possono includere:
• misure relative alla robustness: un sistema operativo tipicamente interagisce col
sottostante livello hardware, e col soprastante livello middleware o applicativo, per cui
una misura d'interesse può essere il grado di resistenza ad input eccezionali
provenienti da questi livelli
• misure relative alla capacità di rilevamento e coverage, ad esempio di meccanismi per
preservare l'integrità del file system
• misure relative alla capacità di confinare gli errori
• misure temporali, ad esempio i tempi di riavvio, riconfigurazione o di esecuzione di
specifici task
57
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Analogamente, i sistemi transazionali presentano misure di interesse proprie di questa
categoria, fra cui:
• numero di transazioni al minuto in presenza di fault
• numero di transazioni abortite in presenza di fault
• availabity in presenza di fault
• failure modes
Un discorso a parte è necessario per quanto riguarda i sistemi embedded, che esulano da
ciascuna categoria fra quelle affrontate e presentano caratteristiche diverse, e
conseguentemente misure d'interesse differenti, a seconda del sistema in esame. In questo
caso le specifiche del sistema costituiscono il riferimento rispetto al quale è possibile
definire quali siano i parametri da considerare. In ogni caso, infatti, la caratterizzazione di
un sistema deve tenere in conto gli obiettivi dello stesso, ossia, nei termini della
dependability, la conformità del servizio fornito alle specifiche relative. Ad ogni modo,
possono essere individuate delle misure di carattere generale:
• tempo di risposta in presenza di fault: è un parametro generalmente di grossa
rilevanza, in particolar modo per i sistemi real-time, fra i quali rientra il sistema
oggetto di questo lavoro. Assumendo che le caratteristiche temporali del servizio siano
note, si può misurare la degradazione delle performance del sistema in presenza di
fault
• throughput in presenza di fault: è un parametro di notevole importanza, soprattutto nel
caso dei sistemi DRE
• stabilità: rappresenta la misura in cui il failure di uno o più componenti del sistema ne
condizionano il comportamento complessivo
• Failure modes
Questi esempi di misure riguardano un sistema nella maniera in cui il suo comportamento
58
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
può essere percepito dall'esterno. Tuttavia esistono una serie di misure che riguardano
aspetti che possono essere definiti interni al funzionamento del sistema, in particolare
quelle che caratterizzano i meccanismi di tolleranza ai guasti, e con riferimento alla
definizione formale delle campagne sperimentali di fault injection, si possono estrarre dai
readout degli esperimenti.
L'efficienza di un meccanismo di fault tolerance è legata alla relativa coverage e latency
(per le definizioni si veda il Capitolo 1). Si noti che entrambi i tipi di misure possono
richiedere l'instrumentazione del sistema sotto test, ed è appunto il caso del sistema in
esame in questo lavoro. Supposto che si abbiano a disposizione i mezzi necessari per
rilevare gli eventi di attivazione, di detection e di failure, le misurazioni possono essere
condotte con relativa facilità.
Le misure relative alla latency richiedono che, attraverso opportuni mezzi, si abbia la
possibilità di caratterizzare temporalmente gli eventi di iniezione, attivazione e detection.
In ogni caso, l'implementazione di tali misurazioni non può prescindere dallo specifico
sistema, e in particolare dall'implementazione dei meccanismi di detection.
Le misure relative alla coverage comprendono i seguenti fattori:
• fattore di attivazione dei fault: è il rapporto tra il numero di fault iniettati ed il numero
di fault attivati
• fattore di non isolamento degli errori: è la somma degli errori, rilevati e non,
degenerati in failure, divisa per il numero di fault attivati
• fattore di error detection – può essere calcolata in diversi modi, fra cui:
◦ numero di errori rilevati diviso il numero di errori attivati
◦ numero di errori rilevati diviso il numero di fault iniettati
• fattore di error recovery – anche questo può essere calcolato in diversi modi:
◦ numero di errori corretti diviso il numero di fault attivati
◦ numero di errori corretti diviso il numero di fault iniettati
59
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Nella trattazione svolta finora si è supposta la presenza di mezzi per la raccolta dei dati
necessari ai diversi tipi di misurazioni, tuttavia questo punto merita un ulteriore
approfondimento.
Come è facile riconoscere, non solo le informazioni d'interesse variano a seconda del
sistema, ma varia anche il modo in cui queste possono essere raccolte. Generalmente, le
informazioni necessarie per ricavare misure di tipo prestazionale possono essere ottenute
mediante l'osservazione degli output ordinari del sistema. D'altro canto, le informazioni
che riguardano il funzionamento interno del sistema, come i meccanismi di fault tolerance,
o le informazioni relative ai fallimeni, generalmente vengono ottenute mediante una
preventiva instrumentazione del sistema, affinchè questo produca una serie di notifiche
utili, che vengono normalmente immagazzinate su opportuni supporti, o inviate ad entità
interessate.
Questo è un meccanismo noto col nome di logging ed è una pratica diffusa da tempo,
poiché fornisce un supporto notevole in numerose attività, come la diagnosi dei guasti,
l'analisi delle attività svolte e i rispettivi responsabili, analisi di mercato etc.
Tipicamente, una voce di log comprende le seguenti informazioni:
• data ed ora dell'evento
• descrizione dell'evento
• applicazione o modulo di sistema che ha notificato l'evento
• tipologia dell'evento
Ai fini della fault injection, i log sono fondamentali per la comprensione dei risultati degli
esperimenti, e generalmente costituiscono la gran parte dei readout di cui si è discusso
precedentemente.
3.3 Analisi dei dati
Poichè, di norma, i log sono utilizzati per ricavare un insieme di informazioni che è spesso
60
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
notevolmente maggiore di quello necessario per gli scopi della fault injection, solitamente
si rende necessaria una fase di filtraggio dei dati, che produce, come risultato, un insieme
di informazioni essenziali, idoneo ad essere poi sottoposto ad una fase di analisi. Tale fase
di filtraggio è guidata da due criteri fondamentali: cosa va raccolto e come va raccolto.
Entrambi i criteri vanno definiti in dipendenza del sistema in esame e del tipo di analisi
che si intende effettuare sui dati, tuttavia in generale esistono delle problematiche comuni.
Ad esempio, il fatto che un evento comporti una serie di relative notifiche comporta spesso
una ridondanza di informazioni non necessaria per l'analisi dei dati.
Le tecniche di filtraggio principali si basano su due strategie principali:
• blacklist: consiste nel filtrare tutti gli eventi che contengono, nella voce di log, delle
parole chiave presenti nella lista
• whitelist: contrariamente alla strategia precedente gli unici eventi raccolti sono quelli
che contengono, nella voce di log, delle parole chiave presenti nella lista
Un'ulteriore fase di organizzazione dei dati, che precede la fase di analisi, e che spesso è
necessaria, è la fase di coalescenza delle informazioni. Letteralmente, il termine indica
un'attività di raggruppamento di parti divise e, nel caso in esame è appunto il lavoro di
individuare, fra le varie voci di log, quelle che sono accomunate da determinati criteri, in
base ai quali si hanno diversi tipi di coalescenza:
• temporale
• spaziale
• basata sui contenuti
La coalescenza temporale, anche detta tupling, si basa sulla vicinanza temporale degli
eventi: spesso, come anticipato, più voci di log possono indicare la notifica dello stesso
evento. Una tecnica frequentemente adottata consiste nel definire una finestra temporale
per il raggruppamento degli eventi. La coalescenza spaziale è, di norma, utilizzata per i
sistemi distribuiti, ed è utilizzata per raggruppare eventi ravvicinati nel tempo su nodi
61
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
diversi del sistema. Tali eventi possono risultare dalla propagazione delle conseguenze di
altri eventi correlati. La tecnica utilizzata è simile a quella esposta per la coalescenza
temporale ma viene applicata sul concatenamento complessivo dei log di tutti i nodi.
Come illustrato in seguito, questo lavoro si è servito di una tecnica di questo tipo. La
coalescenza basata sui contenuti, invece, si basa sull'osservazione della descrizione degli
eventi ed è spesso usata in congiunzione alle altre due tipologie di coalescenza.
L'analisi dei dati che segue queste attività dipende dal contesto specifico per cui viene
effettuata, e generalmente consiste nello studio statistico dei dati al fine di estrarre
informazioni quantitative sui fault del sistema. Le tipologie di analisi che vengono
effettuate solitamente sono:
• Classificazione dei failure e delle relative operazioni di ripristino
• Distribuzione dei tempi di occorrenza dei failure e delle relative operazioni di
ripristino
• Correlazione tra i failure
• Derivazione e caratterizzazione di modelli di simulazione
La classificazione dei failure è finalizzata a definire i failure in base alla loro natura e/o
alla loro collocazione rispetto al sistema e a produrre statistiche in merito alla gravità dei
failure, il loro impatto sul comportamento del sistema, la coverage dei meccanismi di
recovery del sistema. L'analisi della distribuzione dei tempi di occorrenza dei failure e
delle relative operazioni di ripristino è finalizzata a modellare tali tempi mediante delle
variabili aleatorie continue, come quella esponenziale, lognormale, Weibull. L'analisi di
correlazione tra failure ha lo scopo di rilevare eventuali relazioni tra failure occorsi in
diversi componenti o in diversi nodi e permette di effettuare diagnosi on-line e derivare
tendenze statistiche dei failure correlati. L'analisi volta alla derivazione e popolazione dei
modelli, infine, permette di stimare i valori d'interesse per la modellazione che, come
evidenziato in precedenza, è una tecnica di notevole aiuto nello studio della dependability.
62
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
3.3.1 Il caso di studio COFLIGHT
I readout ottenuti dalla campagna sperimentale in questione consistono in specifiche voci
dei log prodotti dalle diverse entità del sistema. E' opportuno chiarire una sfumatura nel
concetto di readout per questo particolare studio: un certo insieme di voci nei log è stato
analizzato per lo scopo, usuale, di verificare eventi anomali, mentre un altro insieme di
voci consente di risalire ai diversi stati del sistema così come sono stati modellati. La
notifica di tali voci è resa possibile da una preventiva instrumentazione del sistema,
secondo la convenzione esemplificata dall'esempio che segue:
1. Mittente del messaggio
2. Timestamp
3. Simbolo utilizzato per distinguere questo tipo di messaggi all'interno dei log
4. Codice del messaggio (si veda la tabella al paragrafo 2.5)
5. Identificativo del piano di volo a cui si riferisce il messaggio
Simili notifiche sono prodotte dalle seguenti entità:
• facade primario
• facade secondario
• processing server #1, #2, #3
I log che, invece, sono stati considerati per la rilevazione degli eventi d'interesse circa
l'andamento dell'esperimento sono quelli prodotti dalle seguenti entità:
• facade primario
• facade secondario
• client
• Cardamom Platform Supervision Daemon (CdmwDaemon) su ciascuno degli host
Quest'ultima è una facility dei core-services di Cardamom che è presente su ogni host del 63
Facade1 2009/05/06 12:48:18:615:865 [M] [02] [01]
1 2 3 4 5
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
sistema ed ha il compito di monitorare le applicazioni in esecuzione sull'host, risultando
dunque utile per l'analisi dei risultati degli esperimenti.
Le voci dei log utili all'analisi dei risultati sono state estratte mediante operazioni di
filtraggio e sono quelle contenenti le seguenti stringhe (la notazione utilizzata è quella
delle espressioni regolari):
• "Facade1.*created" all'interno del log del CdmwDaemon
• "Facade2.*created"
• "exception.*facade1" all'interno del log CdmwDaemon.log
• "exception.*facade2"
• "switching" nel log facade_server.log del facade secondario
• “Num richieste eseguite con successo” nel log del client
Si noti che, nei file di log, a ciascuna delle voci illustrate sono associate data ed ora
dell'evento. Le misure estratte dalla campagna sperimentale sono il numero di esperimenti
andati a buon fine (o dualmente il numero di test falliti) e la distribuzione di tali dati
rispetto ai diversi stati del sistema. Per ogni test fallito, infatti, è stato determinato l'ultimo
stato in cui si trovava il sistema quando il test è terminato. Sebbene determinare lo stato in
cui si è attivato il guasto potrebbe sembrare un'alternativa migliore, comporterebbe una
complicazione eccessiva dell'attività di analisi, non necessaria agli scopi di questo lavoro.
Per esperimento andato a buon fine si intende, in questo lavoro, un esperimento in cui il
sistema ha servito tutte le richieste che sono state ad esso sottoposte. Analogamente, un
esperimento è definito fallito se il sistema ha servito un numero di richieste minore di
quelle sottoposte.
Inoltre, per ognuno dei test effettuati si è ricavata la sequenza degli stati attraversati, e tale
informazione è stata inserita all'interno di un file associato ad ognuno dei test. Di seguito
si riporta un estratto di un tale file:
01 01 → 00:00:00
64
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
02 01 → 00:01:00
12 01 → 00:01:00
01 01 → 01:01:00
01 01 → 02:01:00
01 01 → 03:01:00
13 01 → 03:01:00
05 01 → 03:00:00
12 02 → 03:00:00
13 02 → 03:00:00
...
65
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Secondo la convenzione utilizzata in questa tipologia di file, a sinistra della freccia sono
presenti i messaggi ricavati dai log, mediante la tecnica e la codifica esposta in
precedenza. A destra della freccia, invece, sono presenti gli stati attraversati. La freccia
indica una potenziale transizione di stato (il nuovo stato è appunto quello a destra della
freccia) dovuta al messaggio relativo.
Queste informazioni permettono di ricavare, per ogni test, quali sono gli stati attraversati e
dunque, anche l'ultimo stato del sistema prima che il fault, eventualmente, degenerasse in
un system failure.
E' stato sviluppato un tool al fine di derivare da questi log, in particolare dai messaggi
introdotti nei log appositamente per questo scopo (si veda il paragrafo 2.5), la sequenza
degli stati attraversati.
Tale tool riceve in ingresso i log dei facade e dei processing server, ne effettua una sorta di
coalescenza spaziale e temporale, ordina i messaggi temporalmente, conservando l'identità
del mittente e, da questa sequenza di messaggi ricava gli stati attraversati, coerentemente
con il modello del sistema illustrato nel Capitolo 2. Il funzionamento del tool è riassunto
nella figura 17.
66
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
67
Illustrazione 17: Tool utilizzato per analizzare i log del sistema
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Con riferimento al grafo in figura 16, che modella il comportamento del sistema in
presenza di fault, i predicati sono:
• Il facade primario è andato in crash (E)
• Il facade secondario è stato attivato (D)
• (1 OR 2) AND 3 AND il sistema ha servito tutte le richieste previste (T)
• 1 AND il facade secondario non è stato attivato (Failure)
• 1 AND 2 AND il sistema non ha servito tutte le richieste previste (Failure)
68
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Capitolo 4: Valutazione sperimentale basata sullo stato
dell'arte
Questo capitolo si propone di descrivere i risultati ottenuti con le diverse campagne
sperimentali effettuate con riferimento alle metodologie e tecniche descritte nel capitolo
precedente. Al fine di comprendere con maggiore dettaglio le vulnerabilità del sistema,
infatti, sono state condotte più campagne, ognuna con una diversa configurazione, per
quanto riguarda le componenti soggette all'iniezione di guasti. Ciò è stato anche dettato
dalla necessità di esplorare il modello a stati del sistema con la maggiore accuratezza
possibile, poiché, come evidenziato in precedenza, la tipologia di fault utilizzata per queste
campagne, insieme con la tipologia di workload utilizzato e la logica di funzionamento
del sistema, spesso complica il controllo diretto delle transizioni fra i diversi stati del
sistema. Le campagne effettuate, descritte nei paragrafi successivi, prevedono le seguenti
configurazioni:
• Facade primario e secondario faulty
• Facade primario faulty con esplorazione del modello a stati in senso orizzontale
• Facade primario faulty con esplorazione del modello a stati in senso verticale
• Facade primario faulty con diversificazione delle richieste
• Un solo processing server faulty
• Tutti i processing server faulty
4.1 Entrambi i facade faulty
Questa campagna sperimentale è stata condotta iniettando i fault sia nel facade primario
che in quello secondario, utilizzando, di volta in volta, lo stesso fault per entrambi.
4.1.1 Copertura degli stati
La figura 18 mostra il comportamento del workload utilizzato in questa campagna in
69
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
merito alla modalità di esplorazione del modello. Le frecce di colore blu corrispondono ad
eventi direttamente controllabili tramite il client, mentre quelle di colore giallo seguono
direttamente dal comportamento del sistema. Infine, le bande di colore verde evidenziano
l'ordine con cui vengono attraversati i diversi stati. Nel seguito, salvo dove diversamente
specificato, questo è il workload utilizzato per le altre campagne.
Ricordando che il sistema è composto da due repliche distinte del facade, si noti che i
fallimenti oggetto di questa discussione, e di quelle relative alle campagne sperimentali
seguenti, sono relativi ad un singolo facade e sono stati dedotti dai fallimenti complessivi,
ossia i casi in cui il sistema ha servito un numero di richieste minore del previsto. La
logica utilizzata per dedurre i fallimenti dei singoli facade consiste nel verificare, se il test
è complessivamente fallito, l'evento di switching della replica secondaria a replica
70
Illustrazione 18: Workload ad esplorazione orizzontale
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
primaria. In caso affermativo, è possibile dedurre che entrambi i facade sono falliti,
perchè, pur essendo stata utilizzata la copia di backup, questa non ha assolto alla fornitura
del servizio in maniera corretta. In caso negativo evidentemente le modalità di fallimento
del facade primario sono tali da precludere l'attivazione del facade secondario che, non
essendo attivato non ha modo produrre un fallimento. Una terza alternativa è il caso in cui
il test è complessivamente riuscito, ossia il numero di richieste servite è pari a quello
previsto, tuttavia si riscontra l'occorrenza dell'evento di switching della replica secondaria
a replica primaria che suggerisce l'occorrenza di un fallimento da parte della copia
primaria. La figura 19 riassume quanto detto.
La figura 20 mostra il modello a stati finiti del sistema in cui sono riportate, per ogni stato
in cui è avvenuto almeno un fallimento, le percentuali di fallimento del facade primario.
71
Illustrazione 19: Classificazione dei test
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
72
Illustrazione 20: Distribuzione dei fallimenti nello scenario con entrambi i Facade faulty
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
In questa campagna sperimentale, costituita da 537 casi di test, sono stati riscontrati 145
fallimenti complessivi. Di questi, 68 casi presentano l'occorrenza di switching, per cui ad
essi corrispondono 136 fallimenti singoli, mentre in 77 casi lo switching non si è
verificato. Infine, in 5 casi, fra i test terminati con successo, si è verificato l'evento di
switching. Complessivamente, quindi, i fallimenti singoli che si sono verificati, del facade
primario e del secondario, sono 218.
Per questa particolare campagna è opportuno tenere presente che, nei casi in cui il test
complessivamente è fallito nonostante lo switching, quest'eventualità può verificarsi sia a
causa dei meccanismi di logging e recovery messi a disposizione della piattaforma, per cui
delle richieste, all'atto del failure del facade primario non vengono inoltrate al secondario,
sia a causa della natura stessa del fault iniettato, identico per entrambi i facade, che
determina la scorrettezza del servizio.
73
Illustrazione 21: Classificazione dei test per lo scenario con entrambi i Facade faulty
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
4.2 Facade primario faulty (esplorazione del modello in senso orizzontale e
verticale)
In questa campagna sono stati utilizzati due scenari differenti dal punto di vista
dell'esplorazione del workload, ma entrambi hanno previsto l'iniezione dei guasti nel solo
facade primario. Nel primo scenario è stato utilizzato il workload di base illustrato in
figura 18, mentre nel secondo scenario è stato utilizzato un workload differente, in grado
di stimolare il sistema ad attraversare il modello in senso verticale, ossia colonna per
colonna, presentato più avanti.
4.2.1 Copertura degli stati (esplorazione in senso orizzontale)
I fallimenti del facade primario sono riportati nella seguente figura, che ne riporta la
distribuzione rispetto agli stati del modello.
74
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
75
Illustrazione 22: Distribuzione dei fallimenti nello scenario con singolo Facade faulty ed esplorazione orizzontale
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
In questa campagna si sono osservati 127 fallimenti complessivi del sistema, di cui 56 con
switching e 71 senza. Inoltre, in 21 casi il test è andato a buon fine grazie all'occorrenza di
switching, per cui, complessivamente si sono osservati 204 fallimenti singoli.
76
Illustrazione 23: Classificazione dei test per lo scenario con singolo Facade faulty ed esplorazione orizzontale
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
4.2.2 Copertura degli stati (esplorazione in senso verticale)
La figura 24 mostra il comportamento del workload utilizzato in questa campagna in
merito alla modalità di esplorazione del modello. Le frecce di colore blu corrispondono ad
eventi direttamente controllabili tramite il client, mentre quelle di colore giallo seguono
direttamente dal comportamento del sistema. Infine, le bande di colore verde evidenziano
l'ordine con cui vengono attraversati i diversi stati.
77
Illustrazione 24: Workload ad esplorazione verticale
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Segue il modello a stati (figura 25), in cui sono evidenziate le percentuali di fallimento del
facade primario per ogni stato.
78
Illustrazione 25: Distribuzione dei fallimenti nello scenario con singolo Facade faulty ed esplorazione verticale
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
In questa campagna si sono osservati 136 fallimenti complessivi del sistema, di cui 74 con
switching e 62 senza. Inoltre, in 6 casi il test è andato a buon fine grazie all'occorrenza di
switching, per cui, complessivamente si sono osservati 216 fallimenti singoli.
79
Illustrazione 26: Classificazione dei test per lo scenario con singolo Facade faulty ed esplorazione verticale
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
4.3 Facade primario faulty (workload con diversificazione delle richieste)
Il workload utilizzato in questa campagna è simile a quello utilizzato per la prima
campagna descritta, almeno per quanto riguarda l'attraversamento degli stati del modello.
Tuttavia si differenzia da quello in quanto presenta una diversificazione delle richieste,
utilizzando richieste di insert e delete oltre che di update. Di seguito si riporta, in dettaglio,
la sequenza di richieste effettuate mediante tale workload.
SSR UPDATE REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:1 DELETE REQUEST , FDP:1 INSERT REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:2 DELETE REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:2 SSR UPDATE REQUEST , FDP:2 INSERT REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:3 SSR UPDATE REQUEST , FDP:2 UPDATE REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:2 SSR UPDATE REQUEST , FDP:2 UPDATE REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:3 SSR UPDATE REQUEST , FDP:2 UPDATE REQUEST , FDP:1 SSR UPDATE REQUEST , FDP:4 SSR UPDATE REQUEST , FDP:3 SSR UPDATE REQUEST , FDP:2 UPDATE REQUEST , FDP:1
80
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
4.3.1 Copertura degli stati
La figura riporta la distribuzione dei fallimenti del facade primario rispetto agli stati del
sistema.
81
Illustrazione 27: Distribuzione dei fallimenti nello scenario con singolo Facade faulty e diversificazione delle richieste
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
In questa campagna di test ci sono stati in tutto 269 fallimenti dei facade. In 176 casi il test
è ritenuto complessivamente fallito (le richieste servite sono minori di quelle previste). In
89 di questi casi è fallito solo il facade primario (non dando la possibilità al meccanismo
di recovery di attivare il facade secondario). Nei restanti 87 casi il facade secondario è
stato attivato ma è fallito ugualmente. In 6 casi il test è andato a buon fine, con fallimento
del solo facade primario. Si fa presente che in alcuni casi il fallimento è avvenuto in uno
stato non previsto dal modello oppure in fase di inizializzazione del facade.
82
Illustrazione 28: Classificazione dei test per lo scenario con singolo Facade faulty e diversificazione delle richieste
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
4.4 Processing server faulty
Sono state condotte due campagne sperimentali mirate a concentrare l'iniezione di fault sui
processing server. La prima ha avuto come target un solo processing server, mentre la
seconda ha previsto l'iniezione di fault in tutti i processing server del sistema. La
metodologia di iniezione è la stessa utilizzata nelle campagne concentrate sul facade e ha
dato luogo a 138 casi di test rappresentativi dell'iniezione di altrettanti fault del processing
server.
In queste campagne sperimentali si è osservato un basso numero di fallimenti del sistema,
identico in entrambi i casi. Di seguito si riportano gli stati finali del sistema nei due casi.
Un processing server faulty
5 03:01:00
2 03:02:00
4 11:03:00
Tutti i processing server faulty
7 00:00:00
4 18:04:00
Nella colonna a sinistra sono riportate le occorrenze di fallimento per gli stati a destra.
Come si nota un numero relativamente alto di fallimenti si è avuto in stati non previsti dal
modello.
Inoltre, nel caso di un solo processing server faulty (caso 1) in nessuno esperimento si è
osservata la mancata istanziazione del server, mentre nel caso di tutti i processing server
faulty (caso 2) ciò è avvenuto in alcuni test. In entrmbi i casi si sono osservati 11
fallimenti complessivi sui 138 casi di test effettuati.
Per quanto riguarda i test falliti del caso 1 è stato osservato che il numero di richieste
servite assume sempre un valore pari a 8 oppure a 18. Ricordando che le richieste
effettuate sono in tutto 22, si è dedotto, con l'ausilio dei log di sistema, che, quando le
richieste servite sono 18, il processing server accetta il processamento di una richiesta
83
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
dopodiché va in crash. Il facade, non ricevendo alcuna conferma per tale richiesta,
conserva lo stato di blocco per l'id del piano di volo in questione causando l'accodamento
indefinito delle altre richieste relative a tali id (in tutto tre). Nei test con 8 richieste servite
si è riscontrato che il server faulty accetta i processamenti richiesti ma non li porta a
termine a causa di un errore nell'interazione con l'ORB. A seguito di quest'errore, tuttavia,
il server non va in crash e si rende disponibile per ulteriori richieste che, comunque,
falliscono similmente. A differenza di quanto detto per il caso 1, i test falliti del caso 2
presentano un numero di richieste servite sempre nullo.
In uno studio precedente sul sistema operativo Tandem GUARDIAN90 [16], è stato
dimostrato che, per quel particolare sistema, la replicazione dei processi è una tecnica
efficace per la software fault tolerance. Nel nostro caso, queste campagne dimostrano che
la replicazione dei processing server non è utile ai fini della software fault tolerance,
almeno per quanto riguarda la tipologia di fault emulati. Infatti, i test falliti nella
campagna con tutti i server faulty sono falliti, seppur con modalità diverse, anche quando
c'era un solo processing server faulty e gli altri fault free. Questo significa che un
fallimento di un solo processing server non è tollerato dal sistema ed è comparabile, dal
punto di vista della correttezza del servizio, al fallimento di tutte le copie.
Queste considerazioni di massima, però, prescindono alcune differenze sostanziali tra i
due sistemi:
per processi replicati in Tandem si intende l'insieme del processore e la copia del sistema
operativo in esecuzione su di esso, mentre nel nostro caso i processi sono istanze diverse
della stessa applicazione in esecuzione su host distribuiti. Inoltre la differenza
fondamentale dal punto della software fault tolerance è che in Tandem la replicazione è
progettata con lo scopo di tollerare i guasti mentre in Coflight, almeno per la versione
attuale, la replicazione è dovuta a motivi di load balancing e availability e non è prevista
una logica di funzionamento in grado di coordinare le copie per tollerare eventuali guasti.
84
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
4.5 Vulnerabilità scoperte
Con il termine vulnerabilità in questo contesto si vuole intendere un difetto nei
meccanismi di fault tolerance che inficia la capacità di tollerare la presenza di guasti nel
resto del sistema. Le campagne sperimentali effettuate in questa fase del lavoro hanno
permesso di rivelare delle vulnerabilità del sistema e le circostanze che permettono di
manifestarle.
I fallimenti di tipo hang non vengono rilevati dal servizio di logging and recovery di
CARDAMOM
Quando un oggetto del sistema fallisce degenerando in uno stato di stallo, di fatto non
fornisce più il servizio previsto. L'infrastruttura di Cardamom preposta al monitoraggio dei
fallimenti delle entità del sistema non percepisce questo tipo di fallimento per cui, a sua
volta, fallisce nell'intento di prendere le opportune contromisure. Un esempio notevole di
ciò è il caso di hang del Facade in corrispondenza del quale il servizio di logging and
recovery non attiva la copia di backup decretando il fallimento della copia primaria. Un
ulteriore esempio di questa vulnerabilità è il caso di hang di un processing server: quando
è in stato di processamento per una certa richiesta e degenera in uno stato di stallo, il
Facade, non essendo notificata tale occorrenza, continua a ritenere che la richiesta sia in
stato di processamento, accodando indefinitamente le richieste relative al piano di volo in
questione.
In caso di crash del Facade primario, le richieste in coda vengono perse
Come illustrato in precedenza, poiché il Facade fa da tramite tra i client e i processing
server, si occupa di accodare le richieste relative ad un piano di volo già in processamento.
Le informazioni circa le richieste inoltrate vengono conservate un'apposita struttura dati di
cui è presente una copia nella replica di backup che viene aggiornata quando modificata.
Tuttavia le richieste accodate non ricevono lo stesso trattamento e, in caso di crash del
85
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
facade primario, quando viene attivata la replica di backup questa non è aggiornata circa la
presenza delle richieste accodate che, di conseguenza, vengono perse, senza alcuna
notifica al client.
86
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Capitolo 5: Iniezione di guasti di concorrenza
Nel capitolo precedente è stato evidenziato che, mediante le metodologie proposte, non si
è in grado di stabilire con precisione la capacità del sistema di tollerare l'occorrenza di un
guasto in dipendenza dello stato in cui si trova. Inoltre, una notevole percentuale di stati
del modello (35%) non è stata testata, come evidenzia la figura 30, in cui gli stati in rosso
sono quelli non testati.
La figura mostra, peraltro, una versione modificata del modello del sistema presentato nel
Capitolo 2. Tale modello, infatti, prevede una semplificazione riguardo alle transizioni fra
gli stati di tipo *:3:+ e quelli di tipo *:3:0. Questa semplificazione consiste nel collassare
nell'unica transizione CR dagli stati *:3:0 a quelli *:3:+ le transizioni CR e FR. Lo stesso
discorso vale per la transizione CR dagli stati *:3:+ agli stessi stati.
La figura 29 esemplifica quanto detto.
87Illustrazione 29: Le semplificazioni rimosse dal modello
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Tali semplificazioni sono state rimosse dal modello in modo tale da renderlo
maggiormente idoneo a supportare la determinazione dei percorsi per l'attivazione dei
conflitti. Di seguito (figura 30) si riporta la versione modificata del modello.
In questo capitolo e nel successivo viene illustrata una metodologia innovativa, nata con lo
scopo di superare questi limiti. A tal fine sono stati valutati diversi lavori presenti i
letteratura, che riguardano i software fault transienti riscontrati in sistemi reali. Da tali
lavori si desume che le cause più frequenti per questo tipo di bug sono:
• errori nella sincronizzazione fra thread
• influenza di eventi esterni nella tempificazione
• gestione errata della memoria
• presenza di fault all'interno di codice utilizzato a sua volta per la gestione
dell'occorrenza di un altro fault
• input anomali
88
Illustrazione 30: Nuova versione del modello a stati finiti
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
• software aging
In questo lavoro sono stati presi in considerazione i guasti riguardanti la concorrenza tra
flussi di esecuzione. La programmazione concorrente, negli ultimi anni, ha subito una
notevole diffusione sia in ambienti ad alte prestazioni, sia nei comuni ambienti desktop.
Questo è dovuto, in particolar modo, alla diffusione altrettanto rapida dell'hardware multi-
core, le cui potenzialità sono sfruttate a pieno solo grazie all'implementazione di flussi di
esecuzione concorrenti. Tuttavia questo tipo di programmazione presenta delle
caratteristiche che la rendono piuttosto complicata rispetto alla programmazione
sequenziale e, per questo motivo, insieme con la sua diffusione, sono aumentate le
occorrenze di bug legati a questa tecnica. Si tratta dei bug di concorrenza, che rientrano
nella categoria dei software fault introdotti in fase di sviluppo (si consulti il Capitolo 1 per
una classificazione dei software fault), e per quanto concerne la tipologia di
manifestazione rientrano frequentemente, per la loro natura, nella classe degli heisenbug.
Le problematiche più frequenti legate alla programmazione concorrente dipendono
dall'uso scorretto di dei lock (è il caso dei deadlock, che si verifica quando, in un gruppo
flussi d'esecuzione, ognuno attende che un altro rilasci una risorsa per proseguire) oppure
dalla mancata sincronizzazione (è il caso dei data race).
Per quello che concerne questo lavoro, un'interessante trattazione sull'argomento è stata
effettuata in [17]. Tale articolo, peraltro di recente pubblicazione (2008), affronta diversi
aspetti legati ai bug di concorrenza trovati nei sistemi reali, e in particolare, si occupa di
definire quali sono le tipologie più frequenti di questi fault sulla base di dati sperimentali.
Più in dettaglio, vengono esaminati i pattern, le manifestazioni e le strategie di risoluzione
dei bug (circa cento) trovati in un vasto spettro di applicazioni open-source, con lo scopo
di fornire delle linee guida per l'attività di bug detection, di testing del software e di
progetto dei linguaggi di programmazione con supporto alla concorrenza.
Di seguito vengono illustrati i risultati di maggiore interesse dal punto di vista di questo
lavoro, in particolar modo per la definizione del modello dei guasti illustrata nel paragrafo
seguente.
89
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
1. di tutti i guasti esaminati, la quasi totalità appartiene alle 3 classi: atomicity-violation
(48.57%), deadlock (29.52%) e order-violation (22.86%) bugs
2. la quasi totalità (96%) dei bug esaminati si manifestano sicuramente se viene viene
imposto un certo ordine parziale tra due soli thread
3. una notevole parte (66%) dei bug non correlati a deadlock riguarda l'accesso
concorrente ad una sola variabile
4. la quasi totalità (92%) dei bug esaminati si manifesta certamente se viene imposto un
certo ordinamento parziale tra non più di quattro accessi in memoria
Il pattern atomicity-violation si riscontra quando, in fase di programmazione, si assume
erroneamente che piccole porzioni di codice vengano eseguite atomicamente, come
nell'esempio seguente, in cui l'interleaving errato, che può verificarsi, è S1 – S3 – S2:
Thread 1 Thread 2S1: if (data_struct->field) ...{ S3: data_struct->field=NULLS2:fputs(data_struct->field,...) ...}
Un ulteriore errore, tipicamente commesso in fase di programmazione è quello di
assumere un ordinamento tra operazioni di thread diversi che, di fatto non viene imposto.
Quest'errore conduce ad un bug pattern di tipo order-violation ed è esemplificato nella
figura che segue.
Thread 1 Thread 2void init(...){ void mMain(...)... {mThread=CreateThread(mMain,...) mState=mThread->State... ...} }
90
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Lo scheduling indesiderato, che si può presentare in questo caso, consiste nell'eseguire
l'accesso in lettura della variabile condivisa mThread da parte del thread 2 prima che
questa sia effettivamente inizializzata dal thread 1 col valore restituito dal metodo
CreateThread.
Si noti che i bug che presentano questo pattern sono diversi da quelli di tipo data-race o di
tipo atomicity-violation: anche se due accessi alla stessa variabile vengono protetti dallo
stesso lock, oppure due porzioni di codice sono atomiche l'una rispetto all'altra, l'ordine di
esecuzione fra queste può essere comunque non garantito.
5.1 Definizione del modello dei guasti
La definizione del modello dei guasti utilizzato in questa fase del lavoro tiene conto dei
risultati 1, 3, 4 del lavoro [17], si per quanto riguarda l'ispezione delle fault location idonee
all'iniezione, sia per quanto riguarda la tipologia di guasto da iniettare. Poiché i bug di tipo
atomicity-violation sono riscontrati con maggiore frequenza, questo lavoro si è concentrato
su questa particolare tipologia di bug, tralasciando quelli di tipo order-violation.
Questo tipo di bug sono legati, il più delle volte, ad un uso scorretto dei lock, e sebbene
questa non sia l'unica causa che induce a tali problemi, è stata identificata come la più
rappresentativa, per cui il fault model proposto in questa campagna prevede la rimozione
di primitive di locking in opportune location al fine di rendere non atomico l'accesso ad
una variabile condivisa da parte di due thread, di cui almeno uno acceda in scrittura.
Emulare una simile tipologia di bug in un sistema reale richiede un'ispezione del codice,
statica o dinamica, che per i sistemi complessi può essere impraticabile poiché bisogna
identificare non solo i possibili accessi a variabili condivise effettuare dalle varie
componenti del sistema, ma anche il tipo di accesso ed i lock acquisiti durante un certo
accesso. Nell'ambito di questa campagna sperimentale sono stati osservati gli accessi
effettuati dall'applicazione, in particolare il facade, sottoposta, in sede operazionale, ad un
workload opportunamente progettato al fine di stimolare il sistema a svolgere determinate
91
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
attività, comunque rappresentative del reale funzionamento, che permettessero di ricoprire,
entro certi limiti, tutto l'insieme dei possibili accessi.
In tali condizioni, una volta instrumentato opportunamente il sistema, è possibile ricavare
le informazioni necessarie all'emulazione del fault model. Più in dettaglio, fissato l'insieme
I degli input considerati, per ogni input i∈ I sono necessarie le seguenti informazioni:
• l'insieme delle variabili sv j accedute in corrispondenza degli input in I: sv j∈SV
• l'insieme degli accessi alla variabile condivisa sv j per ciascun input i: a∈Ai , j
• il tipo di accesso (lettura o scrittura) per ogni accesso a sv j : type(a)=r oppure
type(a)=w
• l'insieme dei lock acquisiti durante l'accesso a: La
E' opportuno notare che alcuni accessi in memoria possono non essere effettuati in tutti i
casi anche a parità di input i (a causa di sorgenti di non determinismo come l'I/O). Per tale
ragione in questa trattazione sono stati considerati unicamente gli accessi effettuati
deterministicamente in corrispondenza di un certo input i∈I . Tali accessi possono
essere determinati reiterando l'esecuzione dell'esperimento.
Di fatto, le fault location sono state individuate mediante un'attenta ispezione manuale del
codice in cerca di elementi sintattici del linguaggio di programmazione relativi alla
sincronizzazione dei thread, in particolare le primitive di locking. Il sistema in esame è
implementato in linguaggio C++, e fa uso delle primitive di supporto al multithreading
offerte da Cardamom (per un approfondimento si consulti [19]). Tali primitive sono basate
sui lock e sulle condition variables, per cui l'ispezione delle fault location, di fatto sezioni
di codice critiche, si è basata sulla ricerca di tali primitive, ed ha riscontrato 13 possibili
locazioni, illustrate di seguito. Le print presenti all'interno delle sezioni riassumono le
variabili accedute dalla sezione ed il tipo di accesso (w – scrittura, r – lettura, rw – lettura
92
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
e scrittura) e sono state utilizzate in fase di osservazione delle sezioni critiche accedute dal
sistema in presenza di un workload generico.
Sezione critica 0
void Facade_impl::sblocca(long fdpid){
...
lockz[index].mutex->lock();
std::cout << "[CS][0][lockz(r), lockz.id(r), lockz.flag(r,w), lockz.mutex(r), lockz.cond(r),
stato(r)]" << std::endl;
lockz[index].flag=false;
recover_stato(index);
lockz[index].cond->broadcast();
lockz[index].mutex->unlock();
Sezione critica 1
void Facade_impl::save_stato(const int index,const char* client_back_id){
...
mutex_stato->lock();
std::cout << "[CS][1][lockz(r), stato(w)]" << std::endl;
stato[index*DIM]=lockz[index].flag;
stato[(index*DIM)+1]=client_back_id[0];
stato[(index*DIM)+2]=client_back_id[1];
stato[(index*DIM)+3]=client_back_id[2];
mutex_stato->unlock();
...
Il metodo save_stato serve ad aggiornare la struttura dati che contiene le informazioni
sulle callback dei client in attesa di conferma di un processamento. Questa struttura dati
93
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
viene trasferita alla copia di backup del Facade quando modificata.
Sezione critica 2
void Facade_impl::save_stato(const int index,const char* client_back_id){
...
mutex_flag->lock();
std::cout << "[CS][2][m_state_changed(w)]" << std::endl;
m_state_changed = true;
mutex_flag->unlock();
Sezione critica 3
void Facade_impl::recover_stato(const int index){
...
mutex_stato->lock();
std::cout << "[CS][3][stato(w)]" << std::endl;
stato[index*DIM]=lockz[index].flag;
stato[(index*DIM)+1]=c;
stato[(index*DIM)+2]=c;
stato[(index*DIM)+3]=c;
mutex_stato->unlock();
Il metodo recover_stato viene utilizzato dalla copia di backup quando viene promossa a
replica primaria e server per ricostruire lo stato del facade primario prima del failure, al
fine di proseguirne la fornitura del servizio in maniera corretta.
94
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Sezione critica 3b
void Facade_impl::recover_stato(const int index){
...
mutex_flag->lock();
std::cout << "[CS][2][m_state_changed(w)]" << std::endl;
m_state_changed = true;
mutex_flag->unlock();
Sezione critica 4
int Facade_impl::blocca(long fdpid){
...
lockz[index].mutex->lock();
std::cout << "[CS][4][lockz(r), lockz.flag(r,w), lockz.mutex(r), lockz.cond(r)]" << ...
while(lockz[index].flag==true){
rw_lock->readUnlock();
lockz[index].cond->wait();
rw_lock->readLock();
}
lockz[index].flag=true;
lockz[index].mutex->unlock();
Il metodo blocca serve per la sincronizzazione tra thread che accedono allo stesso piano di
volo, identificato dal parametro d'ingresso fdpid.
95
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Sezione critica 5
bool Facade_impl::has_state_changed() {
bool flag_state;
mutex_flag->lock();
std::cout << "[CS][5][m_state_changed(r)]" << std::endl;
flag_state=m_state_changed;
mutex_flag->unlock();
return flag_state;
}
Il metodo has_state_changed viene utilizzato dal servizio di logging e recovery di
Cardamom per valutare l'eventualità in cui la copia di backup del Facade vada aggiornata
con le nuove informazioni circa lo stato.
Sezione critica 6
FT::State* Facade_impl::get_state()
throw (FT::NoStateAvailable)
{
FT::State_var s = new FT::State();
mutex_stato->lock();
std::cout << "[CS][6][stato(r)]" << std::endl;
s->length(stato->length());
for(CORBA::ULong i=0;i<stato->length();i++)
s[i]=stato[i];
mutex_stato->unlock();
96
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Il metodo get_state viene utilizzato dal servizio di logging e recovery di Cardamom
insieme con has_state_changed nell'eventualità in cui la copia di backup del Facade vada
aggiornata per recuperare le nuove informazioni circa lo stato.
Sezione critica 7
FT::State* Facade_impl::get_state() throw (FT::NoStateAvailable)
{
...
mutex_flag->lock();
std::cout << "[CS][7][m_state_changed(w)]" << std::endl;
m_state_changed = false;
mutex_flag->unlock();
Sezione critica 8
void Facade_impl::delete_return(const Mockup::FullFDP & fdp) throw(CORBA::SystemException) {
...
rw_lock->writeLock();
std::cout << "[CS][8][lockz(r), lockz.id(r), lockz.flag(w), lockz.callback(w),
lockz.mutex(w), lockz.cond(w)]" << std::endl;
int del_pos = search(fdp.key);
lockz[del_pos].id = 0;
lockz[del_pos].flag = false;
lockz[del_pos].client_back_id = CORBA::string_dup("");
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
delete lockz[del_pos].cond;
97
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
lockz[del_pos].cond = 0;
rw_lock->writeUnlock();
Questa fa parte delle sezioni critiche più delicate in quanto effettua delle operazioni per la
cancellazione di un piano di volo. Per tale motivo è stata utilizzata intensivamente per
produrre i conflitti oggetto di questa campagna sperimentale.
Sezione critica 9
int Facade_impl::blocca(long fdpid){
rw_lock->readLock();
std::cout << "[CS][9][lockz(r), lockz.id(r), lockz.flag(r,w), lockz.mutex(r), ...
int index=search(fdpid);
lockz[index].mutex->lock();
std::cout << "[CS][4][lockz(r), lockz.flag(r,w), lockz.mutex(r), lockz.cond(r)]" << ...
while(lockz[index].flag==true){
rw_lock->readUnlock();
lockz[index].cond->wait();
rw_lock->readLock();
}
lockz[index].flag=true;
lockz[index].mutex->unlock();
rw_lock->readUnlock();
return index;
}
Questa sezione critica include la sezione critica 4 per cui l'insieme delle variabili accedute
da questa include quelle della quarta.
98
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Sezione critica 10
void Facade_impl::sblocca(long fdpid){
rw_lock->writeLock();
std::cout << "[CS][10][index(w), lockz(r), lockz.id(r), lockz.flag(r,w), lockz.mutex(r),
lockz.cond(r), stato(r)]" << std::endl;
int index=search(fdpid);
lockz[index].mutex->lock();
std::cout << "[CS][0][lockz(r), lockz.id(r), lockz.flag(r,w), lockz.mutex(r), lockz.cond(r),
stato(r)]" << std::endl;
lockz[index].flag=false;
recover_stato(index);
lockz[index].cond->broadcast();
lockz[index].mutex->unlock();
rw_lock->writeUnlock();
}
Il metodo sblocca è il duale del metodo blocca ed è utilizzato per coordinare l'accesso
concorrente alla struttura dati contenente le informazioni di lock sui piani di volo. Questa
sezione critica include la sezione critica 0 per cui l'insieme delle variabili accedute da
questa include quelle della quarta.
Sezione critica 11
CdmwFDPSDemo::ClientCB_ptr Facade_impl::get_client_cback(long fdpid) throw(CORBA::SystemException{
...
CdmwFDPSDemo::ClientCB_var client_back_ref;
rw_lock->writeLock();
std::cout << "[CS][11][lockz(r), lockz.callback(r)]" << std::endl;
int index = search(fdpid);
99
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
...
try
{
obj = ni.resolve ((char*)lockz[index].client_back_id);
client_back_ref = CdmwFDPSDemo::ClientCB::_narrow( obj.in() );
}
...
rw_lock->writeUnlock();
Questa sezione critica include un accesso in lettura alla varibile condivisa lockz che
permette di recuperare l'id della callback del client per poter notificare ad esso l'avvenuto
processamento
Sezione critica 12
void Facade_impl::insertFDP(CORBA::Long fdpid,const char* client_back, CORBA::Long sleeptime)
throw(CORBA::SystemException){
...
rw_lock->writeLock();
std::cout << "[CS][12][lockz(r,w), lockz.id(w), lockz.flag(w), lockz.callback(r),
lockz.mutex(w), lockz.cond(w)]" << std::endl;
if(fdpid>(long)lockz.size()){
Lock p;
p.id=fdpid;
p.flag=false;
p.client_back_id=CORBA::string_dup(client_back);
p.mutex = new Cdmw::OsSupport::Mutex::Mutex();
100
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
p.cond = new Cdmw::OsSupport::Condition::Condition(*(p.mutex));
Lock vl;
vl.id=0;
p.flag=false;
int x=0;
else
{
lockz[fdpid-1]=p;
}
...
x=lockz.size()+1;
for(int i=x; i<=fdpid; i++)
lockz.push_back(vl);
lockz[lockz.size()-1]=p;
}
else
{
cout << " MIDDLE\n";
lockz[fdpid-1]=p;
}
rw_lock->writeUnlock();
Questa sezione critica è particolamente delicata perchè include operazioni di allocazione
ed inserimento nella struttura dati per la gestione dei lock dei piani di volo. Per tale motivo
è stata utilizzata intensivamente per produrre i conflitti oggetto di questa campagna
sperimentale.
101
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Sezione critica 13
void Facade_impl::save_stato(const int index,const char* client_back_id){
rw_lock->writeLock();
std::cout << "[CS][13][lockz(r), lockz.callback(w), stato(w)]" << std::endl;
lockz[index].client_back_id=CORBA::string_dup(client_back_id);
rw_lock->writeUnlock();
Rispetto al formalismo introdotto in precedenza, l'insieme delle fault location coincide con
l'insieme La mentre gli accessi a coincidono con le operazioni di lettura e scrittura
effettuati all'interno della sezione critica in questione.
Di seguito viene illustrata la corrispondenza tra l'insieme degli input I e l'esecuzione delle
sezioni critiche a cui danno luogo, causando, di conseguenza i relativi accessi a alle
varibili condivise sv j .
Insert 12 9 4 13 1 2 5 6 7 11 10 0 3 3b 5 6 7 CR FR PSC
SSR Update 9 4 13 1 2 5 6 7 11 10 0 3 3b 5 6 7 CR FR PSC
Update 9 4 13 1 2 5 6 7 11 10 0 3 3b 5 6 7 CR FR PSC
Delete 9 4 13 1 2 5 6 7 11 10 0 3 3b 8 5 6 7 CR FR PSC
102
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
A meno dell'operazione di insert, queste operazioni, effettuate su piani di volo già in
processamento, comportano le stesse sequenze di sezioni critiche viste sopra, in cui però,
ai messaggi CR vanno sostituiti messaggi di tipo CRQ.
Tali sequenze vengono eventualmente interrotte a favore di altre sequenze, relative a
operazioni distinte. Ad es. un'operazione di update accodata comporta che, fra le sezioni
critiche dovute ai messaggi CRQ e FR siano presenti quelle relative al messaggio PSC
dell'operazione in processamento e che ha causato l'accodamento.
La tabella che segue mostra per ogni variabile condivisa sv j , le sezioni critiche da cui è
acceduta e il tipo di accesso. Per semplicità, i singoli accessi presenti in una determinata
sezione critica sono stati raggruppati dando priorità agli accessi in scrittura per definire la
tipologia di accesso risultante.
Sezione critica - tipo di accessostato 0-R 1-W 3-W 6-R 10-Rm_state_changed 2-W 3b-W 5-R 7-W 0-W lockz 0-R 1-R 3-R 4-R 8-R 9-R 10-R 11-R 12-RW 13-Rlockz-id 8-R 9-R 10-R 12-Wlockz-flag 0-W 4-RW 8-W 9-RW 10-RW 12-W lockz-callback 8-W 11-R 12-W 13-W lockz-mutex 0-R 4-R 8-W 9-R 10-R 12-W lockz-cond 0-R 4-R 8-W 9-R 10-R 12-W
Tali informazioni sono state sottoposte all'elaborazione di un tool appositamente
implementato per determinare tutti i possibili conflitti tra due sezioni critiche (o,
equivalentemente, tra due thread) su una variabile o su variabili multiple, membri di una
stessa struttura dati. Di seguito viene riportato l'output di tale tool, che evidenzia la
variabile oggetto del conflitto, le sezioni critiche in conflitto e la modalità di accesso alla
variabile (R: lettura, W: scrittura, RW: lettura e scrittura).
103
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
lockz (1-R) <-- (12-RW) lockz (3-R) <-- (12-RW) lockz (6-R) <-- (12-RW) lockz (8-R) <-- (12-RW) lockz (9-R) <-- (12-RW) lockz (10-R) <-- (12-RW) lockz (11-R) <-- (12-RW) lockz (12-RW) <-- (12-RW) lockz (13-R) <-- (12-RW) lockz-callback (8-W) <-- (8-W) lockz-callback (8-W) <-- (12-W) lockz-callback (8-W) <-- (13-W) lockz-callback (11-R) <-- (8-W) lockz-callback (11-R) <-- (12-W) lockz-callback (11-R) <-- (13-W) lockz-callback (12-W) <-- (8-W) lockz-callback (12-W) <-- (12-W) lockz-callback (12-W) <-- (13-W) lockz-callback (13-W) <-- (8-W) lockz-callback (13-W) <-- (12-W) lockz-callback (13-W) <-- (13-W) lockz-cond (8-W) <-- (8-W) lockz-cond (8-W) <-- (12-W) lockz-cond (9-R) <-- (8-W) lockz-cond (9-R) <-- (12-W) lockz-cond (10-R) <-- (8-W) lockz-cond (10-R) <-- (12-W) lockz-cond (12-W) <-- (8-W) lockz-cond (12-W) <-- (12-W) lockz-flag (8-W) <-- (8-W) lockz-flag (8-W) <-- (9-RW) lockz-flag (8-W) <-- (10-RW) lockz-flag (8-W) <-- (12-W) lockz-flag (9-RW) <-- (4-RW) lockz-flag (9-RW) <-- (8-W) lockz-flag (9-RW) <-- (9-RW) lockz-flag (9-RW) <-- (10-RW) lockz-flag (9-RW) <-- (12-W) lockz-flag (10-RW) <-- (0-W) lockz-flag (10-RW) <-- (8-W) lockz-flag (10-RW) <-- (9-RW) lockz-flag (10-RW) <-- (10-RW) lockz-flag (10-RW) <-- (12-W) lockz-flag (12-W) <-- (8-W)
lockz-flag (12-W) <-- (9-RW) lockz-flag (12-W) <-- (10-RW) lockz-flag (12-W) <-- (12-W) lockz-id (8-R) <-- (12-W) lockz-id (9-R) <-- (12-W) lockz-id (10-R) <-- (12-W) lockz-id (12-W) <-- (12-W) lockz-mutex (8-W) <-- (8-W) lockz-mutex (8-W) <-- (12-W) lockz-mutex (9-R) <-- (8-W) lockz-mutex (9-R) <-- (12-W) lockz-mutex (10-R) <-- (8-W) lockz-mutex (10-R) <-- (12-W) lockz-mutex (12-W) <-- (8-W) lockz-mutex (12-W) <-- (12-W) m_state_changed (2-W) <-- (2-W) m_state_changed (2-W) <-- (3b-W) m_state_changed (2-W) <-- (7-W) m_state_changed (2-W) <-- (0-W) m_state_changed (3b-W) <-- (2-W) m_state_changed (3b-W) <-- (3b-W) m_state_changed (3b-W) <-- (7-W) m_state_changed (3b-W) <-- (0-W) m_state_changed (5-R) <-- (2-W) m_state_changed (5-R) <-- (3b-W) m_state_changed (5-R) <-- (7-W) m_state_changed (5-R) <-- (0-W) m_state_changed (7-W) <-- (2-W) m_state_changed (7-W) <-- (3b-W) m_state_changed (7-W) <-- (7-W) m_state_changed (7-W) <-- (0-W) m_state_changed (0-W) <-- (2-W) m_state_changed (0-W) <-- (3b-W) m_state_changed (0-W) <-- (7-W) m_state_changed (0-W) <-- (0-W) stato (1-W) <-- (1-W) stato (1-W) <-- (3-W) stato (3-W) <-- (1-W) stato (3-W) <-- (3-W) stato (6-R) <-- (1-W) stato (6-R) <-- (3-W) stato (10-R) <-- (1-W) stato (10-R) <-- (3-W)
L'occorrenza di un conflitto di accessi ad una variabile può avere conseguenze diverse a
seconda della specifica funzione della variabile stessa:
• lockz: un conflitto su tale variabile è un esempio di conflitto a variabili multiple
poiché è una struttura dati incaricata di conservare lo stato di processamento delle
104
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
richieste sui piani di volo inoltrate ai processing server ed formata dai seguenti campi
◦ id: costituisce l'identificativo del piano di volo a cui si riferisce la richiesta – un
conflitto su questa variabile può produrre un errore di incoerenza dei dati o un
accesso in memoria non valido.
◦ callback: costituisce un identificativo del client che ha effettuato la richiesta,
necessaria per notificarne ad esso il completamento – anche in questo caso, un
conflitto su questa variabile può produrre un errore di incoerenza dei dati o un
accesso in memoria non valido.
◦ mutex: come indicato dal nome, serve per disciplinare l'accesso alla variabile lockz
- un conflitto su questa variabile può produrre un accesso in memoria non valido.
◦ cond: si tratta di una variabile condition, anch'essa usata per gestire la concorrenza
degli accessi alla variabile lockz – un conflitto su questa variabile può produrre un
accesso in memoria non valido.
◦ flag: è utilizzata da un meccanismo di controllo della concorrenza specifico del
sistema in esame – un conflitto su tale variabile può produrre un errore di
incoerenza dei dati o un accesso in memoria non valido.
Si noti che la struttura dati che contiene lo stato di processamento è in realtà un
array di elementi di tipo lockz, ognuno relativo ad uno specifico piano di volo.
• m_state_changed: si tratta di una variabile utilizzata per segnalare al meccanismo di
recovery di Cardamom il cambiamento dello stato di una copia primaria replicata, in
questo caso il facade – un conflitto su tale variabile può produrre un errore di
incoerenza dei dati, in particolare il mancato aggiornamento dello stato da parte della
copia di backup
• stato: contiene le informazioni circa lo stato del facade - un conflitto su tale variabile
può produrre un errore di incoerenza dei dati, in particolare l'aggiornamento di uno
stato errato da parte della copia di backup
105
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Fra i conflitti trovati, sono stati analizzati in maggior dettaglio quelli relativi alle sezioni
critiche 12 ed 8, in quanto i conflitti a cui sono suscettibili hanno conseguenze più gravi
dal punto di vista dei requisiti del sistema e sono più facilmente osservabili. Di questi,
sono stati utilizzati solo 4 conflitti, che hanno, fra l'altro, la caratteristica di dare luogo a
crash del facade, per cui dal punto di vista dell'osservabilità dei risultati degli esperimenti
sono particolarmente utili e sono descritti di seguito.
CONFLICT_1
Variabile condivisa: lockz-cond
Sezioni critiche in conflitto: (9-R) <-- (12-W)
Operazioni in conflitto:
12: lockz.push_back(...)
12: lockz[lockz.size()-1] = p OR lockz[fdpid-1] = p
9: lockz[index].cond->wait()
Questo conflitto è stato attivato rimuovendo le primitive di lock per l'accesso alla sezione
critica 12. Il workload utilizzato effettua una richiesta di update sul piano di volo 1 e, a
breve distanza di tempo, un'ulteriore richiesta di update per lo stesso piano di volo seguita
da una operazione di insert ancora per lo stesso piano di volo.
L'interleaving che ha portato al system failure riscontrato (crash del Facade) è stato
riprodotto con gbd mediante un breakpoint appena prima dell'istruzione
lockz[index].cond->wait();
Interrompendo l'esecuzione della sezione critica 9 in quel punto e facendo schedulare la
s.c. 12 che esegue
lockz[fdpid-1] = p
si attiva un errore in seguito all'esecuzione della sezione critica 9 precedentemente
interrotta. Tale errore comporta un crash del facade nel momento in cui, all'interno della
chiamata wait() si di chiamare il metodo set_unlock() su un lock non attivato.
106
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
CONFLICT_2
Variabile condivisa: lockz-cond
Sezioni critiche in conflitto: (10-R) <-- (8-W)
Operazioni in conflitto:
10:lockz[index].cond->broadcast();
8:delete lockz[del_pos].cond;
8:lockz[del_pos].cond = 0;
Questo conflitto è stato attuato rimuovendo le primitive di lock() e unlock() dalla sezione
critica 8. Il workload utilizzato effettua una richiesta di delete sul piano di volo 1 e, a
breve distanza di tempo, una richiesta di update per lo stesso piano di volo. L'interleaving
che ha portato al system failure (crash del facade) riscontrato è stato riprodotto con gbd
mediante un breakpoint appena prima dell'istruzione
lockz[index].cond->broadcast();
Interrompendo l'esecuzione della sezione critica 10 in quel punto e facendo schedulare la
s.c. 8 che esegue
delete lockz[del_pos].cond;
lockz[del_pos].cond = 0;
si attiva un errore in seguito all'esecuzione della sezione critica 10 precedentemente
interrotta, che porta ad un accesso in memoria non valido, causando il crash del facade.
CONFLICT_3
Variabile condivisa: lockz-mutex
Sezioni critiche in conflitto: (9-R) <-- (8-W)
Operazioni in conflitto:
9:lockz[index].mutex->lock();
8:delete lockz[del_pos].mutex;
8:lockz[del_pos].mutex = 0;
107
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Questo conflitto è stato attuato rimuovendo le primitive di lock() e unlock() dalla sezione
critica 9. Il workload utilizzato effettua una richiesta di update sul piano di volo 1 e, a
breve distanza di tempo, una richiesta di delete per lo stesso piano di volo. L'interleaving
che ha portato al system failure (crash del facade) riscontrato è stato riprodotto con gbd
mediante un breakpoint appena prima dell'istruzione
lockz[index].mutex->lock();
Interrompendo l'esecuzione della sezione critica 9 in quel punto e facendo schedulare la
s.c. 8 che esegue
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
si attiva un errore in seguito all'esecuzione della sezione critica 9 precedentemente
interrotta, che porta ad un accesso in memoria non valido, causando il crash del facade.
Non è possibile attuare lo stesso conflitto rimuovendo le primitive di lock() e unlock()
dalla sezione critica 8 in quanto, al momento del breakpoint all'istruzione
lockz[index].mutex->lock();
il relativo thread ha acquisito il lock in lettura di rw_lock, per cui, nella delete_return() la
chiamata a sblocca, che richiede l'acquisizione dello stesso lock in scrittura, resta in attesa
indefinita.
CONFLICT_4
Variabile condivisa: lockz-mutex
Sezioni critiche in conflitto: (10-R) <-- (8-W)
Operazioni in conflitto:
10:lockz[index].mutex->lock();
8:delete lockz[del_pos].mutex;
8:lockz[del_pos].mutex = 0;
108
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Questo conflitto è stato attuato rimuovendo le primitive di lock() e unlock() dalla sezione
critica 8. Il workload utilizzato effettua una richiesta di delete sul piano di volo 1 e, a
breve distanza di tempo, una richiesta di update per lo stesso piano di volo. L'interleaving
che ha portato al system failure (crash del facade) riscontrato è stato riprodotto con gbd
mediante un breakpoint appena prima dell'istruzione
lockz[index].mutex->lock();
Interrompendo l'esecuzione della sezione critica 10 in quel punto e facendo schedulare la
s.c. 8 che esegue
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
si attiva un errore in seguito all'esecuzione della sezione critica 10 precedentemente
interrotta, che porta ad un accesso in memoria non valido, causando il crash del facade.
Oltre a questi quattro conflitti che sono stati scelti per la campagna sperimentale sono stati
rilevati numerosi altri conflitti, alcuni dei quali non attivabili a causa di subordinazione ad
altri lock, altri attivabili e rivelatisi benigni, ossia l'errore a cui danno luogo viene
immediatamente corretto o non ha modo di propagarsi, altri ancora causano dei cosiddetti
content failure, ossia alterano il valore corretto delle informazioni. Di seguito si riportano,
a titolo di esempio, alcuni di tali conflitti.
CONFLICT
Variabile condivisa: lockz-callback
Sezioni critiche in conflitto: (11-R) <-- (8-W)
Questo conflitto non è stato attuato perchè, anche rimuovendo le primitive di lock() e
unlock() dalla sezione critica 8, questa non viene acceduta in quanto è protetta dalle
chiamate a blocca() e sblocca() , che sono primitive di sincronizzazione definite dagli
sviluppatori di questo particolare sistema.
109
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
CONFLICT
Variabile condivisa: lockz-callback
Sezioni critiche in conflitto: (12-W) <-- (8-W)
Questo conflitto è stato attuato rimuovendo le primitive di lock() e unlock() dalla sezione
critica 12. Il workload utilizzato effettua una richiesta di delete per il piano di volo 1 e, a
breve distanza di tempo, una richiesta di insert per lo stesso piano di volo. L'interleaving
proposto consiste nell'eseguire prima le eventuali operazioni di preparazione
all'inserimento della sezione critica 12:
lockz.push_back(vl);
poi le operazioni di cancellazione e deallocazione della sezione critica 8:
lockz[del_pos].id = 0;
lockz[del_pos].flag = false;
lockz[del_pos].client_back_id = CORBA::string_dup("");
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
delete lockz[del_pos].cond;
lockz[del_pos].cond = 0;
ed infine l'inserimento effettivo del piano di volo all'interno della struttura dati prevista:
lockz[lockz.size()-1]=p; oppure lockz[fdpid-1]=p;
Questo conflitto si è rivelato benigno, ossia, sebbene la cancellazione nella sezione critica
8 sia in conflitto con la sezione critica 12, gli effetti di questa vengono sovrascritti con i
valori finali corretti.
CONFLICT
Variabile condivisa: lockz-cond
Sezioni critiche in conflitto: (8-W) <-- (8-W)
110
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Questo conflitto è stato attuato rimuovendo le primitive di lock() e unlock() per l'accesso
alla sezione critica 8. Il workload utilizzato effettua due richieste di delete per lo stesso
piano di volo in un breve intervallo di tempo. L'interleaving che ha comportato il conflitto
è stato riprodotto con gdb mediante un breakpoint prima delle istruzioni
lockz[del_pos].id = 0;
lockz[del_pos].flag = false;
lockz[del_pos].client_back_id = CORBA::string_dup("");
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
delete lockz[del_pos].cond;
lockz[del_pos].cond = 0;
Facadendo eseguire questo blocco di istruzioni prima ad un thread e poi all'altro, dopo che
entrambi hanno impostato lo stesso valore per del_pos, il conflitto si è rivelato di tipo
benigno, nel senso che, alla fine dell'esecuzione, lo stato rimane corretto.
CONFLICT
Variabile condivisa: lockz
Sezioni critiche in conflitto: (1-R) <-- (12-RW)
12: lockz.push_back(...)
12: lockz[lockz.size()-1] = p OR lockz[fdpid-1] = p
1:stato[...] = lockz[index].flag
Questo conflitto è stato attivato rimuovendo le primitive di lock per l'accesso alla sezione
critica 12. Il workload utilizzato effettua una richiesta di update sul piano di volo 1 e, a
breve distanza di tempo, una richiesta di insert per lo stesso piano di volo. L'interleaving
che ha portato al content failure riscontrato è stato riprodotto con gbd mediante un
breakpoint appena prima dell'istruzione
111
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
stato[...] = lockz[index].flag
Interrompendo l'esecuzione della sezione critica 1 in quel punto e facendo schedulare la
s.c. 12 che esegue
lockz[fdpid-1] = p
si attiva un errore in seguito all'esecuzione della sezione critica 1 precedentemente
interrotta. Quest'errore degenera in un failure quando lo stato erroneo viene passato al
meccanismo di recovery per aggiornare la copia di backup del Facade .
CONFLICT
Variabile condivisa: lockz-cond
Sezioni critiche in conflitto: (10-R) <-- (12-W)
12: lockz.push_back(...)
12: lockz[lockz.size()-1] = p OR lockz[fdpid-1] = p
10: lockz[index].cond->broadcast();
Questo conflitto è stato attivato rimuovendo le primitive di lock per l'accesso alla sezione
critica 12. Il workload utilizzato effettua una richiesta di update sul piano di volo 1 e, a
breve distanza di tempo, un'ulteriore richiesta di update per lo stesso piano di volo seguita
da una operazione di insert ancora per lo stesso piano di volo. L'interleaving che ha portato
al failure riscontrato è stato riprodotto con gbd mediante un breakpoint appena prima
dell'istruzione
lockz[index].cond->broadcast();
Interrompendo l'esecuzione della sezione critica 10 in quel punto e facendo schedulare la
s.c. 12 che esegue
lockz[fdpid-1] = p
si attiva un errore in seguito all'esecuzione della sezione critica 10 precedentemente
interrotta. La chiamata broadcast() viene eseguita su una variabile 'cond' diversa da quella
prevista, per cui i thread in attesa sulla variabile 'cond' corretta non ricevono la
112
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
segnalazione di broadcast.
Per concludere, la figura seguente 31 il processo di definizione del fault model.
5.2 Iniezione dei guasti
Con riferimento al formalismo introdotto nel paragrafo precedente, la fault injection
consiste nei seguenti passi:
1. Considerare ogni coppia di sezioni critiche s' ed s'' , contenenti rispettivamente i due
accessi a' e a'' con type(a') = w e type(a'') = r
2. ossia i conflitti illustrati nel paragrafo precedente
3. Considerare l’insieme di lock L da cui le sezioni s' ed s'' sono protette, ossia
L=Ls '∩L s ' '
4. Rimuovere l’acquisizione dei lock L (e il relativo rilascio)
In merito al terzo punto, la rimozione delle operazioni di acquisizione e rilascio dei lock
può essere effettuata o staticamente, ricompilando il codice privato di tali operazioni,
oppure dinamicamente, ad esempio mediante un debugger. Dati gli scopi di questo lavoro,
è stata adottata la prima alternativa. Sempre in merito allo stesso punto, si è già accennato
in precedenza che, per far collidere due sezioni critiche, è sufficiente rimuovere le
primitive di lock ad una sola delle due. Tuttavia, la rimozione in una sezione può avere
effetti diversi se applicata all'altra sezione.113
Illustrazione 31: Processo di definizione del fault model
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Quale tra le due sezioni deve essere soggetta alla rimozione non è un dato generale e va
determinato in dipendenza del caso specifico, come è stato fatto in questo lavoro.
A scopo esemplificativo si riportano, di seguito, alcuni estratti di codice contenenti sezioni
critiche, in cui è evidenziata la rimozione delle primitive di sincronizzazione.
Quest'esempio mostra la rimozione delle primitive di sincronizzazione da una sezione di
codice che, per inserire un piano di volo all'interno dell'apposita struttura dati lockz,
variabile condivisa, necessita di essere protetta.
114
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Questo, invece, è un estratto di codice responsabile della cancellazione di un piano di volo,
operazione particolarmente delicata dal punto di vista della sincronizzazione, in cui sono
state rimosse le primitive di lock e unlock.
5.3 Attivazione dei guasti
Il risultato 2 del lavoro [17], illustrato precedentemente, dimostra che, sebbene in molti
casi, specialmente nei sistemi complessi, il numero di thread sia molto elevato, la
manifestazione di un bug di concorrenza, nella maggior parte dei casi, coinvolge solo due
thread. Ciò è dovuto al fatto che la maggior parte dei thread non interagisce strettamente
con molti altri e la maggior parte della comunicazione e collaborazione viene condotta tra
due thread, o comunque tra un piccolo gruppo di thread. Quanto detto non contraddice
l'osservazione, comunemente accettata, secondo cui i bug sulla concorrenza si manifestano
più facilmente in condizioni di workload notevolmente impegnativi. Anzi, un tale
workload aumenta appunto la probabilità che si verifichino determinati ordinamenti tra
due thread che possono determinare la manifestazione dei bug.
La fase di attivazione dei guasti effettuata nella campagna sperimentale oggetto di questo
115
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
capitolo e del seguente si basa fortemente su questo risultato, oltre che sul risultato 5. La
metodologia utilizzata, infatti, per raccogliere un insieme di possibili fault sulla
concorrenza prevede che, una volta individuata una fault location, ed iniettato un fault, il
sistema viene esercitato in modo che si verifichi l'esecuzione concorrente di due soli
thread, o più in dettaglio di due sezioni critiche, che opportunamente schedulate
conducono all'attivazione del fault. In questo modo il processo di attivazione è
notevolmente semplificato in quanto, con soli due thread, ci si può concentrare sulla
corretta schedulazione senza interessarsi di ciò che avviene per gli altri thread.
In questa campagna sperimentale vi sono determinati requisiti per ciò che riguarda
l'attivazione dei fault, e sono i seguenti:
1. la manifestazione deve essere transiente: lo scheduling dei thread deve causare
l'interferenza tra gli accessi in memoria, preventivamente privati delle primitive di
sincronizzazione, solo in particolari occasioni, in maniera controllabile
2. deve essere possibile decidere lo stato in cui attivare il guasto
3. deve essere possibile fornire al sistema gli input necessari ad indurre l'esecuzione di
thread
◦ responsabili della collisione
◦ responsabili di far transitare il sistema in un determinato stato
In merito al primo punto, si noti che, una volta rimosse le primitive di sincronizzazione,
non è possibile garantire che si abbiano interferenze solo quando si interviene
appositamente sullo scheduling. Tuttavia, date le dinamiche del sistema e la rapidità con
cui le operazioni vengono effettuate la probabilità che ciò accada è molto bassa e questa
eventualità è stata comunque monitorata durante gli esperimenti, essendo stati, questi,
condotti manualmente, come descritto più avanti in questo capitolo.
Per quanto riguarda i restanti requisiti, si è mostrato nei capitoli precedenti che mediante
116
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
un opportuno workload è possibile far transitare il sistema negli stati desiderati a meno di
pochi stati che fanno eccezione, comunque controllabili indirettamente. In questo modo è
possibile, almeno in linea di principio, imporre lo scheduling per l'attivazione del guasto in
qualsivoglia stato. Il requisito 3 impone un ulteriore vincolo a questo discorso, richiedendo
che sia possibile attivare i thread che poi, in un preciso stato e opportunamente schedulati,
vadano in conflitto.
Il rispetto di tale vincolo dipende dal particolare stato in questione e di ciò si è tenuto
conto nella formulazione della tecnica di input selection descritta in seguito.
Per determinare gli input da fornire al sistema al fine di poter controllare gli stati
attraversati, in particolare quello in cui attivare il guasto, e indurre l'esecuzione di sezioni
critiche in conflitto è stata ideata una tecnica basata sulla formalizzazione del modello che,
mediante un opportuno algoritmo, fornisce le informazioni per determinare, per ogni stato,
quali conflitti possono essere attuati, gli input da fornire per attuarli e i percorsi nel
modello che, a partire dagli input, pervengono allo stato target, ossia quello in cui si vuole
attivare il guasto.
Ai suddetti percorsi si richiedono le seguenti proprietà:
• il percorso deve terminare nello stato in cui si vuole attivare il fault
• deve includere le transizioni che comportano l'esecuzione delle due sezioni critiche
desiderate per la collisione
• deve poter essere attraversato completamente anche sospendendo una delle sezioni
critiche
• deve essere percorribile deterministicamente attraverso gli input che è possibile fornire
Formalmente, l'algoritmo proposto riceve in input le seguenti informazioni:
• stato target
• messaggio finale: è il messaggio che fa transitare il sistema nello stato target e, inoltre,
attiva l'esecuzione, fra le altre, di una della due sezioni critiche desiderate
117
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
• messaggio intermedio: è il messaggio che attiva l'esecuzione dell'altra sezione critica
desiderata
Fissato un certo stato, l'algoritmo verifica l'esistenza di percorsi per ognuno dei quattro
conflitti scelti e, in caso positivo, fornisce gli input da fornire e lo stato di partenza del
percorso (i parametri di I/O sono spiegati in dettaglio nel paragrafo seguente).
Tramite il workload si induce il sistema a transitare in tale stato di partenza e si producono
gli input richiesti mentre attraverso un debugger (in questo caso Gdb, illustrato in seguito),
una volta che il sistema è giunto nello stato target, si forza l'interleaving delle operazioni
che portano al conflitto.
Fissato un certo concurrency fault, a cui corrispondono due sezioni critiche ben definite,
con un preciso interleaving, è dunque possibile verificare una delle tre seguenti
eventualità, per ogni stato:
• non esiste un percorso valido (che rispetta i requisiti esposti)
• esiste un percorso valido, ma non si riesce a forzarlo entro un timeout (ad es. a causa
di vincoli di ordinamento "opponenti")
• esiste un percorso valido e l'iniezione va a buon fine
Nell'ultima eventualità, può accadere che il failure avvenga in uno stato diverso dallo stato
target.
5.4 Implementazione
In questo paragrafo vengono illustrati alcuni dettagli implementativi riguardo alla
campagna sperimentale in questione. In particolare, viene discusso l'algoritmo per la
determinazione dei percorsi utili all'attivazione dei conflitti e la tecnica utilizzata per
imporre lo scheduling desiderato alle operazioni dei thread in conflitto.
118
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
5.4.1 Algoritmo per la determinazione dei percorsi
Per verificare se in un determinato stato è possibile attivare un certo conflitto è stato ideato
un algoritmo che, procedendo a ritroso dallo stato target, determina due percorsi parziali.
Il primo percorso parziale termina nello stato target x tramite il messaggio finale y (che
comporta l'esecuzione dell'accesso a' - per una definizione si veda il paragrafo precedente)
partendo da uno stato z in cui sia possibile fornire, tramite un client, un certo messaggio di
input (CR o CRQ).
Sia w il messaggio intermedio (che comporta l'accesso a''), le cui operazioni relative, in
fase di scheduling, si devono “sospendere”. Il secondo percorso parziale termina in z
attraverso w partendo da uno stato s in cui sia possibile fornire, tramite un client, un certo
messaggio di input.
Determinati questi percorsi parziali, attraverso il workload
• si induce il sistema a transitare nello stato s
• si controllano gli input e lo scheduling in modo da far transitare il sistema in z tramite
w
• si sospende l'esecuzione delle operazioni relative a w
• si controllano gli input e lo scheduling in modo da far transitare il sistema in x tramite
y
Un esempio di tale percorso e fornito in Figura 32, la quale include un estratto dalla FSM
di Figura 30. Supponiamo di voler iniettare un guasto nello stato 2:1:0 tra l’accesso X,w,1
(variabile condivisa X, accesso in scrittura, n° 1) a seguito del messaggio PSC e l’accesso
X,r,2 (variabile condivisa X, accesso in lettura, n° 2) a seguito del messaggio CRQ.
L’algoritmo, partendo dallo stato 2:1:0, trova prima un percorso (first backward path) che
termini in quello stato con l’arco PSC, e che come primo arco contenga un messaggio di
tipo CR o CRQ (i quali corrispondono agli input che il tester può inviare). Nell’esempio,
tale percorso e dato dalla sequenza 2:1:0 → CR → 2:1:0 → FR → 2:2:0 → PSC → 2:1:0.
A partire dal primo stato del percorso appena trovato (nell’esempio, coincide con 2:1:0),
119
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
trova un percorso all’indietro che termini con l’arco CRQ, e che inizi con un arco CR o
CRQ. In questo caso, tale percorso (second o backward path) è costituito da 1:1:0 → CRQ
→ 2:1:0.
Per attivare il guasto utilizzando il percorso in Figura 32, occorre inviare al sistema delle
richieste CR e CRQ secondo la tempificazione mostrata in Figura 33. Inizialmente il
sistema è nello stato 1:1:0 (tale stato può essere raggiunto tramite lo stesso workload
discusso nel paragrafo 3.1.1). Viene dapprima inviata una richiesta di update per un piano
di volo già accodato, per servire la quale viene istanziato il thread 1. Il sistema transita
nello stato 1:1:0 tramite il second backward path. Quando il thread 1 accede alla sezione
critica relativa all'accesso a'' = X,w,1, esso viene bloccato tramite breakpoint.
Successivamente viene inviata una richiesta di tipo insert, a seguito della quale verrà
istanziato il thread 2, ed il sistema transita nello stato 2:1:0 tramite il first backward path.
Infine, dopo che il thread 1 avrà effettuato l'accesso a' = X,r,2, il thread 1 viene sbloccato
per effettuare l'accesso a''. In tal modo, si attiva l'interferenza tra i due thread (in questo
esempio, il valore viene sovrascritto prima della lettura).
120
Illustrazione 32: Esempio di percorso per l'attivazione di un conflitto
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
In generale, possono esistere molteplici percorsi che soddisfano i requisiti previsti.
Tuttavia, è preferibile identificare il percorso più breve possibile. Nella pratica, anche se,
ai fini dell'iniezione di un guasto alcune operazioni di locking vengono rese inefficaci, è
possibile che altri lock (o altre risorse) siano stati acquisiti in precedenza dai thread
coinvolti. Per tale ragione, se si blocca un thread in attesa che interferisca con un secondo
thread, quest'ultimo potrebbe essere impossibilitato a continuare l'esecuzione a causa del
blocco del primo thread. Riducendo la lunghezza del percorso si riduce anche la
probabilità che si verifichi questa situazione. Non essendo possibile evitare tale situazione,
né sapere a priori se essa si verificherà, è possibile stabilire un tempo massimo di blocco di
un thread. Nel caso in cui tale tempo massimo venga superato, si ritiene che la situazione
patologica si sia verificata e l'esperimento viene interrotto. Nel caso in esame, il tempo
massimo è stato determinato in base al tempo massimo di processamento (simulato)
richiesto dal workload sommato ad un margine di sicurezza.
5.4.2 Tecnica di controllo dello scheduling
Per determinare lo scheduling in grado di attivare i 4 conflitti previsti, la seconda
campagna di fault injection è stata preceduta da una fase sperimentale in cui ogni conflitto
è stato studiato, dal punto di vista dell'interleaving delle operazioni, a prescindere dallo
stato del sistema. Una volta determinato, per ogni conflitto, lo scheduling da imporre,
questo è stato riprodotto senza la necessità di modifiche, in ogni stato previsto dalla
campagna (ove possibile – si veda il capitolo 6).
Lo scheduling è stato dunque controllato in fase di runtime del sistema, attraverso Gdb
121
Illustrazione 33: Esmpio di scheduling per l'attivazione di un conflitto
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
(GNU Debugger) [18]. Gdb è uno strumento utile per la comprensione di ciò che accade
durante l'esecuzione di un'applicazione o in seguito ad un crash di questa, permettendo di
leggere i valori delle variabili utilizzate, aree di memoria, etc. ma la caratteristica più utile
ai fini di questo lavoro è la possibilità di introdurre dei breakpoint corrispondenti a precise
locazioni (istruzioni) del codice sorgente, affinchè, durante l'esecuzione del sistema, il
flusso di esecuzione possa essere interrotto in determinati punti. I breakpoint possono
essere definiti sulla base di opportune istruzioni, possono essere condizionate a particolari
eventi, e soprattutto possono essere attivati per singoli thread o gruppi di essi.
In tal modo, Gdb fornisce un supporto essenziale per controllare l'esecuzione concorrente
di più thread poiché rende possibile gestire l'avanzamento delle operazioni controllando i
singoli thread pemettendo, di conseguenza, di controllare lo scheduling di determinate
operazioni.
Nella pratica, Gdb è stato usato per controllare il processo Facade (primario), e la tecnica
utilizzata prevede che, all'avvio di questo, venga lanciato automaticamente anche Gdb,
forzando la sospensione iniziale dell'esecuzione (in realtà la sospensione avviene in
seguito alle operazioni di inizializzazione).
Dopo aver configurato i breakpoint necessari, in dipendenza del conflitto in questione, si
riprende l'esecuzione del processo, che eventualmente istanzia i diversi thread in
dipendenza del workload, fra cui quelli che si vuole far collidere. I breakpoint possono
essere ignorati fino al punto in cui si arriva allo stato target, momento in cui,
manualmente, si interviene sullo scheduling interrompendo e riprendendo i diversi thread,
come descritto nei precedenti paragrafi.
Qualora sia possibile attivare il conflitto, a meno dei casi silenti di content failure, Gdb
fornisce un output dettagliato, esplicativo del failure riscontrato (segmentation fault, abort,
etc.). Segue un esempio della tecnica appena descritta.
Stato target 1:0:0Conflitto 2
122
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Variabile condivisa lockz-condSezioni critiche in conflitto (10-R) <-- (8-W)Messaggio finale e intermedio
PSC, PSC
Breakpoint 591: void Facade_impl::updateCB(...){...}
608: ps_group_Interface->requestCB(...);
776: void Facade_impl::deleteFDP(...){...}
916: lockz[del_pos].id = 0; lockz[del_pos].flag = false; lockz[del_pos].client_back_id = ... delete lockz[del_pos].mutex; lockz[del_pos].mutex = 0; delete lockz[del_pos].cond; lockz[del_pos].cond = 0;
1012: lockz[index].cond->broadcast();
Per motivi pratici, i breakpoint sono stati attivati per tutti i thread e, fino al
raggiungimento dello stato target, sono stati ingorati manualmente. Tuttavia, una possibile
estensione può prevedere la possibilità di automatizzare questo processo.
Tutti i thread relativi ad operazioni di update, dunque, vengono interrotti al breakpoint
591. Viene dapprima schedulato un thread che fa un'update sul piano di volo 1 (thread 1),
poi un ulteriore thread che fa la stessa operazione (thread 2 - lo stato attuale è 1:1:0).
Quando il thread 1 si ferma al breakpoint 1012 viene fatto continuare (lo stato attuale è
1:0:0). Si schedula il thread che effettua la delete del piano di volo 1 (thread 3 - anch'esso
precedentemente interrotto al breakpoint 776) (stato 1:1:0) e viene fatto proseguire fino al
breakpoint 916 (stato 1:0:0), dopodichè viene schedulato un ulteriore thread di update sul
piano di volo 1 (thread 4) (stato 1:1:0), che prosegue l'esecuzione fino al breakpoint 1012
(stato 1:0:0). In questo punto si attiva la scrittura non protetta effettuata dal thread 3 dopo
il breakpoint 916 e, quando si riprende l'esecuzione del thread 4, che accede in lettura a
quella variabile (ormai deallocata), si verifica il crash del Facade per Segmentation Fault.
123
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Capitolo 6: Valutazione sperimentale basata sull'iniezione di
guasti di concorrenza
6.1 Copertura degli stati
Le tecniche discusse nel capitolo precedente, di supporto alla metodologia illustrata,
consentono, in linea di principio, di testare il sistema in qualunque stato di funzionamento,
attivando un arbitrario conflitto fra quelli a disposizione. Tuttavia, come anticipato, ciò
nella pratica non è sempre vero.
In alcuni stati, ad esempio, può non esistere il percorso per l'attivazione del conflitto, ossia
non esiste, nel modello degli stati, un modo di percorrerlo tale che si pervenga allo stato
target avendo attraversato gli archi opportuni.
In altri casi, può capitare che, pur esistendo il percorso, non è possibile percorrerlo a causa
del tipo di conflitto che, richiedendo la sospensione di un flusso di esecuzione in un
particolare punto del percorso, preclude la continuazione dell'attraversamento.
E' possibile, ad esempio, che ci siano meccanismi di protezione, al di fuori di quelli
oggetto della fault injection, tali da far bloccare reciprocamente i thread coinvolti nel
conflitto.
In altri casi, esemplificati più avanti, sebbene esista il percorso e sebbene non vi siano i
problemi di sincronizzazione appena esposti, può capitare che non si riesca ad attivare il
conflitto. Tale circostanza è dovuta alla tipologia di conflitto che, per poter essere attivato,
può necessitare di particolari tipi di richieste, con un preciso ordine di arrivo, e relative ad
un particolare piano di volo. Tali condizioni possono essere rispettate solo in dipendenza
dello stato del sistema.
Premesso quanto detto, in questa campagna si è adottato un approccio sperimentale a tali
problemi, ricercando dapprima il percorso per ogni conflitto e per ogni stato, poiché 124
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
l'esistenza del percorso è una condizione necessaria all'attivazione del conflitto. In seguito,
si è applicata la tecnica di iniezione per tutti gli stati previsti e per tutti i conflitti per i quali
è stato trovato un percorso. Quando è sorto uno dei problemi sopra esposti, l'esperimento è
stato scartato e il conflitto è stato definito come non attivabile.
Di seguito si riporta, per ogni stato considerato in questa campagna, i possibili conflitti che
è stato possibile attuare (in linea di principio), le relative sezioni critiche, i percorsi nel
modello degli stati che consentono l'attuazione del conflitto, gli stati di partenza di tali
percorsi, così come prodotti dall'algoritmo descritto nel paragrafo 5.4.1. Sono presenti
anche stati già testati dalla campagna precedente poiché sono stati effettuati dei test a
campione anche per questo insieme di stati.
Stato target 1:0:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
First subpath
1:0:0 --(CR)--> 1:0:0 --(FR)--> 1:1:0 --(PSC)--> 1:0:0
z: 1:0:0
Second subpath
1:0:0 --(CR)--> 1:0:0 --(FR)--> 1:1:0 --(PSC)--> 1:0:0
s: 1:0:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
1:0:0 --(CR)--> 1:0:0 --(FR)--> 1:1:0 --(PSC)--> 1:0:0
s: 1:0:0
125
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Second subpath
1:0:0 --(CR)--> 1:0:0
z: 1:0:0
Stato target 2:0:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
First subpath
2:0:0 --(CR)--> 2:0:0 --(FR)--> 2:1:0 --(PSC)--> 2:0:0
z: 2:0:0
Second subpath
2:0:0 --(CR)--> 2:0:0 --(FR)--> 2:1:0 --(PSC)--> 2:0:0
s: 2:0:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
2:0:0 --(CR)--> 2:0:0 --(FR)--> 2:1:0 --(PSC)--> 2:0:0
s: 2:0:0
Second subpath
2:0:0 --(CR)--> 2:0:0
z: 2:0:0
Stato target 0:1:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
First subpath
0:1:0 --(CR)--> 0:1:0 --(FR)--> 0:2:0 --(PSC)--> 0:1:0
z: 0:1:0
126
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Second subpath
0:1:0 --(CR)--> 0:1:0 --(FR)--> 0:2:0 --(PSC)--> 0:1:0
s: 0:1:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
0:1:0 --(CR)--> 0:1:0 --(FR)--> 0:2:0 --(PSC)--> 0:1:0
s: 0:1:0
Second subpath
0:1:0 --(CR)--> 0:1:0
z: 0:1:0
Stato target 2:1:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
First subpath
2:1:0 --(CR)--> 2:1:0 --(FR)--> 2:2:0 --(PSC)--> 2:1:0
z: 2:1:0
Second subpath
2:1:0 --(CR)--> 2:1:0 --(FR)--> 2:2:0 --(PSC)--> 2:1:0
s: 2:1:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
2:1:0 --(CR)--> 2:1:0 --(FR)--> 2:2:0 --(PSC)--> 2:1:0
z: 2:1:0
Second subpath
2:1:0 --(CR)--> 2:1:0
s: 2:1:0
lockz-cond (9-R) <-- (12-W) (CRQ, CR)
127
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
First subpath
2:1:0 --(CR)--> 2:1:0
z: 2:1:0
Second subpath
1:1:0 --(CRQ)--> 2:1:0
s: 1:1:0
Stato target 0:2:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
First subpath
0:2:0 --(CR)--> 0:2:0 --(FR)--> 0:3:0 --(PSC)--> 0:2:0
z: 0:2:0
Second subpath
0:2:0 --(CR)--> 0:2:0 --(FR)--> 0:3:0 --(PSC)--> 0:2:0
s: 0:2:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
0:2:0 --(CR)--> 0:2:0 --(FR)--> 0:3:0 --(PSC)--> 0:2:0
s: 0:2:0
Second subpath
0:2:0 --(CR)--> 0:2:0
z: 0:2:0
Stato target 2:2:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
128
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
2:2:0 --(CR)--> 2:2:0 --(FR)--> 2:3:0 --(PSC)--> 2:2:0
z: 2:2:0
Second subpath
2:2:0 --(CR)--> 2:2:0 --(FR)--> 2:3:0 --(PSC)--> 2:2:0
s: 2:2:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
2:2:0 --(CR)--> 2:2:0 --(FR)--> 2:3:0 --(PSC)--> 2:2:0
s: 2:2:0
Second subpath
2:2:0 --(CR)--> 2:2:0
z: 2:2:0
lockz-cond (9-R) <-- (12-W) (CRQ, CR)
First subpath
2:2:0 --(CR)--> 2:2:0
z: 2:2:0
Second subpath
1:2:0 --(CRQ)--> 2:2:0
s: 1:2:0
129
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Stato target 3:2:0
Conflitti lockz-cond (10-R) <-- (8-W) (PSC, PSC)
lockz-mutex (10-R) <-- (8-W) (PSC, PSC)
First subpath
3:2:0 --(CR)--> 3:2:0 --(FR)--> 3:3:0 --(PSC)--> 3:2:0
z: 3:2:0
Second subpath
3:2:0 --(CR)--> 3:2:0 --(FR)--> 3:3:0 --(PSC)--> 3:2:0
s: 3:2:0
lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
3:2:0 --(CR)--> 3:2:0 --(FR)--> 3:3:0 --(PSC)--> 3:2:0
s: 3:2:0
Second subpath
3:2:0 --(CR)--> 3:2:0
z: 3:2:0
lockz-cond (9-R) <-- (12-W) (CRQ, CR)
First subpath
3:2:0 --(CR)--> 3:2:0
z: 3:2:0
Second subpath
2:2:0 --(CRQ)--> 3:2:0
s: 2:2:0
130
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Stato target 1:3:+
lockz-cond (9-R) <-- (12-W) (CRQ, CR)
First subpath
0:3:+ --(CRQ)--> 1:3:+
s: 0:3:+
Second subpath
1:3:+ --(CR)--> 1:3:+
z: 1:3:+
Stato target 2:3:+
lockz-cond (9-R) <-- (12-W) (CRQ, CR)
First subpath
1:3:+ --(CRQ)--> 2:3:+
s: 1:3:+
Second subpath
2:3:+ --(CR)--> 2:3:+
z: 2:3:+
Stato target 3:3:+
lockz-cond (9-R) <-- (12-W) (CRQ, CR)
First subpath
2:3:+ --(CRQ)--> 2:3:+
s: 2:3:+
Second subpath
2:3:+ --(CR)--> 2:3:+
z: 2:3:+
131
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
L'algoritmo finora considerato, applicato all'insieme di stati seguente, non ha prodotto
percorsi utili per nessuno dei conflitti considerati:
2:3:0, 0:3:+
Al fine di ottenere una maggiore copertura e testare anche questi stati, oltre che per cercare
di attivare altri conflitti in alcuni stati già coperti, è stato rilassato il vincolo che impone i
thread relativi alle richieste iniettate come unici thread confliggenti. Con riferimento alla
descrizione formale dell'algoritmo, finora si è imposto che i messaggi relativi alle sezioni
critiche in conflitto fossero w ed y.
Per gli stati in questione, invece, si è prevista la possibilità che i thread in conflitto
potessero essere quelli i cui messaggi relativi facessero parte dei messaggi utilizzati per far
transitare il sistema nello stato s. Per questi stati i percorsi, illustrati di seguito, sono stati
trovati manualmente, adoperando l'algoritmo descritto con il rilassamento proposto.
Stato target 0:3:+
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
Second subpath
0:2:0 --(CR)--> 0:2:0 --(FR)--> 0:3:0 [altre richieste] --(PSC)--> 0:3:+
s: 0:2:0
First subpath
0:3:+ --(CR)--> 0:3:+
z: 0:3:+
Se la richiesta di delete del primo percorso parziale non fosse fatta prima di arrivare allo
stato 0:3:0 sarebbe sospesa, e quindi servita dopo qualche altra richiesta (PSC), che
farebbe cambiare lo stato.
132
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Stato target 1:3:+
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
1:2:0 --(CR)--> 1:2:0 --(FR)--> 1:3:0 [altre richieste]--(PSC)--> 1:3:+
s: 1:3:+
Second subpath
1:3:+ --(CR)--> 1:3:+
z: 1:3:+
Stato target 2:3:+
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
2:2:0 --(CR)--> 2:2:0 --(FR)--> 2:3:0 [altre richieste]--(PSC)--> 2:3:+
s: 2:2:0
Second subpath
2:3:+ --(CR)--> 2:3:+
z: 2:3:+
Stato target 3:3:+
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
3:3:0 --(CR)--> 3:3:0 --(FR)--> 3:3:+ [altre richieste sospese]--(PSC)--> 3:3:+
s: 3:3:0
Second subpath
3:3:+ --(CR)--> 3:3:+
z: 3:3:+
133
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Stato target 0:3:0 Non è stato possbile attuare nessun conflitto
Stato target 1:3:0
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
1:2:0 --(CR)--> 1:2:0 --(FR)--> 1:3:0 [altre richieste]--(PSC)--> 1:3:0
s: 1:2:0
Second subpath
1:3:0 --(CR)--> 1:3:0
z: 1:3:0
Stato target 2:3:0
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
2:2:0 --(CR)--> 2:2:0 --(FR)--> 2:3:0 [altre richieste]--(PSC)--> 2:3:0
s: 2:2:0
Second subpath
2:3:0 --(CR)--> 2:3:0
z: 2:3:0
Stato target 3:3:0
Conflitti lockz-mutex (9-R) <-- (8-W) (CR, PSC)
First subpath
3:2:0 --(CR)--> 3:2:0 --(FR)--> 3:3:0 [altre richieste]--(PSC)--> 3:3:0
s: 3:2:0
Second subpath
3:3:0 --(CR)--> 3:3:0
z: 3:3:0
La figura che segue evidenzia i conflitti che, di fatto, è stato possibile attuare, per ogni
134
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
stato del modello considerato.
La figura 34 riassume i seguenti risultati:
• tutti gli stati non coperti dalla campagna precedente sono stati testati mediante la
tecnica proposta (copertura del 100%)
• la tecnica proposta, unitamente a quella della campagna precedente, permette di testare
il 100% degli stati del modello (risultato valido solo per questo caso particolare)
• non è possibile attivare ogni conflitto in ogni stato
Un caso particolare dell'ultimo punto è lo stato 0:3:0. Per questo stato, infatti, così come
135
Illustrazione 34: Risultati della campanga di concurrency fault injection
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
per altri stati già coperti dalla campagna precedente, sono stati effettuati dei test a
campione, ma non è stato possibile attivare alcun conflitto. Di seguito vengono esaminati i
quattro conflitti per lo stato 0:3:0 e vengono discusse le cause della mancata attivazione.
Conflitto 1
Variabile condivisa: lockz-cond
Sezioni critiche in conflitto: (9-R) <-- (12-W)
Messaggio intermedio e messaggio finale: (CRQ, CR)
Non esiste un percorso utile per attivare questo conflitto
Conflitto 2
Variabile condivisa: lockz-cond
Sezioni critiche in conflitto: (10-R) <-- (8-W)
Messaggio intermedio e messaggio finale: (PSC, PSC)
Si veda il Conflitto 4
Conflitto 3
Variabile condivisa: lockz-mutex
Sezioni critiche in conflitto: (9-R) <-- (8-W)
Messaggio intermedio e messaggio finale: (CR, PSC)
Per poter attivare questo conflitto è necessario bloccare una richiesta di update un certo
piano di volo x all'istruzione
lockz[index].mutex->lock();
A questo punto è necessario schedulare una richiesta di delete di x affinchè esegua le
operazioni
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
136
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Tuttavia, in questo stato non si può interrompere la richiesta di update ed effettuare una
richiesta di delete in quanto verrebbe sospesa e l'unico modo di ritornare nello stato 0:3:0
sarebbe mediante una PSC di una richiesta già in processamento, non relativa al piano di
volo x. Per questo è necessario avviare la delete su x prima dell'update su x. Tuttavia ciò
comporta, necessariamente, che poi l'update venga accodata, pervenendo allo stato 1:3:0,
dal quale non è possibile ritornare in 0:3:0 sospendendo sia la richiesta di delete che quella
di update. Per questi motivi non è possibile attivare questo conflitto in questo stato.
Conflitto 4
Variabile condivisa: lockz-mutex
Sezioni critiche in conflitto: (10-R) <-- (8-W)
Messaggio intermedio e messaggio finale: (PSC, PSC)
Per ottenere questo conflitto è necessario schedulare prima una PSC, interrompendola
all'istruzione
lockz[index].mutex->lock();
e poi schedulare un'altra PSC che esegue
delete lockz[del_pos].mutex;
lockz[del_pos].mutex = 0;
Partendo dallo stato 0:3:0 è possibile schedulare la prima PSC arrivando in 0:3:0 con una
sequenza CR, FR che possa poi indurre tale PSC. Tuttavia non è possibile fare la stessa
cosa per la seconda PSC a partire dallo stato 0:3:0 perchè la richiesta verrebbe sospesa. Né
è possibile arrivare in 0:3:0 con due sequenze CR, FR perchè necessariamente una delle
due CR verrebbe accodata.
Per tali motivi non è possibile attivare questo conflitto in questo stato. Un analogo
discorso vale per il conflitto 2.
137
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
6.2 Vulnerabilità scoperte
Questa campagna sperimentale ha dimostrato che mediante la tecnica esposta è possibile
esplorare stati del sistema che la tecnica precedente non era riuscita a coprire. Nel caso
particolare di questo sistema, fra tali stati rientra una classe di stati particolarmente
sensibili a vulnerabilità, ossia gli stati *:*:+ con richieste sospese. Sebbene non sia stata
dimostrata concretamente una vulnerabilità del sistema legata ad eventi di crash del
Facade in questi stati, i risultati di questa campagna suggeriscono la necessità di un
maggior approfondimento della valutazione delle tecniche di fault tolerance in questi stati
del sistema, poiché la gestione delle richieste sospese, dal punto di vista dei meccanismi di
logging e recovery, necessita di essere testata dettagliatamente.
138
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Conclusioni e sviluppi futuri
In questo lavoro sono state valutate due tecniche per la Sofware Fault Injection
utilizzando, come caso di studio un sistema per la gestione dei piani di volo, chiamato
Coflight.
Entrambe le tecniche sono state supportate da un modello a stati finiti del sistema,
appositamente definito, che ha permesso di valutarne l'efficacia in dipendenza delle
diverse condizioni di funzionamento, o stati del sistema. L'utilità di un tale modello è
risultata evidente in entrambi i casi, poiché è stato mostrato che, guidando gli esperimenti
di fault injection attraverso i diversi stati è possibile ottenere una maggiore comprensione
delle problematiche legate ai fault iniettati e le conseguenze che portano.
Il sistema in esame è stato dunque instrumentato opportunamente per fare in modo che le
entità di interesse (Facade e Processing Server) producessero un insieme di messaggi, in
corrispondenza degli eventi relativi a determinate transizioni del modello proposto.
Attraverso tali messaggi è stato possibile mettere in relazione il funzionamento del sistema
con le transizioni nel modello e, dunque, comprendere gli stati attraversati, e in particolar
modo quelli relativi all'attivazione dei fault o all'occorrenza di failure. E' stato
implementato un tool apposito per ricavare queste informazioni in maniera automatica a
partire dai log delle diverse entità del sistema.
Una volta approntato il sistema nella maniera descritta si è proceduto alla valutazione della
prima tecnica: G-SWFIT. Oggetto dell'iniezione sono stati il Facade (537 fault iniettati) e i
Processing Server (138 fault iniettati), con particolare enfasi sul Facade, single point of
failure del sistema ed entità maggiormente critica a causa delle funzioni che svolge.
Sono stati implementati, inoltre, quattro workload differenti al fine di stimolare il sistema
ad attraversare gli stati in maniera differente e tale scelta si è rivelata utile a coprire il
maggior numero di stati possibile. Tuttavia, dai risultati di questa prima campagna
sperimentale è emerso che mediante questa tecnica non è possibile coprire tutti gli stati del
sistema (il 35% degli stati è rimasto non testato).
139
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Un'ulteriore limitazione di questa tecnica, peraltro non banale, consiste nella tipologia dei
fault, di fatto, emulati. Uno degli scenari proposti per testare l'iniezione sul Facade, che ha
previsto l'iniezione sia sulla copia primaria che su quella secondaria, ha rivelato che nel
94% dei casi, un fault attivato e degenerato in un failure nella copia primaria ha avuto lo
stesso comportamento anche nella copia di backup. Questo suggerisce che la tipologia di
fault in questione è rappresentativa dei bohrbugs piuttosto che degli heisenbug. Ma la fault
tolerance non nasce come contromisura per i bohrbugs, già sufficientemente testati e
rimossi durante la fase di testing di un sistema, quanto per contrastare gli heisenbugs che
difficilmente hanno il comportamento ripetitivo e facilmente riproducibile dei bohrbugs.
Proprio da queste limitazioni è nato lo studio che ha permesso di definire la tecnica
proposta in questo lavoro: la tecnica di iniezione dei bug di concorrenza. Questa tipologia
di bug, infatti, sulla base di studi pregressi, è stata identificata come quella più frequente
tra gli heisenbug, per cui è stato definito un fault model in grado di emularla al meglio.
Il fault model è stato definito con riferimento ai risultati di uno studio precedente,
mediante una serie di errori di programmazione concorrente legati alla sincronizzazione
fra thread e raccogliendo una serie di conflitti fra diverse sezioni critiche del codice del
Facade.
Contestualmente sono state definite le relative condizioni di attivazione, consistenti in
precisi interleaving fra le operazioni dei differenti flussi di esecuzione.
In seguito si è proceduto a verificare l'attivabilità dei conflitti raccolti in ognuno degli stati
non coperti dalla campagna precedente. Nel caso particolare di questo sistema, tale tecnica
ha prodotto risultati estremamente positivi: il 100% degli stati precedentemente non
coperti sono stati coperti con la tecnica proposta. Tuttavia si è sottolineato il fatto che
questo non è un risultato generale, ma dipende dal sistema in esame. Si è mostrato, infatti,
mediante test effettuati a campione su altri stati, che vi sono casi in cui, mediante questa
tecnica, non si riesce ad attivare alcun conflitto.
Ad ogni modo, il risultato complessivo dell'integrazione delle due tecniche è positivo: tutti
140
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
gli stati del modello sono stati testati e entrambe le tecniche hanno permesso di rivelare
vulnerabilità nei meccanismi di fault tolerance del sistema, o quantomeno, evidenziarne di
potenziali.
Proprio le potenziali vulnerabilità rivelabili mediante queste tecniche e mediante il
supporto di un modello del sistema sono uno dei possibili sviluppi di questo lavoro, che
comprendono anche:
• la possibilità di testare modelli del sistema differenti (ad es. le catene di Markov
nascoste)
• valutare i differenti livelli di granularità dello stato (accennati in questo lavoro)
• adattare ed applicare la metodologia proposta a COTS
141
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Appendice A: Modellazioni alternative del sistema
Nel Capitolo 2 si sono discusse diverse alternative per la modellazione del sistema, fra cui
una che prevede di tenere in conto la tipologia delle richieste in coda ed in processamento.
In quest'appendice si dimostrano le relazioni esposte nel paragrafo 2.5 circa il numero di
stati che si ottiene volendo tenere in conto tali informazioni. Come anticipato, le variabili
utili per questo tipo di modellazione sono:
• up (numero di richieste di update in processamento)
• dp (numero di richieste di delete in processamento)
• ip (numero di richieste di insert in processamento)
• uq (numero di richieste di update in coda)
• dq (numero di richieste di delete in coda)
• iq (numero di richieste di insert in coda)
Queste variabili sono legate dalle seguenti relazioni:
up ,dp , ip≥0
uq ,dq , iq≥0variabili non negative
updpip≤3 non ci possono essere più di tre richieste processate
contemporaneamente
uqdqiq≤Q non ci possono essere più di Q richieste accodate
Ognuna di queste due relazioni comporta un numero di combinazioni che è dato dal
numero di punti a coordinate intere presenti all'interno di un quarto di un cubo di lato
rispettivamente 3 e Q.
Tali quantità valgono rispettivamente:
142
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
∑i=1
4
[4−i−1⋅i ]
∑i=1
Q1
[Q1−i−1⋅i ]
Dimostrazione
Consideriamo l'espressione poiché l'altra è un caso particolare
di questa.
Vogliamo dimostrare che quest'espressione fornisce il numero dei possibili modi di
scrivere T con 0≤T≤Q come la somma di 3 variabili.
Si consideri la relazione ricorsiva
comb(n) = comb(n-1) + n + 1
comb(0)= 1
Supponiamo vero che comb(n) sia il numero di combinazioni per ottenere esattamente n
come somma di tre variabili, allora, per ottenere T, con 0≤T≤Q , bisogna calcolare
Un modo di dimostrare che comb(n) è il numero di combinazioni per ottenere esattamente
n come somma di tre variabili è il principio di induzione.
Il passo base è banalmente verificato:
comb(0) = 1 (c'è un solo modo ed è 0 + 0 + 0)
comb(1) = 3 (1 + 0 + 0 oppure 0 + 1 + 0 oppure 0 + 0 +1)
Supponendo vera l'ipotesi induttiva per per n-1 dimostriamo che è vera per n.
Supponiamo che il numero di combinazioni per ottenere T=n-1 sia pari a comb(n-1).
A partire da tali combinazioni si possono ottenere 3 gruppi di combinazioni sommando 1 a
ciascuna combinazione (o terna) in una precisa posizione della terna.
143
∑i=1
Q1
[Q1−i−1⋅i ]
combi≤Q=∑i=0
Q
[combi ]
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Valgono le seguenti proprietà:
• I 3 gruppi hanno la stessa dimensione perchè sono generati a partire dallo stesso
insieme di combinazioni iniziale, sommando 1 ad una posizione.
• I 3 gruppi includono tutte le combinazioni per ottenere T=n ma includono alcune
combinazioni ripetute
• Per ogni combinazione del gruppo 1 (ma vale in generale) esiste almeno una
combinazione equivalente nel gruppo 2 ed una equivalente nel gruppo 3:
◦ Detta (x+1,y,z) la combinazione considerata per il gruppo 1, la combinazione
equivalente per il gruppo 2 si ottiene sommando 1 alla seconda posizione della
combinazione (x+1,y-1,z) del gruppo di partenza.
◦ Va fatta eccezione per le combinazioni (n-k, 0, k) che non sono sicuramente
presenti nel gruppo 2 (ma sono presenti nel gruppo 3 per k≠0 ) e le
combinazioni (n-k, k, 0) che non sono sicuramente ripetute nel gruppo 3.
• Per ogni combinazione nel gruppo 1 esiste al più una combinazione equivalente nel
gruppo 2 ed una equivalente nel gruppo 3: se così non fosse ci sarebbero 2
combinazioni uguali nel gruppo 2 o nel gruppo 3, ma ciò è assurdo.
144
Un approccio per la Software Fault Injection in Sistemi Software Complessi e Distribuiti
Date queste proprietà:
• Il gruppo 1 ha comb(n-1) combinazioni univoche per ottenere T=n
• Il gruppo 2 ha n combinazioni aggiuntive per ottenere T=n che non esistono nel
gruppo 1 ma esistono nel gruppo 3 (ossia quelle del tipo (0, n-k, k)).
• Il gruppo 3 ha una combinazione aggiuntiva per ottenere T=n che non esiste né nel
gruppo 1 né nel gruppo 2 (ossia del tipo (0, 0, n)).
Complessivamente l'intersezione dei tre gruppi fornisce comb(n-1) + n + 1 combinazioni
e tale valore è pari a comb(n).
Infine, si dimostra, ma qui è omesso per brevità, che
145
comb0≤i≤Q=∑i=0
Q
[combi]=∑i=1
Q1
[Q1−i−1⋅i ]
Bibliografia[1] Avresky, D.; Arlat, J.; Laprie, J.-C.; Crouzet, Y., “Fault injection for the formal testing
of fault tolerance”, Twenty-Second International Symposium on Fault Tolerant Computing,
Page(s):345 – 354, 8-10 July 1992
[2] Moraes, R.; Barbosa, R.; Duraes, J.; Mendes, N.; Martins, E.; Madeira, H., “Injection of
faults at component interfaces and inside the component code: are they equivalent?”,
Dependable Computing Conference. EDCC '06. Sixth European, pp.53-64, 18-20 Oct.
2006
[3] Madeira, H.; Costa, D.; Vieira, M., “On the emulation of software faults by software
fault injection”, DSN 2000. Proceedings International Conference on Dependable Systems
and Networks, pp.417-426, 2000
[4] J. Gray, “A Census of Tandem Systems Availability between 1985 and 1990”, IEEE
Tranactions on Reliability, vol. 39, no. 4, pp. 409-418, Oct. 1990
[5] Joao A. Duraes, Henrique S. Madeira, “Emulation of Software Faults: A Field Data
Study and a Practical Approach”, IEEE Transactions on Software Engineering, vol. 32, no.
11, pp. 849-867, November, 2006.
[6] J. C. Laprie, A. Avizienis, and H. Kopetz, “Dependability: Basic Concepts and
Terminology.”, 1992, Springer-Verlag New York, Inc.
[7] Avizienis, A.; Laprie, J.-C.; Randell, B.; Landwehr, C., “Basic concepts and taxonomy
of dependable and secure computing,” IEEE Transactions on Dependable and Secure
Computing, vol.1, no.1, pp. 11-33, Jan.-March 2004
[8] Koopman, P.; Sung, J.; Dingman, C.; Siewiorek, D.; Marz, T., “Comparing operating
systems using robustness benchmarks”, Proceedings of The Sixteenth Symposium on
Reliable Distributed Systems, pp.72-79, 22-24 Oct 1997
[9] Ram Chillarege, Inderpal S. Bhandari, Jarir K. Chaar, Michael J. Halliday, Diane S.
Moebus, Bonnie K. Ray, and Man-Yuen Wong. “Orthogonal defect classification - a
concept for in-process measurements” IEEE Transactions on Software Engineering,
18(11):943–956, 1992.
[10] http://www.eurocontrol.int/statfor/public/subsite_homepage/homepage.html
[11] Cheah, Mervyn ; Lock Pin, Chew ; Chee Ping, Tan. “Command Control and
Information Systems in the Age of Knowledge-Centricity” Defence Science and
Technology Agency (Singapore), June 2004
[12] http://www.eurocontrol.int/eec/public/standard_page/ERS_avenue.html
[13] http://www.laas.fr/DBench/
[14] Christmansson, J. and Chillarege, “Generation of an error set that emulates software
faults based on field data”, Proceedings of the the Twenty-Sixth Annual international
Symposium on Fault-Tolerant Computing , June 25 - 27, 1996, IEEE Computer Society,
Washington, DC, 304.
[15] Li, Z., Tan, L., Wang, X., Lu, S., Zhou, Y., and Zhai, C. “Have things changed now?:
an empirical study of bug characteristics in modern open source software”,Proceedings of
the 1st Workshop on Architectural and System Support For Improving Software
Dependability, October 21 - 21, 2006, ACM, New York, NY, 25-33
[16] I. Lee and R.K. Iyer, “Faults, Symptoms, and Software Fault Tolerance in Tandem
GUARDIAN90 Operating System” Proceedings of the 23rd IEEE International
Symposium on Fault-Tolerant Computing, pp. 20-29, 1993
[17] S. Lu, S. Park, E. Seo, and Y. Zhou, “Learning from mistakes: a comprehensive study
on real world concurrency bug characteristics” Proceedings of the 13th international
conference on Architectural support for programming languages and operating systems,
pp. 329-339, 2008, ACM
[18] http://www.gnu.org/software/gdb/
[19] http://cardamom.ow2.org/doc.html
[20] Michael Grottke; Kishor S. Trivedi, “Fighting Bugs: Remove, Retry, Replicate, and
Rejuvenate” Computer , vol.40, no.2, pp.107-109, Feb. 2007
[21] Inhwan Lee; Iyer, R.K., “Software dependability in the Tandem GUARDIAN
system”, IEEE Transactions on Software Engineering, vol.21, no.5, pp.455-467, May 1995
[22] M. Sullivan and R. Chillarege, “Software defects and their impact on systems
availability – A study of field failures on operating systems”, Proceedings of the 21st Fault
Tolerant Computing Symposium, pp. 2-9, Jun. 1991.
[23] S. Russo, C. Savy, D. Cotroneo, A. Sergio, “Introduzione a Corba”, McGraw-Hill,
Oct. 2002
[24] http://www.omg.org/technology/documents/corba_spec_catalog.htm
[25] D. Cotroneo, R. Natella, A. Pecchia, S. Russo “An Approach for Assessing Logs by
Software Fault Injection ”, Workshop on Proactive Failure Avoidance, Recovery and
Maintenance (PFARM) Estoril, Lisbon, Portugal, June 29th, 2009