Skip to content

Instantly share code, notes, and snippets.

@qsorix
Created November 17, 2010 21:55
Show Gist options
  • Save qsorix/704170 to your computer and use it in GitHub Desktop.
Save qsorix/704170 to your computer and use it in GitHub Desktop.
\documentclass[00-praca-magisterska.tex]{subfiles}
\begin{document}
\chapter{Moduł Arete Master}
Prezentujemy tutaj szczegółowy opis modułu Master. Jest to moduł zarządzający
definicją i wykonywaniem testów. Jest on też interfejsem użytkownika do naszego
narzędzia -- za jego pomocą użytkownik wykonuje testy.
Rozdział rozpoczynamy przedstawienie architektury modułu i wprowadzając nazwy
komponentów omawianych w dalszej części. Następnie szeroko opisujemy format
plików definiujących testy, rzecz bardzo istotną z punktu widzenia użytkownika.
Osoby chcące przeprowadzać bardziej złożone testy skorzystają z opisu części
wykonawczej modułu i możliwości rozbudowy jego funkcjonalności poprzez własne
wtyczki. Następnie opisujemy sposób przechowywania wyników testu w bazie
danych. Na końcu przedstawiamy opcje linii poleceń programu \code{arete}.
\section{Architektura}
Arete Master zarządza wykonaniem testów przez moduły Arete Slave. Master
odpowiada za dwa kluczowe zadania. Pierwszym jest przetworzenie konfiguracji
dostarczonej przez użytkownika w plany testów dla poszczególnych urządzeń
biorących w nim udział. Drugie, to dostarczenie tych planów do poszczególnych
urządzeń i kontrola wykonania testu.
\begin{figure}[htb]
\begin{center}
\leavevmode
\includegraphics[width=0.8\textwidth]{arete-master-arch}
\end{center}
\caption{Architektura modułu Arete Master}
\label{fig:arete-master-arch}
\end{figure}
Widoczną na rysunku \ref{fig:arete-master-arch} architekturę modułu można
podzielić na dwie części. Pierwsza, to implementacja frameworku, na którą
użytkownik nie ma wpływu. Dostarcza ona takie mechanizmy jak wczytywanie plików
konfiguracyjnych i analiza ich poprawności (komponent Configuration),
sterowanie przebiegiem testu (komponent Controller) oraz przechowywanie wyników
w bazie danych. Program korzysta z bazy wyłącznie jako ujścia danych, sam
niczego z niej nie odczytując.
Pozostałe części architektury użytkownik może rozwijać i uzupełniać ich
funkcjonalność w zależności od potrzeb wynikających z testów, jakie chce
przeprowadzić. Odbywa się to przez dostarczenie własnych wtyczek
implementujących wymagane zachowanie. Więcej na ten temat w dalszej części
rozdziału, tutaj przedstawiam tylko rolę poszczególnych komponentów.
Najważniejsze elementy, wymagane do poprawnego działania każdego testu, to
wtyczki Frontend oraz Connection. Connection zestawia fizyczne połączenie ze
zdalnym urządzeniem (np. tunel SSH), Frontend korzysta z tego połączenia i
implementuje protokół komunikacyjny rozumiany przez drugą stronę (np. moduł
Arete Slave). Każdemu urządzeniu odpowiada jedna instancja wtyczek Frontend i
Connection.
Wtyczki Drivers na podstawie konfiguracji generują komendy dla zdalnych
urządzeń. Przykładem może być driver, który na czas testu konfiguruje adresy IP
urządzeń. Wtyczki te są szerzej omówione w sekcji \fixref{sekcja o drivers}.
Komponenty Hooks\footnote{W tym znaczeniu polskie tłumaczenie \emph{ang. hook}
to punkt zaczepienia. W pracy używamy terminu \emph{hook}, ponieważ jest zwięzły, a w
informatyce termin ten jest często spotykany i jednoznaczny.} to wtyczki dające
użytkownikowi możliwość ingerencji w generowane plany testów. Obecnie możliwe
jest wykonanie własnego kodu po wygenerowaniu wszystkich planów, ale jeszcze
przed rozpoczęciem wykonywania testu. W sekcji \fixref{Sekcja o Hooks}
prezentujemy przykład, który pozwala w wygodny sposób rozbudować test o
pobieranie informacji o zdalnych hostach.
Komponent Utils mógłby być pominięty, ponieważ nie jest wymagany. Pod tą nazwą
grupujemy wszystkie pomocnicze algorytmy, dzięki którym tworzenie testów jest
prostsze. Zainteresowanych odsyłamy do plików źródłowych w katalogu
\code{Arete/utils}. Czytelnik będzie miał możliwość zapoznania się z częścią
funkcjonalności tego komponentu, ponieważ wielu funkcji używamy w
prezentowanych przykładach.
\section{Konfiguracja}
Cechą dobrze zaplanowanego testu jest możliwość przeprowadzania go
wielokrotnie. Aby test można było powtarzać, trzeba go opisać. Opis taki
powinien określać postępowanie w czasie testu, a także środowisko, w jakim się
go przeprowadza.
Sposób opisu powinien być dostosowany do charakteru przeprowadzanych testów.
Inaczej określa się doświadczenia fizyczne, a inaczej chemiczne. Naszym
zdaniem w przypadku testów aplikacji sieciowych opis ten powinien:
\begin{itemize}
\item umożliwiać wielokrotne użycie,
\item abstrahować koncepcję testu,
\item wspierać złożone konfiguracje,
\item być zwięzły i precyzyjny.
\end{itemize}
Wielokrotne użycie części tego samego opisu pozwala szybciej stworzyć wiele
podobnych testów. Zamiast za każdym razem zaczynać od nowa, można skorzystać z
części już przygotowanego testu i wykorzystać je.
Przez koncepcję testu rozumiemy to, co w teście chcemy zrobić. Jeśli naszym
celem jest nawiązanie komunikacji między dwoma komputerami w pracowni, zwykle
nie interesuje nas, które konkretnie komputery wykorzystamy.
W skład najprostszego testu wchodzi jeden komputer. Złożone konfiguracje mogą
zawierać ich kilkadziesiąt i definiować specyficzne sposoby komunikacji, jak
np. Chord\footnote{http://en.wikipedia.org/wiki/Chord\_\%28peer-to-peer\%29}.
Sposób opisu testu powinien umożliwiać zdefiniowanie rozbudowanych
eksperymentów, a jednocześnie nie komplikować definicji tych prostych.
Dzięki zwięzłości opisu można się szybko zorientować co w danym teście się
dzieje. Łatwiej jest tworzyć nowe testy i wprowadzać zmiany. Odpowiednia
notacja może stać się zarówno definicją testu, jak też sposobem komunikacji
dla ludzi ten test przygotowujących.
Z technicznego punktu widzenia, rozwiązanie posiadające powyższe cechy
powinno:
\begin{itemize}
\item umożliwiać podział opisu na kilka plików,
\item osobno definiować wykonywane czynności i środowisko ich przeprowadzania,
\item korzystać z formatu tekstowego,
\item umożliwiać stosowanie komentarzy,
\item być proste w nauce,
\item być niezależne od platformy sprzętowej i programowej.
\end{itemize}
Początkowo planowaliśmy stworzyć narzędzie, które graficznym interfejsem
będzie wspierać tworzenie testu. Zdaliśmy sobie jednak sprawę, że jedyną jego
zaletą byłaby prostsza edycja grafu przedstawiającego topologię sieci. Z
drugiej strony narzędzie takie wprowadzałoby pewne utrudnienia techniczne, jak
np. przenośność między systemami operacyjnymi, zależności od bibliotek
graficznych i, co najważniejsze, prawdopodobnie nieprzyjazny człowiekowi
format zapisu.
Następnie rozważaliśmy stworzenie dedykowanego języka\footnote{DSL -
Domain-Specific Language}, którego cechy będą odpowiadać naszym wymaganiom.
Inspiracją był tutaj język NED\footnote{NEtwork Description} używany przez
symulator sieci OMNeT++\footnote{http://www.omnetpp.org/} do opisu budowy
urządzeń, topologii połączeń i formatu komunikatów. Po kilku eksperymentach ze
składnią stało się jednak jasne, że aby w czytelny sposób wspierać wiele
rodzajów testów i jednocześnie niczego nie utrudniać, użytkownik będzie
potrzebował możliwości rozbudowy tego języka. OMNeT++ rozwiązuje ten problem
umożliwiając tworzenie implementacji modułów w języku C++, my postanowiliśmy
skorzystać z języka Python. Tego typu rozwiązanie stosują m.in. takie narzędzia
jak systemy budowania SCons\footnote{http://www.scons.org/} i
Waf\footnote{http://code.google.com/p/waf/}. Idąc ich śladem, postanowiliśmy
stworzyć framework, który w prosty sposób będzie umożliwiał tworzenie
różnorodnych konfiguracji.
Język Python posiada wszystkie poszukiwane przez nas cechy. Ponadto użycie właśnie
języka Python jako środowiska do stworzenia potrzebnego nam DSL jest łatwiejsze niż
korzystanie z innych języków ponieważ:
\begin{itemize}
\item jest ogólnie dostępny,
\item instalacja jest prosta,
\item jest przenośny,
\item skrypty nie wymagają kompilacji,
\item posiada mechanizmy znane z języków funkcyjnych,
\item umożliwia przeładowywanie operatorów,
\item jest ogólnie znany i łatwy do nauki.
\end{itemize}
Fakt, że Python umożliwia przeładowanie znaczenia operatorów oraz wspiera
szeroką gamę technik stosowanych w programowaniu funkcyjnym powoduje, że
łatwiej jest go użyć jako DSL, gdyż jesteśmy w stanie tworzyć nowe konstrukcje,
które będą dokładnie odpowiadać naszym potrzebom, a jednocześnie pozostaną
zwięzłe w opisie.
Równie istotną cechą Pythona jest bogata biblioteka standardowa, z której mogą
korzystać autorzy własnych modułów rozszerzających możliwości frameworku.
Porównując z popularnymi językami: zarówno Java jak i C++ ze względu na
silne typowanie posiadają bardzo werbalną składnię, która utrudniałaby
odczytanie sedna zapisanego testu. Poza tym konfiguracja taka musiałaby być
kompilowana po każdej zmianie. Perl jest mniej popularny i naszym zdaniem
trudniejszy od Pythona. Bash i inne języki powłok systemowych są zbyt ubogie,
żeby wygodnie się nimi posługiwać w tworzeniu złożonych struktur danych.
Interesującą alternatywą byłby Lua, uznaliśmy jednak, że nie chcemy zmuszać
użytkowników do nauki mniej popularnego języka.
Przeciwnicy Pythona mogą wytknąć wadę związaną z bezpieczeństwem. Jako, że
konfiguracja testu jest interpretowana jako kod źródłowy programu, możliwe są
różnego rodzaju nadużycia. Uznaliśmy jednak, że nie możemy ograniczać
użytkowników w kwestiach takich jak dostęp do dysków twardych czy sieci, gdyż
wypaczałoby to działanie narzędzia.
Ryzyko ogranicza fakt, że kod źródłowy programu jak i treść konfiguracji jest
zapisana w postaci tekstowej i można samodzielnie skontrolować akcje
podejmowane przez uruchamiany kod.
\subsection{Format konfiguracji}
Aby zwiększyć szanse na ponowne wykorzystanie poprzednio definiowanych
testów, konfiguracja logicznie podzielona jest na cztery osobne części:
\begin{itemize}
\item model,
\item laboratorium,
\item odwzorowanie,
\item plan.
\end{itemize}
Model opisuje koncepcyjną topologię testu. Przedstawia ilość wymaganych
urządzeń i połączenia między nimi. Laboratorium służy do definiowania sprzętu,
którym w rzeczywistości dysponujemy. Odwzorowanie przedstawia powiązania między
urządzeniami z modelu, a tymi dostępnymi w laboratorium. Te trzy komponenty
definiują statyczną strukturę testu. Plan natomiast określa dynamiczne
zachowanie urządzeń, tj. komendy wykonywane na nich w trakcie odbywania się
testu.
Żeby lepiej oddać rolę każdego komponentu można posłużyć się analogią do
ćwiczeń laboratoryjnych. Model będzie tutaj schematem sieci narysowanym w
poleceniu do ćwiczenia. Laboratorium, to po prostu spis dostępnego nam
sprzętu. Odwzorowanie określa, które z dostępnych urządzeń zostaną wykorzystane
do zbudowania zadanej topologii. Plan mówi, co zrobić w momencie, kiedy
potrzebna sieć jest już gotowa.
Framework nie narzuca żadnego podziału, poszczególne części można nawet
definiować równolegle. Możliwe i zalecane jest jednak aby każdą część
definiować w osobnym pliku. W ten sposób będziemy posiadać pliki,
które opisywać będą dostępne nam laboratoria i kilka plików z testami, jakie
aktualnie wykonujemy. Definicji odwzorowań będzie potrzeba tylu, ilu różnych
par model-laboratorium będziemy używać. Plan najczęściej można umieścić razem z
modelem, choć oczywiście w bardziej złożonych konfiguracja tutaj także
praktyczne będzie umieszczenie ich osobno.
W dalszej części opisujemy szczegółowo poszczególne fragmenty konfiguracji,
ograniczając się jednak tylko do podstawowego interfejsu zapewnianego przez
framework. \fixref{Odesłać do rozdziału z opisem toolsetów i pełnego API}
\subsubsection{Model}
Zadaniem modelu jest przedstawienie koncepcyjnej topologii sieci. W tym
elemencie konfiguracji opisuje się występujące urządzenia, ich interfejsy i
połączenia między nimi.
O definiowaniu modelu możemy myśleć, jak o definiowaniu grafu, którego węzłami
są urządzenia, a połączenia odpowiadają krawędziom. Framework zapewnia tylko
interfejs do tworzenia węzłów -- urządzeń i krawędzi -- dodawania interfejsów
i połączeń między nimi.
Dodatkowo możliwe jest przypisanie dowolnych cech zarówno urządzeniom jak też
ich interfejsom. Cechy te mają format par nazwa-wartość. Sposób ich
wykorzystania zależy od użytkownika. Framework przekaże je do odpowiednich
sterowników \fixref{ref do rozdziału o Host i Interface Drivers}, które można
tworzyć samodzielnie. Cechy można wykorzystać na przykład do przypisywania
adresów IP interfejsom, określania sztucznie generowanych opóźnień,
sprawdzania czy system pracuje pod kontrolą danego systemu operacyjnego itp.
Framework nie wymaga definiowania żadnego z wymienionych elementów, jeśli
jawnie go nie używamy. Jeśli w teście interesuje nas tylko, aby występowały
dwa komputery, nie musimy określać ich interfejsów i adresów sieciowych,
możemy też sami założyć, że komputery są połączone i w konfiguracji to
przemilczeć.
Definicja prostego modelu może wyglądać tak, jak poniżej:
\begin{pythoncode}
create_model('peers')
alice = add_host('alice')
bob = add_host('bob')
\end{pythoncode}
W pierwszej linii funkcja \code{create\_model} tworzy model. Funkcja ta jest
definiowana przez framework i eksportowana do globalnej przestrzeni nazw w
czasie wczytywania plików konfiguracyjnych. Zadanie \code{create\_model} jest
bardzo proste. Dzięki niej nie można przez pomyłkę podać do testu dwóch
różnych modeli (np. przekazując do programu złe pliki). Przed jej wykonaniem
nie można używać pozostałej części interfejsu modelu, a wywołać można ją tylko
raz. Z tego powodu powinna się pojawić na początku każdego definiowanego
modelu.
Dwie pozostałe linie tworzą i dodają do modelu dwa urządzenia przy pomocy
funkcji \code{add\_host}. Pierwszym i obowiazkowym argumentem jest nazwa
urządzenia. Dla wygody funkcja zwraca referencję do utworzonego urządzenia.
Taka konfiguracja wystarczy do przeprowadzenia szerokiej gamy testów, w
których potrzebne są jedynie dwa komputery.
Załóżmy jednak, że z jakiegoś powodu musimy na czas testu określić komputerom
zadane adresy IP. Adresy przypisuje się oczywiście do interfejsów.
\begin{pythoncode*}{firstnumber=5}
a_eth = alice.add_interface('eth', ip='192.168.6.4')
b_eth = bob.add_interface('eth', ip='192.168.6.5')
\end{pythoncode*}
Metoda \code{add\_interface} dodaje interfejs o podanej nazwie i adresie IP.
Podobnie jak \code{add\_host} dla wygody zwracana jest referencja do
stworzonego interfejsu.
Pierwszy parametr (nazwa interfejsu) jest obowiązkowy. Dalsze, nazwane
parametry\footnote{keyword arguments} są dowolne i służą do przypisania
interfejsowi pożądanych cech. W tym wypadku podajemy parametr \code{ip}. W
czasie przetwarzania konfiguracji, odpowiedni sterownik widząc ten parametr
wygeneruje komendy konfigurujące podany adres na odpowiednim interfejsie
urządzenia docelowego.
Nawiasem mówiąc, funkcja \code{add\_host} również przyjmuje dowolny zestaw
nazwanych parametrów.
Można używać dowolnych cech. Framework nie przypisuje im żadnego znaczenia, a
sterowniki definiowane są przez użytkownika. Dzięki temu mamy pełną kontrolę i
swobodę w tym, co chcemy osiągnąć.
Ostatnim pojęciem występującym w modelu jest połączenie. Połączenie występuje
zawsze pomiędzy dwoma interfejsami i tworzy się je korzystając z funkcji
\code{add\_link}.
\begin{pythoncode*}{firstnumber=7}
add_link(a_eth, b_eth)
\end{pythoncode*}
Kolejność parametrów nie jest istotna. Połączeniu nie można przypisywać żadnych
atrybutów. Definiowanie połączeń nie jest obowiązkowe, a ich ewentualna
interpretacja przez sterowniki pozostawiona jest użytkownikowi.
Ostatecznie otrzymujemy konfigurację, która odpowiada diagramowi \ref{fig:konfiguracja-model}.
\begin{figure}[htb]
\begin{center}
\leavevmode
\includegraphics[width=0.8\textwidth]{konfiguracja-model}
\end{center}
\caption{Wizualizacja modelu złożonego z dwóch hostów (\code{alice} oraz
\code{bob}). Hosty połączone są poprzez swoje interfejsy (o nazwie \code{eth}).}
\label{fig:konfiguracja-model}
\end{figure}
\subsubsection{Laboratorium}
Laboratorium prezentuje zestaw dostępnych urządzeń i sposób, w jaki można się
z nimi komunikować.
W danym teście może być zdefiniowanych więcej urządzeń, niż wykorzystujemy, podobnie
jak w prawdziwym laboratorium sieciowym zwykle znajduje się wiecej sprzętu niż
wymagają tego proste ćwiczenia.
Sposób definiowania urządzeń jest bardzo podobny do tego zaprezentowanego w
modelu, dlatego od razu prezentujemy pełną konfigurację:
\begin{pythoncode}
create_laboratory('myroom')
defteros = add_device('defteros',
connection='tcp',
ip='192.168.1.100',
port='9999',
frontend='arete_slave')
marvin = add_device('marvin',
connection='tcp',
ip='192.168.1.101',
port='9999',
frontend='arete_slave')
d_eth0 = defteros.add_interface('eth0')
m_eth0 = marvin.add_interface('eth0')
\end{pythoncode}
Konfiguracja ta mówi, że w laboratorium \code{myroom} dysponujemy komputerami
\code{defteros} i \code{marvin}. Na obu uruchomiony jest demon
\code{arete\_slave}\footnote{Taki jest identyfikator modułu slave dostarczanego
razem z framework'iem.} i połaczyć się z nim można korzystając z TCP. Plugin
połączenia typu \code{tcp} korzysta z atrybutów \code{ip} i \code{port}. Oba
komputery posiadają interfejs sieciowy nazwany \code{eth0}. Przedstawione jest
to na diagramie \ref{fig:konfiguracja-laboratory}.
\begin{figure}[htb]
\begin{center}
\leavevmode
\includegraphics[width=0.8\textwidth]{konfiguracja-laboratory}
\end{center}
\caption{Wizualizacja laboratorium. W jego skład wchodzi komputer stacjonarny
\code{defteros} oraz laptop \code{marvin}. Oba urządzenia posiadają interfejs
sieciowy \code{eth0}.}
\label{fig:konfiguracja-laboratory}
\end{figure}
Funkcja \code{create\_laboratory}, podobnie jak w przypadku modelu, służy
uniemożliwieniu przypadkowego mieszania kilku definicji laboratoriów.
Funkcja \code{add\_device} zachowuje się analogicznie do \code{add\_host}.
Pierwszy, obowiązkow parametr to nazwa urządzenia. Framework wymaga
zdefiniowania kilku dodatkowych atrybutów:
\begin{itemize}
\item \code{connection} -- określa, z którego pluginu korzystać w celu
nawiązania komunikacji z danym urządzeniem,
\item \code{frontend} -- mówi, która wtyczka\footnote{ang. plugin} określa
sposób w jaki przebiega komunikacja z danym urządzeniem (protokół przesyłanych
komunikatów).
\end{itemize}
Więcej na temat wtyczek piszemy w rozdziale \fixref{referencja do rozdziału o
wtyczkach}.
Framework nie wymaga podawania parametrów \code{ip} oraz \code{port}. Są to
atrybuty, z których korzysta plugin \code{tcp} i w przypadku ich braku,
zgłosiłby błąd w czasie przetwarzania konfiguracji. Opis wymaganych atrybutów
znajduje się w dokumentacji każdego z pluginów.
Sposób tworzenia interfejsów jest identyczny jak w przypadku modelu. Ważne, aby
nazwy odpowiadały rzeczywistym nazwom, jakich używa się na danej platformie do
identyfikacji interfejsu, ponieważ nazwy te mogą pojawić się w generowanych
komendach i muszą być zrozumiałe przez system operacyjny urządzenia.
Podobnie jak w laboratorium może znajdować się więcej urządzeń niż jest
wymaganych w teście, tak samo urządzenie może posiadać więcej interfejsów niż
wymaga tego model. Nadmiarowe po prostu są ignorowane.
W przypadku laboratorium nie definiuje się połączeń między urządzeniami. Nie
miałoby to sensu ponieważ połączenia te muszą istnieć w rzeczywistości.
Wykonuje je użytkownik wpinając odpowiednie kable i nie ma możliwości zmiany
połączeń z poziomu oprogramowania.
\subsubsection{Odwzorowanie}
Odwzorowanie to najprostsza część konfiguracji. Jej zadaniem jest przypisanie
hostów z modelu do konkretnych urządzeń w laboratorium. Jeśli w modelu
zdefiniowano interfejsy, również należy je przypisać do interfejsów urządzeń.
Oczywiście interfejsy danego hosta można przypisywać tylko do interfejsów
urządzenia, z którym powiązany został host.
Do tworzenia tych przypisań służy funkcja \code{bind}, która przyjmuje dwa
argumenty. Na początku pliku widzimy też funkcję \code{create\_mapping}, której
zadanie jest analogiczne jak poprzednio w przypadku modelu i laboratorium.
\begin{pythoncode}
create_mapping('peers-to-myroom')
bind(alice, defteros)
bind(bob, marvin)
bind(a_eth, d_eth0)
bind(b_eth, m_eth0)
\end{pythoncode}
Warto zwrócić uwagę, że zmienne utworzone w dwóch poprzednich częściach
konfiguracji są dostępne w czasie wczytywania pliku z odwzorowaniem, dzięki czemu
odwoływanie się do stworzonych wcześniej obiektów jest łatwiejsze.
Stworzone odwzorowanie przedstawia diagram \ref{fig:konfiguracja-mapping}.
\begin{figure}[htb]
\begin{center}
\leavevmode
\includegraphics[width=0.8\textwidth]{konfiguracja-mapping}
\end{center}
\caption{Odwzorowanie między modelem i laboratorium.}
\label{fig:konfiguracja-mapping}
\end{figure}
Wymaga się, aby wszystkie hosty i interfejsy z modelu zostały odwzorowane. W
przeciwnym razie framework odmówi działania, ponieważ koncepcyjnemu urządzeniu
nie będzie odpowiadać żadna realizacja sprzętowa, na której można by wykonać
zadane akcje.
\subsubsection{Plan}
Trzy poprzednie części konfiguracji opisywały test od strony sprzętowej --
statycznej. Plan służy do określenia co urządzenia występujące w teście będą
robić. Kod poniżej przedstawia prosty plan, w którym host wykonuje polecenie
\code{ping}.
\begin{pythoncode}
create_schedule('schedule_ping')
test_end_policy('complete', setup_phase_delay=1.0)
append_schedule('alice', [('ping', at(0), shell('ping @{bob.eth.ip}')])
\end{pythoncode}
Tym razem framework również udostępnia funkcję (\code{create\_schedule}) do
zapewnienia, że użyjemy tylko jednego planu.
Funkcja \code{test\_end\_policy} służy określeniu kiedy test się zakończy.
Wartość ta powinna być zrozumiała dla wybranej wtyczki frontendu. W powyższym
przykładzie test zostanie zakończony po wykonaniu wszystkich komend. Parametr
\code{setup\_phase\_delay} określa jaki margines czasowy (w sekundach)
przeznaczyć na wykonanie komend konfiguracyjnych. Jest to parametr opcjonalny,
domyślnie wynoszący zero.
Wywołanie funkcji \code{append\_schedule} jest bardziej złożone, poświęcimy mu
zatem więcej czasu.
Pierwszy argument to nazwa hosta z modelu, na którym wykonane mają zostać
przekazywane polecenia. Z tego powodu plan jest związany z konkretnym modelem -
jeśli na jednym modelu realizujemy tylko jeden plan - często będzie definiowany
w tym samym pliku.
Drugi argument to lista trójek składających się z:
\begin{itemize}
\item nazwy komendy,
\item strategii wykonania,
\item komendy
\end{itemize}
Nazwa komendy to po prostu napis ją identyfikujący. Dla danego hosta musi być
unikalna, ponieważ możliwe jest odwoływanie się do jednej komendy z drugiej.
Strategia wykonania mówi w jaki sposób komenda ma być uruchamiana. W tym
wypadku \code{at(0)} zleca jednokrotne wykonanie w zerowej sekundzie testu.
Strategie muszą być wybierane z uwzględnieniem możliwości wybranej wtyczki
frontendu (patrz opis laboratorium). Konstruktor \code{at} tworzy obiekt typu
\code{RunPolicy}. Użytkownik może definiować własne strategie. Opis dostępnych
strategii, a także informacje o ich tworzeniu znajdują się w rozdziale
\fixref{ref do rozdziału}.
Ostatni element trójki to komenda do wykonania. Jest ona enkapsulowana w
obiekcie typu \code{Command}, którego klasa dostarcza interfejsu
potrzebnego do przekazania samej komendy oraz jej typu (w tym wypadku jest to
polecenie powłoki), ale także do kilku pomocniczych zadań jak dołączania
zasobów czy stworzenia testów poprawności środowiska (np. sprawdzenia czy
potrzebny program jest dostępny na urządzeniu).
Warto zwrócić uwagę na składnię \code{@\{...\}}, która pozwala w treści komendy
podstawiać wartości zależne od innych elementów konfiguracji. W przykładzie
powyżej ciąg \code{@\{bob.eth.ip\}} zostanie zamieniony wartością atrybutu
\code{ip} interfejsu \code{eth} hosta \code{bob}.
Wydawać się może, że taki rozbudowany interfejs będzie niewygodny w użyciu, ale
zwięzłość i funkcje narzędziowe dostępne wraz z frameworkiem pozwalają w prosty
i ekspresywny sposób przekazać to, co chcemy osiągnąć. Przykładem może być taki
plan:
\begin{pythoncode}
dccp = flow('dccp',
server_command='iperf -s -p 4099 -d',
client_command='iperf -c @{server.ip} -p 4099 -d')
append_schedule('alice', dccp.server(start=0, end=30))
append_schedule('bob', dccp.client(start=1, end=29, server='alice'))
\end{pythoncode}
Fragment ten używa pomocniczej klasy \code{flow} do wygenerowania komend
uruchamiających i kończących aplikacje klienta i serwera. Przed testem
sprawdzone zostanie również, czy dostępny jest używany program \code{iperf}.
W teście tym serwer będzie uruchomiony między 0 a 30 sekundą na
hoście \code{alice}, klient natomiast połączy się z nim w 1 sekundzie i rozłączy
w 29.
Inne przykłady definicji planów można znaleźć w rozdziale prezentującym
przykładowe zastosowania \fixref{ref do rozdziału Przykłady}. Ich lektura
pozwoli lepiej zrozumieć w jaki sposób wykorzystać dostępny interfejs.
\subsubsection{Pozostałe}
Aby umożliwić prosty mechanizm synchronizacji zadań na hostach wykonujących
test, w sytuacjach gdzie trudno przewidzieć ramy czasowe wykonywanych operacji,
wprowadziliśmy mechanizm powiadomień.
Po stworzeniu planu, dostępna jest funkcja \code{create\_trigger}, która tworzy
wyzwalacz o wartości podanej przy tworzeniu.
\begin{pythoncode}
create_trigger('stop-server', 3)
\end{pythoncode}
Powyższy wyzwalacz o nazwie \code{stop-server} zostanie aktywowany, kiedy moduł
master otrzyma 3 powiadomienia dla tego wyzwalacza. Nie jest istotne, z jakich
źródeł one pochodzą (np. jedno urządzenie może wysłać wszystkie trzy).
Demonstracja zastosowania dostępna jest w rozdziale \fixref{Przykłady}.
Ostatni pomocniczy mechanizm dostępny w konfiguracji to możliwość
parametryzowania testów. Został on wprowadzony z myślą o ułatwieniu wykonywania
tego samego testu z różną wartością parametrów, jak np. ilością uczestników.
Wartości parametrów mogą być definiowane w linii polecenia, dzięki czemu łatwo
jest stworzyć skrypt uruchamiający test dla różnych wartości. Program może też
zapytać o podanie wartości dla parametrów, których wartości nie zostały
określone. Można dzięki temu w bezpieczny sposób korzystać z haseł, nie trzeba
ich bowiem zapisywać w plikach tekstowych.
\begin{pythoncode}
from config.Global import parameters
add_host(
'milan',
ip = parameters['ip'],
port = parameters['port'])
add_device(
'm103',
password = parameters.get_secure('password'))
\end{pythoncode}
Powyższy przykład najpierw importuje obiekt \code{parameters} dostarczający
odpowiedni interfejs. Operator nawiasów kwadratowych powoduje zwrócenie
wartości parametru \code{ip} (taki sam efekt ma metoda \code{get}). Metoda
\code{get\_secure} różni się tylko tym, że w przypadku kiedy program zapyta
użytkownika o wprowadzenie wartości, zostanie użyty tryb, w którym tekst
zostanie ukryty\footnote{Pod warunkiem, że używany przez nas terminal umożliwia
wprowadzanie znaków bez wyświetlania ich.}.
Gdybyśmy uzupełnili powyższą konfigurację o brakujące elementy, przy
uruchomieniu wartości parametrów można byłoby podać w ten sposób:
\begin{textcode}
% arete -c parameters_example --set ip=192.168.2.1 --askparams
No value set for requested parameter 'port'. Please provide it now.
port = 2710
No value set for requested parameter 'password'. Please provide it now.
Note that secure value has been requested. The input you type won't be echoed
to the screen.
password =
\end{textcode}
Co zaowocowałoby przekazaniem do konfiguracji wartości \code{192.168.2.1} dla
parametru \code{ip}, \code{2710} dla parametru \code{port} oraz nie
wyświetlonej na ekranie wartości hasła dla parametru \code{password}.
\subsection{Podsumowanie}
Zaprezentowany sposób konfiguracji dostarcza prostego interfejsu pozwalającego
budować test poprzez wiele wywołań funkcji. Dzięki zastosowaniu istniejącego
języka programowania użytkownik nie musi uczyć się nowej składni, a dostarczony
interfejs może dowolnie rozszerzyć do swoich potrzeb.
Podział konfiguracji na model, laboratorium, odwzorowanie i plan umożliwia
wielokrotne użycie tych samych definicji bez konieczności powtarzania się. W
wyraźny sposób oddziela to też koncepcję testu od jego sprzętowej realizacji.
\section{Wykonawcza część frameworku}
Zdefiniowany w konfiguracji test jest wykonywany przez moduł Master. Odpowiada
on za wczytanie i walidację konfiguracji, połączenie z występującymi w teście
urządzeniami, przesłanie im przygotowanej konfiguracji, uruchomienie i
nadzorowanie testu oraz pobranie wyników. Tam, gdzie działanie zależne
jest od specyficznej konfiguracji testu, użytkownik ma możliwość dostarczenia
własnej implementacji potrzebnych algorytmów za pomocą mechanizmu wtyczek.
W dalszej części omawiamy te miejsca i wyjaśniamy w jaki sposób zaimplementować
własne wtyczki realizujące odpowiednią funkcjonalność.
\subsubsection{Komunikacja z modułem Slave}
Moduł Master w czasie testu kontroluje pracę uczestniczących w nim urządzeń.
Dialog z nimi odbywa się za pośrednictwem klas implementujących interfejs
\code{FrontendPlugin}. Obiekty Frontend mają za zadanie enkapsulować rzeczywisty
protokół komunikacji i sposób pracy urządzenia, udostępniając modułowi Master
spójnego sposobu nadzoru nad każdym rodzajem urządzenia.
Zadania frontendu to między innymi: synchronizacja z urządzeniem, przesłanie
konfiguracji testu, pobieranie wyników. Wykonywanie tych czynności jest zlecane
przez moduł Master, frontend tłumaczy te rozkazy na protokół zrozumiały przez
kontrolowane urządzenie i przesyła mu odpowiednie instrukcje.
Sam sposób dostarczania rozkazów jest poza zasięgiem klas \code{FrontendPlugin}.
Odpowiadają za to pluginy typu \code{ConnectionPlugin}. Frontend nie musi
wiedzieć, że rozkazy przesłane będą połączeniem TCP, wystarczy, że od niższej
warstwy otrzyma interfejs do wysyłania i odbierania danych. Więcej informacji o
interfejsie \code{ConnectionPlugin} dostępnych jest w dalszej części tego
rozdziału.
Diagram sekwencji \ref{fig:arete-master-controller-frontend-sequence}
przedstawia sposób, w jaki moduł kontrolny steruje pracą modułów frontend.
Należy zwrócić uwagę, że obiektów frontend w jednym teście występuje tyle, ile
hostów skonfigurowano w modelu.
\begin{figure}[htb]
\begin{center}
\leavevmode
\includegraphics[width=1\textwidth]{arete-master-controller-frontend-sequence}
\end{center}
\caption{Współpraca modułów Controller i Frontend w prawidłowym przebiegu testu.}
\label{fig:arete-master-controller-frontend-sequence}
\end{figure}
Praca kontrolera rozpoczyna się od stworzenia obiektów frontend korzystając z
wtyczek określonych w konfiguracji testu. Frontend przy tworzeniu otrzymuje
konfigurację podlegającego mu urządzenia, a także klasę, której powinien użyć
do nawiązania z nim połączenia.
Następnie frontendy instruowane są do przesłania konfiguracji testu (komunikat
\code{deploy\_configuration}). Protokół w jakim odbywa się komunikacja jest z
punktu widzenia kontrolera nieistotny.
W kolejnej fazie frontendy mają poinstruować urządzenia, aby rozpoczęły test
poprawności odebranej konfiguracji (komunikat \code{start\_sanity\_check}).
Urządzenie ma w tym momencie wykonać komendy z fazy sprawdzania poprawności.
Kontroler czeka, aż każdy frontend otrzyma od urządzeń status poprawności
(komunikat \code{wait\_sanity\_check}). Test jest przerywany, jeśli któreś z
urządzeń stwierdzi, że konfiguracja nie jest poprawna.
Po tym teście kontroler rozpoczyna wykonanie testu (komunikat
\code{start\_test}). W czasie trwania testu za pomocą komunikatu
\code{check\_test\_end} kontroler pyta frontendów czy test na podległym
urządzeniu zakończył się.
Pobieranie wyników (komunikat \code{fetch\_results}) odbywa się, gdy test
zakończy się wykonywać na wszystkich urządzeniach.
Powyższy schemat działania może zostać zaburzony w przypadku wystąpienia
błędów, jak zerwane połączenie lub urządzenie, którego konfiguracja nie spełnia
wymogów testu. W takich wypadkach kontroler wykonuje na wszystkich frontendach
metodę \code{abort\_test}, aby powiadomić urządzenia, że dalsze wykonywanie
testu należy przerwać.
Zamieszczony diagram sekwencji pomija również możliwość komunikacji w obrębie
metody \code{check\_test\_end}. Frontend w tej metodzie może nawiązywać
komunikację z kontrolowanym urządzeniem, np. w celu sprawdzenia czy test się
zakończył. Ponieważ metoda ta jest wywoływana cyklicznie na wszystkich
frontendach w teście, istotne jest, aby wykonanie jej miało charakter
nieblokujący. W przeciwnym razie jeden frontend mógłby zablokować innym
możliwość komunikacji.
W czasie komunikacji frontend może także odbierać powiadomienia dla
zdefiniowanych wyzwalaczy. Jeżeli kontroler stwierdzi, że któryś z wyzwalaczy
należy aktywować, wykonana na frontendzie metodę \code{trigger}, przekazując w
parametrach jego nazwę.
Ostatni aspekt implementacji frontendu to wykonywanie testu w trybie
bezpołączeniowym. Frontend z konfiguracji otrzymuje informację na temat
polityki zakończenia testu. Jeżeli jest to możliwe, powinien on na czas
wykonywania testu zamknąć połączenie z kontrolowanym urządzeniem i ponownie
otworzyć je dopiero, kiedy będzie pobierał wyniki. Dzięki temu urządzenia mogą
na czas testu zmieniać swoją konfigurację sieciową bez powodowania błędów
komunikacyjnych.
Zainteresowanych szczegółami implementacji odsyłamy do kodu źródłowego
\code{AreteSlaveFrontend} -- standardowo dostępnej wtyczki typu frontend.
\subsubsection{AreteSlaveFrontend}
Jest to standardowo dostepna implementacja wtyczki frontend służąca komunikacji
z modułem Arete Slave opisanym szczegółowo w kolejnym rozdziale.
Aby skorzystać z tej wtyczki należy w konfiguracji urządzenia użyć nazwy
\code{arete\_slave}, jak w przykładzie poniżej:
\begin{pythoncode}
add_device('local1',
frontend='arete_slave',
synchronize=False,
...)
\end{pythoncode}
Opcja \code{synchronize} jest opcjonalna i domyślnie aktywna. Ustawienie jej na
\code{False} powoduje pominięcie kroku synchronizacji z zegarem zdalnego
urządzenia. Jeżeli komputery, na których wykonujemy test, synchronizowane są
niezależnym źródłem (np. korzystając z NTP), zaleca się wyłączyć wbudowany
algorytm synchronizacji wtyczki \code{AreteSlavePlugin}.
\subsection{Połączenie ze zdalnym urządzeniem}
Przez połączenie ze zdalnym urządzeniem będziemy tutaj rozumieć kanał
komunikacyjny pomiędzy frontendem i kontrolowanym przez niego urządzeniem.
Najprostszym przykładem takiego połączenia może być połączenie TCP. Nieco
trudniejszy technicznie, choć koncepcyjnie identyczny, jest tunel SSH.
Najważniejsze, żeby możliwe było wysyłanie i odbiór danych.
We frameworku Arete połączenia modelowane są przez pluginy implementujące
interfejs \code{ConnectionPlugin}. Jedyne zadanie tego interfejsu to
dostarczanie i odbiór danych z kontrolującego go Frontendu. Otrzymuje on także
pomocnicze rozkazy określające moment, w którym można nawiązać fizyczne
połączenie oraz kiedy należy je przerwać.
Standardowo udostępniamy dwie wtyczki typu \code{ConnectionPlugin}, są to
\code{TCPConnection} oraz \code{SSHConnection}. Zainteresowanych szczegółami
współpracy między frontendem a połączeniem odsyłamy do kodu źródłowego tych
wtyczek.
\subsubsection{TCPConnection}
Wtyczka ta służy do nawiązania połączenia korzystającego z protokołu TCP i może
być użyta w większości przypadków, kiedy nie jest wymagane szyfrowanie.
Poniżej przedstawiona jest przykładowa konfiguracja korzystająca z tej wtyczki.
Warto zwrócić uwagę na jej identyfikator \code{tcp}.
\begin{pythoncode}
add_device('local1',
connection='tcp',
ip='127.0.0.1',
port='9998',
...)
\end{pythoncode}
Atrybuty \code{ip} oraz \code{port} są oczywiście wymagane.
\subsubsection{SSHConnection}
Wtyczka \code{SSHConnection} (o identyfikatorze \code{ssh}) umożliwia
nawiązanie uwierzytelnionego i szyfrowanego połączenia korzystając z tunelu
SSH. Do jej pracy wymagana jest instalacja biblioteki \code{paramiko}.
Oto przykład konfiguracji urządzenia:
\begin{pythoncode}
add_device('local2',
connection='ssh',
ip='127.0.0.1',
username='bob',
keyfile=path.relative('id_rsa'),
...)
\end{pythoncode}
Wymagane atrybuty to oczywiście \code{ip} i \code{port}, a także
\code{username} oraz jeden z poniższych:
\begin{itemize}
\item \code{keyfile} dla uwierzytelnienia opartego o klucz RSA,
\item \code{password} dla uwierzytelnienia hasłem.
\end{itemize}
Na marginesie dodamy, że widoczna w przykładzie funckja \code{relative}
dostępna jest w module \code{utils.path} i powoduje zbudowanie ścieżki względem
bieżącego pliku konfiguracyjnego. Dzięki temu można umieścić plik
\code{'id\_rsa'} w tym samym katalogu, co plik z konfiguracją laboratorium.
\subsection{Sterowniki parametrów}
W tym fragmencie pracy wracamy do kwestii poruszonych w opisie konfiguracji.
Najpierw przypomnijmy, że host, urządzenie oraz interfejs mogły mieć określone
pewne -- dowolne -- atrybuty. Atrybuty te mają na celu w zwięzły sposób opisać
cechy jakich od elementu będziemy oczekiwać. Samo ich podanie niczego jednak nie
powoduje, framework musi jeszcze zostać poinformowany o ich znaczeniu.
Sterowniki parametrów\footnote{\fixme{kiepska nazwa}} służą do przetłumaczenia
wartości atrybutów na odpowiednie akcje. Rodzaj akcji jest zależny od atrybutu.
Dla przykładu, określenie atrybutu \code{ip} powinno owocować wykonaniem komend,
które na docelowym urządzeniu odpowiednio skonfigurują interfejs sieciowy.
Użytkownik ma pełną swobodę w definiowaniu atrybutów i określaniu ich
znaczenia. Konfiguracja jest jednak przewidziana w taki sposób, aby atrybuty
urządzeń laboratorium dotyczyły ich bieżącej konfiguracji, a te z modelu
opisywały stan, jaki chcemy osiągnąć. Przykładowo atrybut \code{ip} w modelu
będzie mówił, że w czasie testu dany interfejs ma mieć taki adres IP. W
przypadku interfejsu urządzenia, atrybut \code{ip} określa aktualny adres, w
zastanej konfiguracji sprzętowej.
Framework do sterowników przekazuje więc atrybuty określone w modelu. Poniżej
zamieszczony jest przykład prostego sterownika parametrów interfejsu,
konfigurującego właśnie adres IP.
\begin{pythoncode}
class InterfaceIP(InterfaceDriverPlugin):
def process_interface(self, cmd, host, interface, attributes):
if 'ip' not in attributes:
return
ip = interface['ip']
cmd.add_check('which ip')
args = {'ip': interface['ip'],
'dev': interface.bound()['name']}
cmd.add_setup('ip addr add {ip} dev {dev}'.format(**args))
cmd.add_cleanup('ip addr del {ip} dev {dev}'.format(**args))
attributes.remove('ip')
\end{pythoncode}
Dla każdego interfejsu wykonywane są wszystkie zarejestrowane sterowniki, aż
lista jego atrybutów będzie pusta. W ten sposób sterowniki mogą w czasie
wykonania decydować, czy dysponując dostarczonymi atrybutami są w stanie
wygenerować komendy. Aby realizować ten prosty algorytm, sterownik na początku
funkcji \code{process\_interface} sprawdza czy \code{'ip'} należy do listy
nieprzetworzonych atrybutów, a na końcu ten element z listy usuwa.
Parametr \code{cmd} to obiekt, w którym gromadzone są tworzone komendy. W fazie
przygotowania za pomocą polecenia \code{which} zostanie sprawdzone, czy program
\code{ip} jest dostępny. W fazie konfiguracji odpowiedni adres zostanie dodany,
a po zakończeniu testu usunięty z interfejsu.
Metoda \code{interface.bound()} zwraca związany (poprzez zdefiniowane
odwzorowanie) z tym interfejsem interfejs urządzenia. Dzięki temu mamy dostęp do
jego nazwy w rzeczywistym urządzeniu i możemy podać argument wymaganay przez
program \code{ip}.
Dokładne wyjaśnienie znaczenia każdej funkcji znajduje się w \fixref{rozdział
Reference}. Tutaj dodamy jeszcze tylko, że sterowniki dzielą się na te
przetwarzające argumenty hostów i te, które przetwarzają argumenty interfejsów.
Pluginy dziedziczą odpowiednio z klas \code{HostDriverPlugin} i
\code{InterfaceDriverPlugin}.
\subsection{Przesyłanie zasobów}
Komendy wykonywanie w czasie testu często wymagać będą pewnych zewnętrznych
danych, jak na przykład plik konfiguracyjny. Framework zapewnia możliwość
przesłania tego typu zasobów na urządzenia, gdzie będą one potrzebne.
W konfiguracji zasoby definiuje się funkcją \code{add\_resource}, której
argumentem jest obiekt implementujący interfejs \code{Resource}. Sama definicja
zasobu nie będzie wystarczająca, należy jeszcze podać, na których hostach ma on
być dostępny. W tym celu należy skorzystać z metody \code{use\_resource} klasy
\code{Model.Host}. Przedstawia to przykład poniżej.
\begin{pythoncode}
torrent = add_resource(File('torrent', 'test-file.torrent'))
alice.use_resource(torrent)
bob.use_resource(torrent)
\end{pythoncode}
Taka konfiguracja spowoduje, że na hosty \code{alice} i \code{bob} razem z
konfiguracją testu zostanie przesłany lokalny plik \code{test-file.torrent}.
Transfer danych realizowany jest przez klasę implementującą interfejs
\code{Resource}. Klasa ta musi umożliwiać transfer poprzez Frontend kontrolujący
dane urządzenie. Poniżej znajduje się przykład algorytmu, który pozwala przesłać
plik do modułu Arete Slave. Kod nie jest kompletny, służy tylko zobrazowaniu
sposobu działania.
\begin{pythoncode}
class File(Resources.Resource):
def __init__(self, ...)
...
def transfer_with_arete_slave(self, frontend):
with open(self._path, 'rb') as file:
frontend.output().write('file @{id=%(id)s} @{size=%(size)s}\n'
% {'id':self['name'], 'size':self._size})
frontend.output().write(file.read())
response = frontend.input().readline().strip()
if not resp.startswith('200'):
raise RuntimeError('Wrong response while transfering file')
\end{pythoncode}
Klasa implementująca interfejs \code{Resource} powinna dostarczać metod
\code{transfer\_with} dla każdego frontendu z jakim będzie używana. Nazwa
metody jest właściwie dowolna ponieważ uruchamiana jest ona z frontendu -- obie
klasy implementuje więc użytkownik, zalecamy jednak stosowanie schematu
\code{transfer\_with\_<frontend\_type>}.
Jeśli przed użyciem zasobu chcemy go jeszcze przygotować, framework Arete
pozwala zdefiniować potrzebne do tego komendy. Może to być użyteczne na
przykład przy pracy z archiwami, które przed testem mają zostać rozpakowane. Aby
skorzystać z tej możliwości należy zaimplementować metodę
\code{generate\_commands}.
\begin{pythoncode}
class Archive(Resources.Resource):
...
def generate_commands(self, cmd, host):
cmd.add_setup('tar zxf ...')
...
\end{pythoncode}
Metoda ta w argumentach otrzymuje instację klasy \code{HostCommands}, do której
może dodać nowe komendy. Dzięki przekazaniu argumentu \code{host} możliwe jest
sprawdzenie atrybutów zdefiniowanych w konfiguracji. Można w ten sposób
określić np. system operacyjny docelowego urządzenia i wygenerować komendy w
odpowiednim formacie.
Szersze omówienie interfejsu \code{Resource} znajduje się w rozdziale
\fixref{Reference}.
\subsection{Wtyczki typu Hook}
Wtyczki typu Hook umożliwiają rozszerzenie przedstawionego przez nas schematu
konfiguracji o własne algorytmu modyfikujące tworzony test. Aktualnie dostępny
jest tylko jeden hook, wykonywany po przygotowaniu konfiguracji, ale jeszcze
przed uruchomieniem testu.
Poniżej prezentujemy wtyczkę \code{Uname}, która go wykorzystuje.
\begin{pythoncode}
from common.Hooks import HookPlugin
class Uname(HookPlugin):
hook_name = 'uname'
def visit_configured_test(self, configured_test):
for host in configured_test.hosts.values():
host.commands.add_check('uname -a')
\end{pythoncode}
Metoda \code{visit\_configured\_test} jako parametr otrzyma przygotowaną
konfigurację. W przykładzie, do każdego zdefiniowanego hosta, dodajemy komendę,
dzięki której zapamiętamy wersję jądra systemu.
Hooki należy aktywować opcją linii poleceń \code{--hooks}. W tym wypadku
program należałoby więc uruchomić tak:
\begin{textcode}
% arete -c ... --hooks uname
\end{textcode}
Kod hooków musi zostać wcześniej załadowany. Jeżeli stanowi on część plików
konfiguracyjnych nie będzie to problemem. W innych wypadkach można skorzystać z
opcji \code{--plugins}.
\section{Przechowywanie wyników}
Wyniki testów zapisywane są w bazie danych SQLite. Schemat tabel przedstawia
rysunek \ref{fig:arete-master-db}.
\begin{figure}[htb]
\begin{center}
\leavevmode
\includegraphics[width=0.8\textwidth]{arete-master-db}
\end{center}
\caption{Schemat bazy danych modułu Master. Baza przechowuje wyniki
uruchamianych testów.}
\label{fig:arete-master-db}
\end{figure}
Główna tabela \code{tests} zawiera informacje dotyczące pojedyńczego wykonania
testu. Kolumna \code{id} to automatycznie generowany, unikalny identyfikator.
Kolejne uruchomienia tego samego testu będą posiadały inne identyfikatory.
Kolumna \code{comment} przeznaczona jest do przechowywania dowolnych komentarzy
użytkownika (podawane przy uruchamianiu testu). Naszym zamiarem jest
umożliwienie dokonywania pewnych adnotacji, jak np. opisania szczególnych
warunków wykonywania testu. W kolumnach \code{model}, \code{laboratory},
\code{schedule} i \code{mapping} zapisywane są nazwy podawane w argumentach
odpowiednich funkcji, np. \code{create\_model}. O ile użytkownik
dobierze opisowe nazwy, kolumny te pozwolą określić, która konfiguracja została
użyta w teście.
Tabela \code{nodes} wymienia wszystkie urządzenia zdefiniowane w modelu podając
ich nazwy (kolumna \code{node}). Kolumy \code{start\_time} oraz \code{duration}
to data (z zegaru urządzenia) rozpoczęcia testu oraz czas jego wykonania.
Wartości te nie są określone w przypadku, kiedy test na urządzeniu nie wykona
się (np. z powodu błędów).
W tabeli \code{commands} zapisane są komendy uruchamiane na urządzeniach.
Kolumna \code{phase} określa fazę testu, natomiast \code{type} mówi o rodzaju
komendy (np. komenda powłoki systemowej).
Ponieważ komendy mogą wykonywać się wielokrotnie, wyniki każdego uruchomienia
zapisane są w tabeli \code{invocations}. Ponieważ niektóre komendy (jak np.
\code{notify}) nie produkują żadnych wyników, wartości w kolumnach
\code{output}, \code{duration} oraz \code{return\_code} mogą pozostać puste.
\section{Uruchamianie}
Program \code{arete} w celu poprawnej pracy wymaga podania argumentów
określających test. W przypadku minimalnym należy tylko wskazać zestaw plików
zawierających kompletną konfigurację. Wszystkie argumenty programu przedstawione
są poniżej.
\begin{description}
\item[\code{--help}]
Wyświetla informacje dotyczące opcji programu.
\item[\code{--config}]
Określa pliki z konfiguracją testu. Opcja przyjmuje wiele parametrów.
\item[\code{--set}]
Definiuje wartości parametrów używanych w konfiguracji. Przyjmuje wiele
parametrów w formacie \code{nazwa=wartość}.
\item[\code{--askparams}]
Zezwala na interaktywne podanie brakujących wartości parametrów, tj. używanych w
konfiguracji, a nie określonych opcją \code{--set}. Pozwala w bezpieczny sposób
korzystać z haseł.
\item[\code{--plugins}]
Określa dodatkowe ścieżki, z których ładowane będą wtyczki. Opcja przyjmuje
wiele parametrów.
\item[\code{--hooks}]
Określa nazwy \fixme{hook'ów} do uruchomienia po zbudowaniu konfiguracji.
Wybrane hooki muszą być dostępne w wybranych katalogach wtyczek (opcja
\code{--plugins})
\item[\code{--map}]
Definiuje odwzorowania z poziomu linii poleceń. W takim wypadku nie należy
podawać go w konfiguracji testu. Można podać wiele odwzorować w formacie
\code{host:urządzenie}.
\item[\code{--message}]
Określa komentarz dotyczący danego wykonania testu.
\item[\code{--database}]
Określa ścieżkę do bazy danych SQLite, która ma być użyta do zapisania wyników.
\end{description}
\end{document}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment