Commit 9df31f5f authored by AdamSimpson's avatar AdamSimpson
Browse files

Builder refactor

parent 75a0ca14
Loading
Loading
Loading
Loading
+2 −109
Original line number Diff line number Diff line
#pragma once

#include "Messenger.h"
#include "Logger.h"
#include "Messenger.h"
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/process.hpp>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/asio.hpp>

namespace asio = boost::asio;
using asio::ip::tcp;
namespace bp = boost::process;

class Builder {
public:
    explicit Builder() {

        // Full build connection - this will not run until the io_service is started
        asio::spawn(io_service,
                    [&](asio::yield_context yield) {
                        boost::system::error_code error;
                        Messenger client(io_service, "8080", yield, error);
                        if(error) {
                            throw std::runtime_error("Error connecting to client: " + error.message());
                        }

                        // Receive client data
                        ClientData client_data = client.async_read_client_data(yield, error);
                        if (error) {
                            throw std::runtime_error("Error receiving client data: " + error.message());
                        }

                        // Receive the definition file from the client
                        client.async_read_file("container.def", yield, error);
                        if (error) {
                            throw std::runtime_error("Error receiving definition file: " + error.message());
                        }

                        logger::write("Received container.def");

                        if(client_data.arch == Architecture::ppc64le) {
                            // A dirty hack but the ppc64le qemu executable must be in the place the kernel expects it
                            // Modify the definition to copy this executable in during %setup
                            boost::filesystem::ofstream def{"container.def", std::ios::app};
                            std::string copy_qemu("\n%setup\ncp /usr/bin/qemu-ppc64le  ${SINGULARITY_ROOTFS}/usr/bin/qemu-ppc64le");
                            def << copy_qemu;
                        }

                        // Create a pipe to communicate with our build subprocess
                        bp::async_pipe std_pipe(io_service);

                        // Launch our build as a subprocess
                        // We use "unbuffer" to fake the build into thinking it has a real TTY, which the command output eventually will
                        // This causes things like wget and color ls to work nicely
                        std::string build_command("/usr/bin/sudo ");
                        // If the cleint has a tty trick the subprocess into thinking that as well
                        if(client_data.tty) {
                            build_command += "/usr/bin/unbuffer ";
                        }
                        build_command += "/usr/local/bin/singularity build ./container.img ./container.def";

                        bp::group group;
                        std::error_code build_ec;
                        bp::child build_child(build_command, bp::std_in.close(), (bp::std_out & bp::std_err) > std_pipe,
                                              group, build_ec);
                        if (build_ec) {
                            throw std::runtime_error("subprocess error: " + build_ec.message());
                        }

                        logger::write("launched build process: " + build_command);

                        // stream from the async pipe process output to the socket
                        std::array<char, 4096> buffer;
                        std::size_t read_size = 0;
                        boost::system::error_code stream_error;
                        bool fin = false;
                        do {
                            // Read from the pipe into a buffer
                            auto read_bytes = std_pipe.async_read_some(buffer, yield[stream_error]);
                            if (stream_error != boost::system::errc::success && stream_error != boost::asio::error::eof) {
                                throw std::runtime_error("reading process pipe failed: " + stream_error.message());
                            }
                            // Wrap it up if we've hit EOF
                            if(stream_error == boost::asio::error::eof) {
                                fin = true;
                            }
                            // Write the buffer to our socket
                            client.async_write_some(fin, asio::buffer(buffer, read_bytes), yield, error);
                            if (error) {
                                throw std::runtime_error("sending process pipe failed: " + error.message());
                            }
                        } while (!fin);

                        // Get the return value from the build subprocess
                        logger::write("Waiting on build process to exit");
                        build_child.wait();
                        int build_code = build_child.exit_code();

                        // Send the container to the client
                        if (build_code == 0) {
                            logger::write("Build complete, sending container");
                            client.async_write_file("container.img", yield, error);
                            if (error) {
                                throw std::runtime_error("Sending file to client failed: " + error.message());
                            }
                        } else {
                            throw std::runtime_error("Build failed, not sending container");
                        }
                    });

    }
    explicit Builder();

    // Start the IO service
    void run();
+82 −0
Original line number Diff line number Diff line
#include "Builder.h"
#include "Logger.h"
#include "Messenger.h"

Builder::Builder() {
    // Full build connection - this will not run until the io_service is started
    asio::spawn(io_service,
                [&](asio::yield_context yield) {
                    boost::system::error_code error;
                    Messenger client(io_service, "8080", yield, error);
                    if (error) {
                        throw std::runtime_error("Error connecting to client: " + error.message());
                    }

                    // Receive client data
                    ClientData client_data = client.async_read_client_data(yield, error);
                    if (error) {
                        throw std::runtime_error("Error receiving client data: " + error.message());
                    }

                    // Receive the definition file from the client
                    client.async_read_file("container.def", yield, error);
                    if (error) {
                        throw std::runtime_error("Error receiving definition file: " + error.message());
                    }

                    logger::write("Received container.def");

                    if (client_data.arch == Architecture::ppc64le) {
                        // A dirty hack but the ppc64le qemu executable must be in the place the kernel expects it
                        // Modify the definition to copy this executable in during %setup
                        // NOTE: singularity currently doesn't have a way to inject this file in before bootstrap
                        //       and so DockerHub or local singularity files are the only supported ppc64le bootstrapper
                        boost::filesystem::ofstream def{"container.def", std::ios::app};
                        std::string copy_qemu(
                                "\n%setup\ncp /usr/bin/qemu-ppc64le  ${SINGULARITY_ROOTFS}/usr/bin/qemu-ppc64le");
                        def << copy_qemu;
                    }

                    // Create a pipe to communicate with our build subprocess
                    bp::async_pipe std_pipe(io_service);

                    // Launch our build as a subprocess
                    // We use "unbuffer" to fake the build into thinking it has a real TTY, which the command output eventually will
                    // This causes things like wget and color ls to work nicely
                    std::string build_command("/usr/bin/sudo ");
                    // If the cleint has a tty trick the subprocess into thinking that as well
                    if (client_data.tty) {
                        build_command += "/usr/bin/unbuffer ";
                    }
                    build_command += "/usr/local/bin/singularity build ./container.img ./container.def";

                    bp::group group;
                    std::error_code build_ec;
                    bp::child build_child(build_command, bp::std_in.close(), (bp::std_out & bp::std_err) > std_pipe,
                                          group, build_ec);
                    if (build_ec) {
                        throw std::runtime_error("subprocess error: " + build_ec.message());
                    }

                    logger::write("launched build process: " + build_command);

                    // stream from the async pipe process output to the socket
                    client.async_write_pipe(std_pipe, yield, error);

                    // Get the return value from the build subprocess
                    logger::write("Waiting on build process to exit");
                    build_child.wait();
                    int build_code = build_child.exit_code();

                    // Send the container to the client
                    if (build_code == 0) {
                        logger::write("Build complete, sending container");
                        client.async_write_file("container.img", yield, error);
                        if (error) {
                            throw std::runtime_error("Sending file to client failed: " + error.message());
                        }
                    } else {
                        throw std::runtime_error("Build failed, not sending container");
                    }
                });

}

void Builder::run() {
    io_service.run();
+1 −0
Original line number Diff line number Diff line
#include "Builder.h"
#include "Logger.h"

int main(int argc, char *argv[]) {

+8 −6
Original line number Diff line number Diff line
@@ -9,17 +9,15 @@
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>
//#include <boost/archive/text_iarchive.hpp>
//#include <boost/archive/text_oarchive.hpp>
#include "BuilderData.h"
//#include <boost/progress.hpp>
//#include <boost/crc.hpp>
#include <memory.h>
#include "Logger.h"
#include "ClientData.h"
#include <boost/process.hpp>

namespace asio = boost::asio;
using asio::ip::tcp;
namespace bp = boost::process;
namespace beast = boost::beast;
namespace websocket = beast::websocket;

@@ -79,8 +77,7 @@ public:

    void async_write_file(boost::filesystem::path file_path,
                         asio::yield_context yield,
                         boost::system::error_code& error,
    );
                         boost::system::error_code& error);

    BuilderData async_read_builder(asio::yield_context yield,
                                      boost::system::error_code& error);
@@ -96,6 +93,11 @@ public:
                    asio::yield_context yield,
                    boost::system::error_code& error);


    void async_write_pipe(bp::async_pipe& pipe,
                          asio::yield_context yield,
                          boost::system::error_code& error);

private:
    websocket::stream<tcp::socket> stream;
};
 No newline at end of file
+25 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
#include <boost/archive/text_oarchive.hpp>
#include "Builder.h"


namespace asio = boost::asio;
using asio::ip::tcp;

@@ -221,3 +222,27 @@ void Messenger::async_write_client_data(ClientData client_data) {
        return;
    }
}

void Messenger::async_write_pipe(bp::async_pipe pipe,
                      asio::yield_context yield,
                      boost::system::error_code &error) {
    std::array<char, 4096> buffer;
    boost::system::error_code stream_error;
    bool fin = false;
    do {
        // Read from the pipe into a buffer
        auto read_bytes = pipe.async_read_some(buffer, yield[stream_error]);
        if (stream_error != boost::system::errc::success && stream_error != boost::asio::error::eof) {
            throw std::runtime_error("reading process pipe failed: " + stream_error.message());
        }
        // Wrap it up if we've hit EOF on our process output
        if (stream_error == boost::asio::error::eof) {
            fin = true;
        }
        // Write the buffer to our socket
        stream.async_write_some(fin, asio::buffer(buffer.data(), read_bytes), yield, error);
        if (error) {
            throw std::runtime_error("sending process pipe failed: " + error.message());
        }
    } while (!fin);
}
 No newline at end of file