You are here -> Home Logram et son site » Un pas de plus vers un beau serveur de construction

Un pas de plus vers un beau serveur de construction

Le 27/04/2010 à 20h22 by steckdenis, in Logram et son site, 0 commentaries

Bonjour :) ,

Je pensais d'abord en faire un journal privé, mais sachant que j'ai passé largement plus d'une semaine sur cette refonte du serveur de compilation, j'estime que ça peut être intéressant d'être partagé ici.

Les défauts du serveur de compilation

Le serveur de compilation, comme présenté dans la news précédente, était déjà vraiment pas mal, mais je ne le trouvais pas satisfaisant.

Le code était relativement sale, tout était contenu dans un seul fichier, dans une seule fonction (ou presque). Bref, je passais autant de temps à scroller pour trouver l'endroit à modifier qu'à faire la modification.

De plus, des fonctionnalités manquaient, d'autres étaient boguées. Par exemple, le même serveur de compilation s'occupait de compiler toutes les architectures d'un paquet. Ok, pas de problème si le tout tourne sur un serveur x86_64, et que les deux seules architectures sont i686 et x86_64, mais ajoutez de l'ARM, et voilà le serveur foutu.

D'autres petits problèmes étaient également présents, comme le fait que le dépôt utilisé pour l'importation des paquets (celui dans lequel on place les paquets construits) était le même que celui duquel on installe les paquets.

Autrement dit, il était impossible de lancer le serveur de compilation sur un serveur différent de celui qui contient le mirroir.

La solution

J'ai donc pris mon courage à deux mains et corrigé le problème. Pas de grosse réécriture, le code est encore frais, mais plutôt pas mal de mouvement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
buildserver/app.cpp              | 1551 ++++++--------------------------------
buildserver/app.h                |   54 +-
buildserver/buildserver.conf     |    3 +
buildserver/buildserver.pro      |    4 +-
buildserver/cleanup              |   11 +
buildserver/skel/bin/sh          |    1 +
buildserver/skel/etc/hosts       |    9 +
buildserver/skel/etc/resolv.conf |    6 +
buildserver/thread.cpp           |   49 ++
buildserver/thread.h             |   47 ++
buildserver/worker.cpp           | 1423 ++++++++++++++++++++++++++++++++++
buildserver/worker.h             |  118 +++
libpackage/databasepackage.cpp   |    2 +
13 files changed, 1936 insertions(+), 1342 deletions(-)

On voit bien dans ce diffstat qu'une énorme partie du code de app.cpp a été envoyé dans worker.cpp. Ce dernier contient toute la partie métier de la construction des paquets, d'où son nom.

thread.cpp contient une toute petite classe, de quelques lignes (49 à en croire le diffstat), se chargeant simplement de lancer un worker. La raison de cette astuce est qu'un thread appartient à son thread parent, et que ça pose des problèmes dans la gestion des événements. Worker appartient bien au thread, donc tout va bien, pas de conflit :) (si vous n'avez pas compris, pas grave ;) ).

Le dossier skel/ fait également son apparition. Malheureusement, Git ne supporte pas les dossiers vides, donc les dossiers dev/, proc/, sys/, tmp/, var/cache/lgrpkg/db/pkgs/ et var/cache/lgrpkg/download/ sont absents. Il faut les créer avant d'utiliser le serveur de compilation pour éviter une erreur.

Nouvelles fonctionnalités

Outre le code plus clair, bien découpé en procédures, plusieurs fonctionnalités arrivent.

Tout d'abord, il y a cette gestion des threads. Il est possible de configurer combien de threads on veut, ce qui permet de construire plusieurs paquets en même temps. Je n'ai pas encore testé avec plusieurs threads, donc je ne garanti rien (et je sais qu'il y a des endroits où il y a des problèmes de synchronisation, parfois très importants).

Ensuite, il y a la possibilité de définir dans le fichier de configuration le mirroir duquel installer les paquets. Par exemple, si Marc a son propre dépôt, et qu'il décide d'y faire tourner un serveur de construction, il peut directement récupérer ses paquets depuis archive.logram-project.org, sans avoir besoin de copier tout ce dépôt dans le sien, avec tous les problèmes que ça implique (espace disque, bande passante, mise à jour, et surtout envoie de ses propres paquets chez nous, s'il le souhaite).

Un petit détail, qui a son importance, est l'arrivée de l'option «WebsiteIntegration» dans le fichier de configuration du serveur. Cette option permet de désactiver la synchronisation avec le wiki, qui n'est présent que si un site web Logram tourne sur le serveur. Pas la peine d'alourdir inutilement la BDD pour rien :) .

Conclusion

Pour finir, le code est maintenant propre et facilement maintenable. Je vais pouvoir ajouter tout ce que je voulais (envoie de mail au mainteneur en cas d'erreur, reconstruction des paquets qui dépendant d'un autre, etc).

Voici, pour vous amuser, un avant/après d'un morceau de code choisi :) :

Avant

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
QString enabledDistros = set->value("DistroDeps/" + distro, distro).toString();

// S'assurer d'être dans le bon état
setState(Downloading, log_id);
log(Operation, "Beginning of the build of " + name + " for the architecture " + arch);
curArch = arch;

// Générer les fichiers nécessaires au Setup chrooté :
// /etc/lgrpkg/sources.list
// /etc/lgrpkg/scripts/weight.qs (pris d'une resource)
// /var/cache/lgrpkg/{db/pkgs,download}
curDir.mkpath("tmp/etc/lgrpkg/scripts");
curDir.mkpath("tmp/var/cache/lgrpkg/db/pkgs");
curDir.mkpath("tmp/var/cache/lgrpkg/download");
curDir.mkpath("tmp/usr/libexec");

// Sources.list
QSettings sourcesList("tmp/etc/lgrpkg/sources.list", QSettings::IniFormat, this);

sourcesList.setValue("Language", "en");
sourcesList.beginGroup("repo");
sourcesList.setValue("Type", "local");
sourcesList.setValue("Mirrors", set->value("Repository/Root").toString());
sourcesList.setValue("Distributions", enabledDistros);
sourcesList.setValue("Archs", arch + " all");
sourcesList.setValue("Active", true);
sourcesList.setValue("Description", "Automatic building repository");
sourcesList.setValue("Sign", false);
sourcesList.endGroup();

sourcesList.sync();

// weight.qs
QFile in(":weight.qs");
QFile out("tmp/etc/lgrpkg/scripts/weight.qs");

if (!in.open(QIODevice::ReadOnly))
{
    log(Error, "Unable to open the resource :weight.qs");
    endPackage(log_id, new_flags | SOURCEPACKAGE_FLAG_FAILED);

    recurseRemove("tmp", 0);
    return true;
}

if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
    log(Error, "Unable to open tmp/etc/lgrpkg/scripts/weight.qs for writing");
    endPackage(log_id, new_flags | SOURCEPACKAGE_FLAG_FAILED);

    recurseRemove("tmp", 0);
    return true;
}

out.write(in.readAll());
out.close();

// /usr/libexec/scriptapi
if (!QFile::copy("/usr/libexec/scriptapi", "tmp/usr/libexec/scriptapi"))
{
    log(Error, "Unable to copy /usr/libexec/scriptapi to tmp/usr/libexec/scriptapi");
    endPackage(log_id, new_flags | SOURCEPACKAGE_FLAG_FAILED);

    recurseRemove("tmp", 0);
    return true;
}

Après

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
void Worker::run()
{
    /* ... */

    // Créer et pré-remplir le dossier tmpRoot
    log(Operation, "Populating the working directory (" + tmpRoot + " ;)  with skeleton files");

    if (!prepareTemp())
    {
        error();
        return;
    }

    /* ... */
}

bool Worker::prepareTemp()
{
    // Créer le dossier de travail
    QDir::root().mkpath(tmpRoot);

    // Copier le contenu de QDir::currentPath() + "/skel" dans tmpRoot
    if (!recurseCopy(QDir::currentPath() + "/skel", tmpRoot))
    {
        return false;
    }

    if (mount("/dev", qPrintable(tmpRoot + "/dev"), 0, MS_BIND, 0))
    {
        log(Error, "Unable to bind /dev to " + tmpRoot + "/dev");
        return false;
    }

    if (mount("/proc", qPrintable(tmpRoot + "/proc"), 0, MS_BIND, 0))
    {
        log(Error, "Unable to bind /proc to " + tmpRoot + "/proc");
        return false;
    }

    if (mount("/sys", qPrintable(tmpRoot + "/sys"), 0, MS_BIND, 0))
    {
        log(Error, "Unable to bind /sys to " + tmpRoot + "/sys");
        return false;
    }

    // Générer les fichiers nécessaires au Setup chrooté
    // Sources.list
    QSettings sourcesList(tmpRoot + "/etc/lgrpkg/sources.list", QSettings::IniFormat, 0);

    sourcesList.setValue("Language", "en");
    sourcesList.beginGroup("repo");
    sourcesList.setValue("Type", app->sourceType());
    sourcesList.setValue("Mirrors", app->sourceUrl());
    sourcesList.setValue("Distributions", app->enabledDistros(distro));
    sourcesList.setValue("Archs", app->architecture() + " all");
    sourcesList.setValue("Active", true);
    sourcesList.setValue("Description", "Automatic building repository");
    sourcesList.setValue("Sign", false);
    sourcesList.endGroup();

    sourcesList.sync();

    // weight.qs
    QFile in(":weight.qs");
    QFile out(tmpRoot + "/etc/lgrpkg/scripts/weight.qs");

    if (!in.open(QIODevice::ReadOnly))
    {
        log(Error, "Unable to open the resource :weight.qs");
        return false;
    }

    if (!out.open(QIODevice::WriteOnly | QIODevice::Truncate))
    {
        log(Error, "Unable to open " + tmpRoot + "/etc/lgrpkg/scripts/weight.qs for writing");
        return false;
    }

    out.write(in.readAll());
    out.close();

    // /usr/libexec/scriptapi
    if (!QFile::copy("/usr/libexec/scriptapi", tmpRoot + "/usr/libexec/scriptapi"))
    {
        log(Error, "Unable to copy /usr/libexec/scriptapi to " + tmpRoot + "/usr/libexec/scriptapi");
        return false;
    }

    return true;
}

Tout est mieux : c'est beau, structuré, propre, la gestion des erreurs est bien meilleure, etc :) . On voit également que le serveur de compilation se charge maintenant du peuplement du dossier de travail, ainsi que du montage des dossiers /dev, /proc et /sys, pour permettre au chroot de fonctionner. Il fallait faire ça à la main avant.

Commentaries

Author Message