Created
November 17, 2010 21:55
-
-
Save qsorix/704170 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\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