systemu operacyjnego, pod kontrolą którego będą pracować maszyny wirtualne. Celem tej pracy było:
\begin{itemize}
\item napisanie programu serwera obrazów maszyn wirtualnych, którego zadaniem jest ich rejestrowanie, przypisywanie oraz dystrybuowanie
\item stworzenie klienta synchronizującego stan maszyny klienckiej ze stanem obecnym na serwerze, pobierającego obrazy, wyświetlającego ekran wyboru systemy do uruchomienia oraz obsługującego ich uruchmianie poprzez mechanizm QEMU wraz z KVM
\item przygotowanie konfiguracji dla serwera opartego o system operacyjny Linux:
\begin{itemize}
\item do automatycznego instalowania systemu nadzorcy dla maszyn klienckich
\item do obsługi sieciowej podłączonych maszyn (przydzielanie adresów IP, wskazywanie na serwer konfiguracji)
\item do zarządzania maszynami klienckimi
\end{itemize}
\item wdrożenie rozwiązania w symulowanym środowisku testowym
\end{itemize}
\paragraph{}
Prezentowana praca podzielona została na kilka rozdziałów:
\item wymagania i narzędzia: jakie są wymagania dla aplikacji, jakie narzędzia zostały wykorzystane do rozwiązania problemu, ich opis oraz uzasadnienie wyboru
\item specyfikacja zewnętrzna: specyfikacja opisująca całe rozwiązanie z perspektywy użytkowników końcowych, z wyszczególnionymi elementami składowymi rozwiązania
\item specyfikacja wewnętrzna: specyfikacja opisująca techniczne aspekty rozwiązania, z perspektywy osoby technicznej zaznamiającej się z kodem źródłowym oprogramowania
\item weryfikacja i walidacja: wyniki testów przeprowadzonych na testowym środowisku wdrożeniowym, ich analiza
\item podsumowanie i wnioski: prezentacja wniosków wynikających z analizy wyników testów, krytyka proponowanego rozwiązania i propozycje poprawek
serwer SFTP sprawdza dostęp do maszyny klienta poprzez SSH (Secure SHell). Jeżeli test przebiegnie pomyślnie, nawiązywane jest połączenie SFTP, przez
które rozpoczyna się transfer plików. Protokół obsługuje przesył wielowątkowy, gdzie każda operacja transferu ma przypisany unikalny numer
identyfikacyjny, nadawany przez klienta, dzięki czemu serwer może odpowiadać na zapytania klienta asynchronicznie i bez zachowania kolejności.
Aby poprawić wydajność, klient wysyła często wiele zapytań zanim zatrzymuje się i czeka na odpowiedzi.\cite{bib:ssh-article}
\paragraph{SSH}
SSH (Secure SHell) to standard protokołów, który miał w założeniu stanowić bezpieczną alternatywę dla protokołu telnet.
Do autoryzacji wykorzystuje kryptografię klucz publiczy - klucz prywatny, gdzie w najprostszej postaci
pary kluczy sa generowane automatycznie i służą do zabezpieczenia połączenia sieciowego, natomiast sama autoryzacja
odbywa się za pomocą hasła użytkownika systemu zdalnego.\cite{bib:ssh-rfc} Obecnie najpopularniejszą implementacją protokołu SSH jest OpenSSH, stworzona przez
część członków projektu OpenBSD. Jest wykorzystywana przez większość najpopularniejszych systemów operacyjnych, takich jak
różne dystrybucje Linux, macOS, Microsoft Windows, czy OpenBSD.\cite{bib:openssh-usage}
\paragraph{Serwer HTTP}
Protokół HTTP (HyperText Transfer Protocol) powstał jako część WWW (World Wide Web) i miał służyć do transferu dokumentów hipertekstowych.
Prace nad nim były prowadzone przez Tima Berners-Lee zatrudnionego w CERN (Conseil Européen pour la Recherche Nucléaire) od 1989 roku
(pierwsza propozycja systemu hipertekstowego opartego o Internet),
gdzie implementacja nastąpiła w 1990 roku, by już rok później pojawiły się pierwsze serwery poza organizacją macierzystą twórcy\cite{bib:http-history}.
W 1997 roku powstała pierwsza ustandaryzowana wersja protokołu, HTTP/1.1, która została wykorzystana w tej pracy.\cite{bib:http-rfc}
HTTP to obecnie bardzo wszechstronne narzędzie, pozwalające obecnie na przesyłanie dowolnych danych poprzez sieć Internet.
Jako protokół jest podstawą współczesnej sieci Web, służy do obustronnego transferu danych pomiędzy serwerami i klientami.
Komunikacja jest rozpoczynana zawsze przez klienta. Tok takiej tranzakcji jest następujący (opis dotyczy wersji HTTP/1.1):
\item otwarcie połączenia TCP (Transmission Control Protocol) z serwerem
\item wysłanie zapytania HTTP
\item odebranie odpowiedzi HTTP od serwera
\item zamknięcie połączenia, lub ponowne jego użycie (do wysłania kolejnego zapytania)
\end{itemize}
Wiadomości HTTP (dla wersji HTTP/1.1 i starszych) mają ustandaryzowany format, czytelny dla człowieka i zapisywalny w postaci tekstu.
Format różni się dla zapytań i odpowiedzi, gdzie zapytanie posiada następujące pola\cite{bib:http-overview}:
\begin{itemize}
\item metoda HTTP: definiuje operację, którą chce przeprowadzić klient. Może to być np. operacja pobrania danych (metoda GET), czy wysłania danych (metoda POST). Standard HTTP/1.1 zawiera poniższe metody\cite{bib:http-methods}:
\begin{itemize}
\item CONNECT
\item DELETE
\item GET
\item HEAD
\item OPTIONS
\item POST
\item PUT
\item TRACE
\end{itemize}
\item ścieżka dostępu do danych
\item wersja protokołu HTTP
\item opcjonalne nagłówki
\item ciało zapytania, dla metod obsługujących przesył danych z klienta do serwera
\end{itemize}
Natomiast struktura odpowiedzi jest następująca\cite{bib:http-overview}:
\begin{itemize}
\item wersja protokołu HTTP
\item kod statusu: jeden z kodów HTTP oznaczający czy zapytanie zostało obsłużone oraz jaki był efekt jego obsługi
\item wiadomość statusu: krótka wiadomość opisująca znaczenie kodu statusu
\item nagłówki HTTP, podobnie do zapytania
\item ciało odpowiedzi, jeżeli wymagane
\end{itemize}
Z perspektywy prezentowanego projektu ważną cechą protokołu HTTP jest możliwość przesyłu dowolnych danych w ten sam sposób. Zostało to wykorzystane do
stworzenia serwera, który potrafi przesyłać dane do klientów w formacie JSON (JavaScript Object Notation), czyli tekstowym formacie
opisującym (w tym konkretnym przypadku) konfigurację oraz w formacie binarnym, co jest wykorzystywane do przesyłu obrazów maszyn wirtualnych.
Powyższe rozwiązanie zostało wybrane właśnie z powodu łatwości połączenia części przesyłającej konfigurację z serwera do klientów oraz części
przesyłającej obrazy. W dalszej części pracy opisano dokładną architekturę tego rozwiązania, jej zalety oraz wady.
\paragraph{Maszyny wirtualne}
Maszyna wirtualna to w najprostszym ujęciu programowa implementacja fizycznego urządzenia, która wykonuje zadany program w sposób identyczny
do tego urządzenia. Aplikacja, która to umożliwia jest nazywana nadzorcą (ang. hypervisor). Jej zadaniem jest tłumaczenie zapytań programów
do natywnego sprzętu na zapytania do sprzętu, na którym działa uruchomiona maszyna wirtualna. Istnieje kilka popularnych rodzajów wirtualizacji\cite{bib:virtualization-article}:
\begin{itemize}
\item pełna wirtualizacja z translacją binarną, rys. \ref{fig:fullvirt-diagram} przedstawia schemat działania takiego systemu
\item wirtualizacja ze wsparciem sprzętowym, rys. \ref{fig:hwvirt-diagram} przedstawia schemat działania takiego systemu
\item wirtualizacja wspierana przez system operacyjny i parawirtualizacja, rys. \ref{fig:paravirt-diagram} przedstawia schemat działania takiego systemu
W prezentowanym projekcie wykorzystano model ze sprzętowym wsparciem dla wirtualizacji poprzez KVM (Kernel Virtual Machine) wraz z QEMU (Quick EMUlator) i technologie
Intel VT-d/AMD-V, pod kontrolą systemu operacyjnego Ubuntu Linux w wersji 22.04 LTS.
Wirtualizacja urządzeń We/Wy jest realizowana poprzez mechanizm IOMMU (Input/Output Memory Management Unit). Działa on na analogicznej zasadzie co MMU, zapewniając kontrolę
oraz tłumaczenie adresów logicznych urządzeń do ich adresów fizycznych.\cite{bib:amd64-specs} Pozwala to na przekierowanie
urządzenia bezpośrednio pod kontrolę systemu operacyjnego uruchomionego w maszynie wirtualnej, pomijając sterownik systemu zarządcy.
Technologia ta może zostać wykorzystana do przekazania w taki sposób urządzeń wymagających bardzo niskich opóźnień i dużej przepustowości,
na przykład kart graficznych, lub kart sieciowych. Wtedy pełną kontrolę nad sprzętem otrzymuje system operacyjny gościa w maszynie
wirtualnej, a narzut wirtualizacji na wydajność jest zminimalizowany.
\paragraph{DHCP}
Protokół DHCP (Dynamic Host Configuration Protocol) odpowiada za automatyczne przypisywanie adresów IP oraz wysyłanie konfiguracji
sieciowej do urządzeń. Jego założeniem była także iteroperatywność z już wtedy istniejącym protokołem BOOTP (BOOTstrap Protocol), przez co
pojedyncze paczki danych (nazwane wiadomościami) są te same dla obu protokołów (z kilkoma odstępstwami).\cite{bib:dhcp-rfc}
Format wiadomości DHCP jest następujący:\cite{bib:dhcp-rfc}
\begin{itemize}
\item OP (1 bajt): typ wiadomości
\item HTYPE (1 bajt): typ adresu karty sieciowej
\item HLEN (1 bajt): długość adresu karty sieciowej
\item HOPS (1 bajt): ustawiane przez klienta na 0 (zero); używane przez agentów pośredniczących
\item XID (4 bajty): numer identyfikacyjny (ID) tranzakcji; używany do rozpoznania wiadomości przeznaczonych dla konkretnego klienta
\item SECS (2 bajty): liczba sekund od rozpoczęcia procesu odnawiania lub nabywania adresu; ustawiane przez klienta
\item FLAGS (2 bajty): flagi
\item CIADDR (4 bajty): adres IP klienta; uzupełniany tylko, jeżeli klient może odpowiedzieć na zapytanie ARP (Address Resolution Protocol) i jest w stanie BOUND, RENEW, lub REBINDING
\item YIADDR (4 bajty): ,,twój'' (klienta) adres IP
\item SIADDR (4 bajty): adres następnego serwera do użycia podczas pobierania
Zdefiniowano następujące wiadomości w protokole DHCP:\cite{bib:dhcp-rfc}
\begin{itemize}
\item DHCPDISCOVER - Klient wysyła pakiet rozgłoszeniowy, aby zlokalizować dostępne serwery
\item DHCPOFFER - Serwer odpowiada klientowi na zapytanie DHCPDISCOVER z proponowaną konfiguracją
\item DHCPREQUEST - Klient odpowiada serwerowi (a) żądając oferowanych parametrów od jednego serwera i ignorując pozostałe, (b) potwierdzając prawidłowość otrzymanej wcześniej konfiguracji, lub (c) przedłużając dzierżawę adresu IP
\item DHCPACK - Serwer odpowiada klientowi parametrami konfiguracji, wraz z przyznanym adresem IP
\item DHCPNAK - Serwer odpowiada klientowi zaznaczając, że konfiguracja żądana przez klienta jest nieprawidłowa, lub dzierżawa adresu wygasła
\item DHCPDECLINE - Klient odpowiada serwerowi, że dany adres sieciowy jest już w użytku
\item DHCPRELEASE - Klient odpowiada serwerowi, zwalniając dany adres sieciowy i anulując bieżącą dzierżawę
\item serwer wysyła klientowi proponowaną konfigurację poprzez wiadomość \texttt{DHCPOFFER}
\item klient żąda zadanej konfiguracji poprzez wiadomość \texttt{DHCPREQUEST}
\item serwer odpowiada klientowi wiadomością \texttt{DHCPACK}, jeżeli żądana konfiguracja jest poprawna, w przeciwnym przypadku odpowiada \texttt{DCHPNAK}
\item przy odłączaniu od sieci, klient wysyła wiadomość \texttt{DHCPRELEASE}, zwalniając zajmowany adres sieciowy
\end{itemize}
\paragraph{TFTP}
TFTP (Trivial File Transfer Protocol) to nieskomplikowany protokół przesyłu plików. Jego jedynym zadaniem jest
odczyt pliku po stronie serwera i przesłanie go do klienta. Nie obsługuje listowania plików, ani uwierzytelniania.\cite{bib:tftp-rfc}
\item\texttt{NETASCII}, czyli zmodyfikowana forma formatu \texttt{ASCII}, rozszerzona do długości 8 bitów i zawierająca dodatkowe znaki kontrolne
\item\texttt{OCTET}, czyli format przesyłu surowych bajtów danych
\item\texttt{MAIL}, uznany za przestarzały w specyfikacji RFC1350\cite{bib:tftp-rfc}
\end{itemize}
Proces transferu plików jest następujący\cite{bib:tftp-rfc}:
\begin{itemize}
\item klient wysyła zapytanie RRQ (read request/żądanie odczytu), lub WRQ (write request/żądanie zapisu), zawierające nazwę pliku i tryb transferu do serwera na port 69
\item serwer odpowiada pakietem OPTIONS ACK (jeżeli były użyte) i pakietem ACK w przypadku zapytania WRQ; w przypadku zapytania RRQ odpowiedź to od razu pierwszy pakiet żądanych danych
\item maszyna będąca źródłem pliku wysyła go w ponumerowanych segmentach (domyślnie o wielkości 512 bajtów); maszyna odbierająca na każdy segment odpowiada pakietem ACK
\item ostatni segment jest rozpoznawany po wielkości, która jest mniejsza od poprzednich (domyslnie musi mieścić się w przedziale od 0 bajtów do 511 bajtów)
\item jeżeli odpowiedź ACK dla danego segmentu nigdy nie została otrzymana przez maszynę wysyłającą, po upływie określonego czasu (timeout) segment jest retransmitowany
\end{itemize}
\paragraph{PXE}
PXE (Preboot eXecution Environment) to specyfikacja ustandaryzowanego środowiska pozwalającego na uruchamianie oprogramowania
poprzez sieć. Rozwiązanie takie pracuje w architekturze klient-serwer, gdzie od klienta wymagane jest jedynie posiadanie
odpowiedniej karty sieciowej (wspierającej to rozwiązanie) oraz wsparcie w oprogramowaniu inicjalizującym maszyny, odpowiedzialnym
za uruchamianie oprogramowania (na przykład BIOS/UEFI dla maszyn o architekturze X86). Część serwera jest zbiorem już wcześniej istniejących
rozwiązań, składającym się z serwera DHCP oraz serwera TFTP. \cite{bib:pxe-specs}
Proces uruchamiania składa się z następujących kroków\cite{bib:pxe-specs}:
\begin{itemize}
\item wysłanie zapytania \texttt{DHCPDISCOVER} w trybie BROADCAST przez klienta
\item serwer DHCP odpowiada poprzez wiadomość \texttt{DHCPOFFER}; jeżeli serwer DHCP nie implementuje obsługi PXE, wysyła poprawną wiadomość nie zawierającą informacji o lokalizacji plików potrzebnych do uruchomienia minimalnego systemu, a maszyna klienta nie uruchamia się poprzez PXE. W przeciwnym przypadku, serwer DHCP poza zwykłymi informacjami przesyłanymi we wiadomości \texttt{DHCPOFFER} dodaje także informacje o lokalizacji serwera TFTP, gdzie znajdują się pliki potrzebne do uruchomienia minimalnego środowiska uruchomienowego, oraz o nazwach tych plików
\item klient PXE konfiguruje się danymi przesłanymi przez serwer DHCP oraz pobiera pliki uruchomieniowe do pamięci RAM (Random Access Memory); uruchamia otrzymane pliki
\item minimalne środowisko uruchomieniowe powinno pobrać pełen obraz instalacyjny lub obraz systemu do uruchomienia i obsłużyć jego urchomienie/instalację; ten krok najczęściej jest wykonywany poprzez bardziej skomplikowane i niezawodne protokoły, takie jak HTTP, lub NFS
\end{itemize}
Jako minimalne środowisko uruchomieniowe wykorzystuje się często zestaw jądra Linux oraz prostego obrazu initrd, które mają za zadanie
pobrać i uruchomić instalator pełnego systemu operacyjnego.
WakeOnLAN (w skrócie WOL) to technologia pozwalająca na zdalne uruchomienie urządzenia przez przesłanie specjalnego pakietu w sieci LAN (Local Area Network) poprzez adres rozgłoszeniowy.
Pakiet ten składa się z 6 bajtów wypełnionych jedynkami (w zapisie szesnastkowym: FF FF FF FF FF FF) oraz powtórzonego 16 razy adresu MAC urządzenia, które powinno zostać wzbudzone.
Aby maszyna potrafiła zinterpretować taki pakiet, wymagane jest wsparcie dla tej technologii w oprogramowaniu karty sieciowej oraz płyty głównej urządzenia.\cite{bib:wol-specs-amd}
\paragraph{Rozwiązania zarządzające flotą urządzeń klienckich}
Istnieje wiele rozwiązań mających na celu automatyzację instalacji i zarządzanie flotą komputerów w pracowniach informatycznych/biurach/itp.
Zapewniają one narzędzia do skonfigurowania automatycznej instalacji systemu i zdalnego nim zarządzania (między innymi dodawania, usuwania użytkowników, czy instalacji oprogramowania).
\paragraph{Microsoft Windows}
Dla systemów z rodziny Microsoft Windows zbiorem narzędzi do zarządzania instalacją systemu jest Windows Assessment and Deployment Kit (ADK).
Zawiera on pełny zestaw aplikacji służący do prekonfiguracji instalacji, czy stworzenia minimalnego środowiska uruchomieniowego dla PXE.
Zawarte w nim narzędzie WISM (WindowsSystem Image Manager) pozwala utworzyć pliki odpowiedzi dla instalatora systemu, automatyzujące proces instalacji.
Z kolei narzędzie DISM (Deployment Image Servicing and Management) daje możliwość modyfikacji elementów obrazu systemu, na przykład pozwalając na wbudowanie zewnetrznych aplikacji bezpośrednio w obraz instalatora.\cite{bib:adk-docs}
Po zainstalowaniu, Microsoft zaleca dla takiego przypadku użycia przyłączenie maszyny do usługi Active Directory (AD).
Jest to zestaw narzędzi wbudowanych w system Microsoft Windows Server pozwalający na zdalne, scentralizowane zarządzanie
wieloma klientami opartymi o system Microsoft Windows.\cite{bib:ad-docs}
\paragraph{Linux}
Z kolei dla systemów z rodziny Linux podobny efekt można uzyskać poprzez kombinację kilku narzędzi. Do automatyzacji procesu instalacji powstało narzędzie
cloud-init, zaprezentowane przez firmę Canonical. Pozwala ono na stworzenie ,,przepisu'' na podstawie którego instalator systemu między innymi
konfiguruje parametry, instaluje pakiety, tworzy użytkowników, czy importuje klucze SSH. W połączeniu z serwerem PXE możliwe jest
w ten sposób instalowanie tak samo skonfigurowanego systemu na wielu maszynach, w sposób w pełni zautomatyzowany.\cite{bib:cloudinit-docs}
Z kolei do zarządzania flotą urządzeń można wykorzystać narzędzia takie jak Ansible, Chef, czy Puppet. Umożliwiają wykonywanie skryptów
na wielu maszynach w sposób zautomatyzowany. Pozwalają one na zarządzanie użytkownikami, aplikacjami, a nawet potrafią synchronizować pliki.
Chef oraz Puppet używają procesu agenta, który odpytuje serwer konfiguracji i aplikuje wykryte zmiany. Ansible natomiast wykorzystuje
podejście, w którym to maszyna nadzorcy wysyła pierwsza konfigurację na maszyny zarządzane. Nie wymaga także instalacji dodatkowego
oprogramowania na maszynie zarządzanej, wystarczy jedynie uruchomiony serwer SSH oraz zainstalowany interpreter języka Python.
%\item osadzenie tematu w kontekście aktualnego stanu wiedzy (\ang{state of the art}) o poruszanym problemie
%\item studia literaturowe \cite{bib:artykul,bib:ksiazka,bib:konferencja,bib:internet} - opis znanych rozwiązań (także opisanych naukowo, jeżeli problem jest poruszany w publikacjach naukowych), algorytmów,
%\end{itemize}
%
%
% Wzory
% \begin{align}
% y = \frac{\partial x}{\partial t}
% \end{align}
% jak i pojedyncze symbole $x$ i $y$ składa sięw trybie matematycznym.
\item pełna automatyzacja procesu instalowania i konfiguracji systemu operacyjnego na urządzeniach
\item użycie maszyn wirtualnych
\item pozorna ,,bezstanowość'': po zakończeniu pracy w maszynie wirtualnej i jej wyłączeniu, kolejne jej uruchomienie przywróci pierwotny obraz dysku maszyny, bez zmian wprowadzonych przez poprzedniego użytkownika
\item proste tworzenie obrazów maszyn wirtualnych o zmodyfikowanej konfiguracji
\item łatwość użycia: użytkownicy końcowi powinni być w stanie w łatwy sposób obsługiwać maszyny klienckie
Do obsługi wirtualizacji wykorzystano narzędzie QEMU. Jest to zintegrowany system do emulacji procesorów poprzez tłumaczenie binarne, potrafi także korzystać z technologii KVM do uruchamiania
aplikacji/maszyn wirtualnych z minimalnym narzutem wydajnościowym.\cite{bib:qemu-main} W proponowanym
rozwiązaniu użyto trybu, gdzie QEMU korzysta z KVM, aby zapewnić jak najlepszą wydajność.
% \begin{itemize}
% \item wymagania funkcjonalne i niefunkcjonalne
% \item przypadki użycia (diagramy UML) -- dla prac, w których mają zastosowanie
% \item opis narzędzi, metod eksperymentalnych, metod modelowania itp.
% \item metodyka pracy nad projektowaniem i implementacją -- dla prac, w których ma to zastosowanie
% \end{itemize}
% TODO
\chapter{Specyfikacja zewnętrzna}
\label{ch:specyfikacja-zewnetrzna}
\paragraph{Wymagania sprzętowe - strona kliencka}
Maszyny klienckie, na których uruchomiona ma być aplikacja kliencka i maszyny wirtualne, powinny spełniać następujące wymagania:
\item uruchomić instalator klikając enter w oknie wyboru przedstawionym na rys. \ref{fig:boot-screen-pxe}
\item po restarcie wszystkich urządzeń, podmienić adres IP serwera w pliku config.yml i uruchomić skrypty ansible z katalogu \texttt{ansible-scripts} na serwerze:
Po wykonaniu wstępnej konfiguracji, należy zbudować obraz maszyny wirtualnej i zarejestrować go w systemie ich obsługi.
Aby zbudować obraz maszyny wirtualnej, wystarczy stworzyć obraz dysku w formacie qcow2 (np. \texttt{qemu-img create -f qcow2 ubuntu.qcow2 20G}),
a następnie uruchomić maszynę wirtualną z tym obrazem dysku i medium instalacyjnym, zainstalować oraz skonfigurować system operacyjny,
a następnie skopiować utworzony obraz dysku na serwer.
Będąc zalogowanym do powłoki na serwerze, należy umieścić obraz dysku w miejscu, gdzie użytkownik uruchamiający serwer ma dostęp do odczytu i zapisu plików.
Następnie, należy zarejestrować obraz w bazie danych serwera komendą \texttt{python fleetcontrol add\_image}, przykład użycia
przedstawiono na rys. \ref{fig:register-image-command}. Po dodaniu obrazu można zweryfikować, że obraz został prawidłowo zarejestrowany
poprzez komendę \texttt{python fleetcontrol print\_images}. Dla skonfigurowanego serwera dała ona rezultat przedstawiony na rys. \ref{fig:print-images-command}.
Po pierwszym uruchomieniu wszystkie maszyny klienckie powinny same się zarejestrować na serwerze, co można sprawdzić komendą
\texttt{python fleetcontrol print\_clients}. Przykładowy rezultat takiej komendy predstawiono na rys. \ref{fig:print-clients-command}.
Po pomyślnej weryfikacji, można przypisać obrazy do klientów. Odbywa sie to poprzez komendę
\texttt{python fleetcontrol assign\_image} zaprezentowaną na rys. \ref{fig:assign-image-command}.
Konfiguracja serwera odbywa się poprzez edycję pliku \texttt{config.yml}, lub poprzez ustawienie następujących zmiennych środowiskowych
\caption{Przykładowe wywołanie komendy przypisującej obraz do klienta}
\label{fig:assign-image-command}
\end{figure}
\paragraph{Obsługa - strona klienta}
Maszyna kliencka po uruchomieniu przedstawia ekran wyboru obrazu maszyny wirtualnej do uruchomienia, zaprezentowany na rys. \ref{fig:client-runner-screen}.
Użytkownik wybiera interesujący go obraz korzystając ze strzałek, następnie klawiszem tabulatora wybiera opcję OK i naciska klawisz Enter.
Wtedy cały ekran przejmowany jest przez maszynę wirtualną, która uruchamia się. Po skończonej pracy w maszynie wirtualnej i jej wyłączeniu,
\item serwer przechowuje metadane o obrazach (nazwa, wersja, hash pliku, lokalizacja pliku), same pliki obrazów oraz dane o klientach (nazwa hosta, adres IP, adres MAC, wersja aplikacji klienckiej)
\item dane o obrazach jak i same obrazy są ręcznie dodawane przez administratora
\item klienci sami rejestrują się oraz aktualizują dane o sobie na serwerze
\item klienci pobierają dane o przypisanych do siebie maszynach wirtualnych, pobierają także przypisane obrazy
\item komunikacja odbywa się poprzez protokół HTTP
\item do komunikacji z serwerem wymagana jest autoryzacja poprzez token JWT(JSON Web Token), uzyskiwany poprzez wysłanie loginu oraz hasła w formacie JSON do odpowiedniego punktu końcowego na serwerze
\item po synchronizacji stanu pomiędzy klientem a serwerem, na kliencie uruchamia się okno wyboru obrazu do uruchomienia
\item aplikacja klienta zbiera także podstawowe dane o maszynie: (zainstalowaną pojemność pamięci RAM, liczbę wątków procesora, adres IP oraz MAC karty sieciowej, która ma połączenie z siecią)
\item po wybraniu obrazu, na podstawie danych o maszynie zebranych przez aplikację klienta generowany jest skrypt, uruchamiający program QEMU z zebranymi danymi jako argumentami wywołania
\item oryginalny obraz maszyny wirtualnej jest kopiowany, a następnie uruchamiany jest skrypt włączający maszynę wirtualną
\item po zakończonej pracy i wyłączeniu maszyny wirtualnej, skrypt podmienia modyfikowany obraz dysku maszyny wirtualnej na oryginalny, skopiowany w kroku wcześniej
\item środowisko klienta składa się z bardzo okrojonego środowiska graficznego \texttt{i3}\footnote{\url{https://i3wm.org/}}, które nie pozwala na wyjście z aplikacji klienckiej oraz emulatora terminala rxvt
\item po uruchomieniu maszyny wirtualnej środowisko graficzne promuje jej okno do wypełnienia całego ekranu, jednocześnie przechwytując klawiaturę i myszkę, aby przekierować je do obsługi maszyny wirtualnej
Serwer wystawia następujące punkty końcowe (ang. endpoints):
\begin{itemize}
\item GET \texttt{/} - przedstawia podstawowe dane o serwerze: nazwę, wersję oraz nazwę hosta, przykład odpowiedzi podano w listingu \ref{fig:response-main}
\item POST \texttt{/login} - pozwala na zalogowanie się poprzez wysłanie nazwy użytkownika i hasła, zwraca token JWT służący do autoryzacji, id użytkownika oraz jego nazwę jako potwierdzenie, przykład ciała zapytania logowania pokazano w listingu \ref{fig:request-login}, przykłady możliwych odpowiedzi pokazano w listingu \ref{fig:response-login}
\item POST \texttt{/clients} - pozwala na rejestrację klienta, przykład wymaganego ciała zapytania pokazano w listingu \ref{fig:response-register}
\item PUT \texttt{/clients} - pozwala na modyfikację danych o już zarejestrowanym kliencie, zapytania i odpowiedzi są takie same, jak dla żądań rejestracji klientów
\item GET \texttt{/clients/adres\_mac} - pozwala na pobranie danych o zadanym kliencie z serwera, możliwe odpowiedzi pokazano w listingu \ref{fig:response-client-data-get}
\item GET \texttt{/clients/adres\_mac/vms} - pozwala na pobranie listy numerów id obrazów przypisanych do klienta o podanym adresie MAC, możliwe odpowiedzi ukazano w listingu \ref{fig:response-list-vms}
\item GET \texttt{/images/id\_obrazu} - pozwala na pobranie danych o obrazie o zadanym numerze id, możliwe odpowiedzi pokazano w listingu \ref{fig:response-get-image-data}
\item GET \texttt{/images/id\_obrazu/download} - pozwala na pobranie obrazu o zadanym numerze id
\end{itemize}
\begin{figure}[hb]
\centering
\begin{lstlisting}
HTTP200:
{
"server_name": "VALHALLA",
"server_version": "v0.0.1",
"host": "127.0.0.1"
}
\end{lstlisting}
\caption{Przykład możliwej odpowiedzi na zapytanie pod punkt końcowy /}
\label{fig:response-main}
\end{figure}
\begin{figure}[hb]
\centering
\begin{lstlisting}
{
"username": "user",
"password": "sekret_password"
}
\end{lstlisting}
\caption{Przykład prawidłowego ciała żądania logowania}
\label{fig:request-login}
\end{figure}
\begin{figure}[hb]
\centering
\begin{lstlisting}
HTTP202 (poprawne dane):
{
"token": "xxxxxxxxx",
"user_id": "1",
"username": "user"
}
HTTP401 (niepoprawne dane):
{
"data": null,
"error": "Auth error",
"message": "Invalid login data"
}
\end{lstlisting}
\caption{Możliwe odpowiedzi na żądanie logowania od serwera}
\label{fig:response-login}
\end{figure}
\begin{figure}[hb]
\centering
\begin{lstlisting}
Poprawne zapytanie:
{
"mac_address": "00:00:00:00:00:00:00:62",
"ip_address": "192.168.1.12",
"hostname": "test",
"client_version": "v0.0.1alpha",
"vm_list_on_machine": []
}
Odpowiedz: HTTP201
{
"success": true
}
Niepoprawne zapytanie:
{
"mac_address": "00:00:00:00:00:00:00:62",
"ip_address": "192.168.1.12",
"client_version": "v0.0.1alpha",
"vm_list_on_machine": []
}
Odpowiedz: HTTP400
{
"data": null,
"error": "'hostname'",
"message": "Internal server error"
}
\end{lstlisting}
\caption{Możliwe zapytania i odpowiedzi serwera na żądanie rejestracji klienta}
\label{fig:response-register}
\end{figure}
\begin{figure}[hb]
\centering
\begin{lstlisting}
HTTP200 (istniejacy adres MAC):
{
"client_version": "v0.0.1alpha",
"hostname": "test",
"ip_address": "192.168.1.12",
"mac_address": "00:00:00:00:00:00:00:62"
}
HTTP404 (nieistniejacy adres MAC):
{
"data": null,
"error": null,
"message": "Client not found in database"
}
\end{lstlisting}
\caption{Możliwe odpowiedzi serwera na zapytanie o dane zadanego klienta}
\label{fig:response-client-data-get}
\end{figure}
\begin{figure}[hb]
\centering
\begin{lstlisting}
HTTP200 (istniejacy adres MAC):
[
1
]
HTTP404 (nieistniejacy adres MAC):
{
"data": null,
"error": null,
"message": "Client not found in database"
}
\end{lstlisting}
\caption{Mozliwe odpowiedzi serwera na zapytanie o obrazy przypisane do zadanego hosta}
\label{fig:response-list-vms}
\end{figure}
\begin{figure}[hb]
\centering
\begin{lstlisting}
HTTP200 (instniejacy obraz):
{
"image_file": "ubuntu.qcow2",
"image_hash": "dbdc4d7f10511387ef89ffc503080b92",
"image_id": "1",
"image_name": "ubuntu22",
"image_name_version_combo": "ubuntu22@v1.0.0",
"image_version": "v1.0.0"
}
HTTP404 (nieistniejacy obraz):
{
"data": null,
"error": null,
"message": "Image not found in database"
}
\end{lstlisting}
\caption{Możliwe odpowiedzi serwera na zapytanie o dane zadanego obrazu}
Sprawdzenie danych o maszynie odbywa się w kilku krokach: aby pobrać adres IP maszyny, program nawiązuje połączenie z serwerem
DNS Google (adres IP: \texttt{8.8.8.8}), a następnie sprawdza nazwę gniazda (ang. socket), które połączyło się z siecią.
Adres MAC jest sprawdzany przez bibliotekę \texttt{get\_mac\_address}, liczba rdzeni procesora przez bibliotekę \texttt{os}, a
ilość pamięci RAM poprzez bibliotekę \texttt{psutil} (bibiloteka zwraca liczbę bajtów pamięci, a reszta aplikacji wymaga liczby gigabajtów, stąd wymagana była konwersja).
Implementacja tych funkcjonalności, wraz z odczytem i parsowaniem plików konfiguracyjnych, znajduje się w klasie \texttt{MachineData}
Pomiary czasu, w jakim urządzenie przeprowadzało instalację, powtórzono dziesięciokrotnie. Wyniki zostały przedstawione w tabeli \ref{id:tab:czas-konfiguracja}.
Pomiary czasu pobierania obrazów także wykonano dziesięciokrotnie, a wyniki zamieszczono w tabeli \ref{id:tab:czas-pobieranie}.
Zauważono, że najdłuższym procesem jest ładowanie obrazu do pamięci serwera, samo jego przesyłanie trwa od 15 do 20\% zmierzonego czasu.
\paragraph{Wykryte błędy}
Podczas testów wykryto kilka błędów. Pierwszym z nich było błędne ustawianie uprawnień pliku skryptu uruchamiającego
maszynę wirtualną. Kolejny z nich dotyczył błędu w konfiguracji serwera PXE, który uniemożliwiał uruchomienie instalatora.
Po zmianie konfiguracji tak, by podstawowe środowisko uruchomieniowe korzystało z serwera HTTP do pobierania obrazów
% \item uzyskane wyniki w świetle postawionych celów i zdefiniowanych wyżej wymagań
% \item kierunki ewentualnych danych prac (rozbudowa funkcjonalna …)
% \item problemy napotkane w trakcie pracy
% \end{itemize}
\paragraph{}
Uzyskano prawie pełną automatyzację procesu instalacji systemu nadzorcy. Administrator musi wykonać jedynie 4 manualne kroki:
dodanie adresów MAC obsługiwanych maszyn do konfiguracji serwera DHCP i konfiguracji skryptów Ansible, wywołanie komendy wysyłającej ,,magic packet'' do
wyznaczonych urządzeń, potwierdzenie instalacji na każdym z urządzeń oraz wywołanie skryptu Ansible do konfiguracji urządzeń.
Dzięki wykorzystaniu QEMU udało się zapewnić użycie maszyn wirtualnych oraz łatwość tworzenia własnych obrazów dla nich. Osiągnięto
także ,,bezstanowość'', maszyny wirtualne po restarcie wracają do swojego pierwotnego stanu sprzed wszsytkich zmian wprowadzonych
przez ostatniego użytkownika.
Użycie PXE, cloud-init oraz Ansible jako systemu automatyzującego instalację systemu nadzorcy pozwoliło na zapewnienie
modularności i możliwości wbudowania rozwiązania w już istniejącą architekturę sieciową.
\paragraph{}
Niestety wydajność systemu zarządzania obrazami maszyn nie jest satysfakcjonująca. Największym problemem obecnego rozwiązania jest
konieczność załadowania całego wysyłanego pliku do pamięci aplikacji, przez co wydajność jest mocno ograniczona. Dobrym rozwiązaniem tego
problemu byłoby zintegrowanie serwera aplikacji z serwerem Apache2, lub Nginx, gdzie to dedykowany serwer HTTP wysyłałby duże pliki,
a serwer zarządzający obsługiwałby tylko przechowywanie danych. Ciekawym rozwiązaniem tego problemu byłoby też użycie narzędzia rsync,
którego zaawansowane wykrywanie różnic w plikach i podmiana jedynie tych właśnie róznic na pewno znacząco przyspieszyłoby
działanie serwera obrazów. Kolejnym problemem jest kwestia bezpieczeństwa. Aby możliwe było pełne zautomatyzowanie procesu
zdecydowano się na użycie tylko jednego użytkownika dla wszystkich klientów. Jest to duże ryzyko ze względów bezpieczeństwa,
ponieważ potencjalny atakujący może wtedy odczytać adresy MAC wszystkich urządzeń i podszywać się pod nie.
\paragraph{}
Podobnie jak w przypadku serwera, wydajność systemu w maszynach wirtualnych nie jest satysfakcjonująca. Największym problemem jest
wydajność graficzna. Rozwiązaniem tego problemu byłoby wprowadzenie mechanizmu przekazywania kontroli nad kartą graficzną
systemowi gościa maszyny wirtualnej. Aby takie podejście było możliwe w przypadku posiadania tylko jednej karty graficznej,
wymagane byłoby jej wcześniejsze odpięcie od systemu zarządcy. Istnieje kilka projektów\cite{bib:joe-knock-git}, które wykorzystują mechanizm
,,zaczepów'' (ang. hooks) do odłączania urządzeń PCI przy uruchamianiu maszyny wirtualnej i podpinania ich z powrotem do systemu
zarządcy po wyłączeniu maszyny wirtualnej. Jednak to rozwiązanie wymaga skomplikowanej konfiguracji na maszynach klienckich,
między innymi wybrania odpowiednich urządzeń PCI do przekazania do maszyny wirtualnej, gdzie adresy tych urządzeń będą różne
na każdej z maszyn, a także ta konfiguracja byłaby różna dla każdego typu urządzeń. Napisanie programu, który w dynamiczny sposób
tworzyłby odpowiednie pliki konfiguracyjne, byłoby na pewno skomplikowane, ale możliwe.