aboutsummaryrefslogtreecommitdiff
path: root/bbot/machine.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2017-07-13 22:50:15 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-07-14 19:10:22 +0300
commitc8ace1ee0a6cab5fd4ea2f084ea436cfa513637d (patch)
treea8db884a665fbf14797393a3b2ff95438c338bb9 /bbot/machine.cxx
parent8e8d599b129d35f638f2c1957c869b054a38b021 (diff)
Make use of wildcards in buildfiles
Diffstat (limited to 'bbot/machine.cxx')
-rw-r--r--bbot/machine.cxx474
1 files changed, 0 insertions, 474 deletions
diff --git a/bbot/machine.cxx b/bbot/machine.cxx
deleted file mode 100644
index 0c8a0e5..0000000
--- a/bbot/machine.cxx
+++ /dev/null
@@ -1,474 +0,0 @@
-// file : bbot/machine.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
-// license : TBC; see accompanying LICENSE file
-
-#include <bbot/machine.hxx>
-
-#include <unistd.h> // sleep()
-
-#include <sys/un.h> // sockaddr_un
-#include <sys/socket.h>
-
-#include <cstdio> // snprintf()
-#include <cstring> // strcpy()
-
-#include <bbot/agent.hxx>
-#include <bbot/machine-manifest.hxx>
-
-using namespace std;
-using namespace butl;
-
-namespace bbot
-{
- // Forward TFTP requests (UDP/69) coming from the machine to the specified
- // port.
- //
- // This allows the machine to connect to any "unknown" IP (e.g., link-local
- // 196.254.111.222) port 69 and end up being redirected to out TFTP server.
- //
- static void
- iptables (tracer& t,
- const char* a,
- const string& tap,
- const string& br,
- uint16_t port,
- bool ignore_errors = false)
- {
- string addr (iface_addr (br));
-
- auto_fd fdn (ignore_errors ? fdnull () : nullfd);
- int ofd (ignore_errors ? fdn.get () : 2);
-
- process_exit::code_type e;
-
- e = run_io_exit (t, 0, ofd, ofd,
- "sudo", "iptables",
- "-t", "nat",
- a, "PREROUTING",
- "-m", "udp",
- "-p", "udp",
- "-m", "physdev",
- "-i", br,
- "--physdev-in", tap,
- "--dport", 69,
- "-j", "DNAT",
- "--to-destination", addr + ':' + to_string (port));
-
- if (e != 0 && !ignore_errors)
- fail << "process iptables terminated with non-zero exit code";
-
- // Nobody really knows whether this is really needed (really)...
- //
- e = run_io_exit (t, 0, ofd, ofd,
- "sudo", "iptables",
- a, "FORWARD",
- "-m", "udp",
- "-p", "udp",
- "-m", "physdev",
- "-o", br,
- "--physdev-out", tap,
- "-d", addr,
- "--dport", port,
- "-m", "state",
- "--state", "NEW,ESTABLISHED,RELATED",
- "-j", "ACCEPT");
-
- if (e != 0 && !ignore_errors)
- fail << "process iptables terminated with non-zero exit code";
- }
-
- static string
- create_tap (const string& br, uint16_t port)
- {
- string t ("tap" + to_string (tc_num));
-
- tracer trace ("create_tap", t.c_str ());
-
- // First try to delete it in case there is one from a previous run.
- //
- iptables (trace, "-D", t, br, port, true); // Ignore errors.
- run_exit (trace, "sudo", "ip", "tuntap", "delete", t, "mode", "tap");
-
- run (trace, "sudo", "ip", "tuntap", "add", t, "mode", "tap", "user", uid);
- run (trace, "sudo", "ip", "link", "set", t, "up");
- run (trace, "sudo", "ip", "link", "set", t, "master", br);
-
- iptables (trace, "-A", t, br, port); // Add.
-
- return t;
- }
-
- static void
- destroy_tap (const string& t, const string& br, uint16_t port)
- {
- tracer trace ("destroy_tap", t.c_str ());
- iptables (trace, "-D", t, br, port); // Delete.
- run (trace, "sudo", "ip", "tuntap", "delete", t, "mode", "tap");
- }
-
- class tap
- {
- public:
- string iface;
-
- string bridge; // Bridge interface to which this tap belongs
- uint16_t port; // UDP port to forward TFTP traffic to.
-
- tap (string b, uint16_t p)
- : iface (create_tap (b, p)), bridge (move (b)), port (p) {}
-
- ~tap ()
- {
- if (!iface.empty ())
- {
- try {destroy ();} catch (...) {}
- }
- }
-
- void
- destroy ()
- {
- destroy_tap (iface, bridge, port);
- iface.clear ();
- }
- };
-
- static string
- generate_mac ()
- {
- // The last two bits of the first byte are special: bit 1 indicates a
- // multicast address (which we don't want) while bit 1 -- local assignment
- // (which we do want).
- //
- char r[6 * 2 + 5 + 1];
- snprintf (r, sizeof (r),
- "%02x:%02x:%02x:%02x:%02x:%02x",
- (genrand<uint8_t> () & 0xFE) | 0x02,
- genrand<uint8_t> (),
- genrand<uint8_t> (),
- genrand<uint8_t> (),
- genrand<uint8_t> (),
- genrand<uint8_t> ());
- return r;
- }
-
- class kvm_machine: public machine
- {
- public:
- kvm_machine (const dir_path&,
- const machine_manifest&,
- const optional<string>& mac,
- const string& br_iface,
- uint16_t tftp_port);
-
- virtual bool
- shutdown (size_t& seconds) override;
-
- virtual void
- forcedown (bool fail_hard) override;
-
- virtual void
- suspend () override;
-
- bool
- wait (size_t& seconds, bool fail_hard) override;
-
- using machine::wait;
-
- virtual void
- print_info (diag_record&) override;
-
- private:
- void
- monitor_command (const string&, bool fail_hard = true);
-
- private:
- path kvm; // Hypervisor binary.
- tap net; // Tap network interface.
- string vnc; // QEMU VNC TCP addr:port.
- path monitor; // QEMU monitor UNIX socket.
- process proc;
- };
-
- kvm_machine::
- kvm_machine (const dir_path& md,
- const machine_manifest& mm,
- const optional<string>& omac,
- const string& br,
- uint16_t port)
- : machine (mm.mac ? *mm.mac : // Fixed mac from machine manifest.
- omac ? *omac : // Generated mac from previous bootstrap.
- generate_mac ()),
- kvm ("kvm"),
- net (br, port),
- vnc ("127.0.0.1:" + to_string (5900 + tc_num)),
- monitor ("/tmp/" + tc_name + "-monitor")
- {
- tracer trace ("kvm_machine", md.string ().c_str ());
-
- if (sizeof (sockaddr_un::sun_path) <= monitor.size ())
- throw invalid_argument ("monitor unix socket path too long");
-
- // Map logical CPUs to sockets/cores/threads. Failed that, QEMU just makes
- // it a machine with that number of sockets and some operating systems
- // (like Windows) only can do two.
- //
- size_t cpu (ops.cpu ());
-
- size_t sockets (cpu <= 8 ? 1 : cpu <= 64 ? 2 : 4);
- size_t cores (cpu / sockets);
- size_t threads (cores <= 4 ? 1 : 2);
- cores /= threads;
-
-
- // We probably don't want to commit all the available RAM to the VM since
- // some of it could be used on the host side for caching, etc. So the
- // heuristics that we will use is 4G or 1G per CPU, whichever is greater
- // and the rest divide equally between the host and the VM.
- //
- size_t ram ((cpu < 4 ? 4 : cpu) * 1024 * 1024); // Kb.
-
- if (ram > ops.ram ())
- ram = ops.ram ();
- else
- ram += (ops.ram () - ram) / 2;
-
- // If we have options, use that instead of the default network and
- // disk configuration.
- //
- strings os;
-
- if (mm.options)
- {
- os = mm.unquoted_options ();
-
- // Pre-process ifname=? and mac=?.
- //
- auto sub = [] (string& o, const char* s, const string& r)
- {
- size_t p (o.find (s));
-
- if (p != string::npos)
- {
- p = o.find ('?', p + 1);
- assert (p != string::npos);
- o.replace (p, 1, r);
- }
- };
-
- for (string& o: os)
- {
- sub (o, "ifname=?", net.iface);
- sub (o, "mac=?", mac);
- }
- }
- else
- {
- auto add = [&os] (string o, string v)
- {
- os.push_back (move (o));
- os.push_back (move (v));
- };
-
- // Network.
- //
- add ("-netdev", "tap,id=net0,script=no,ifname=" + net.iface);
- add ("-device", "virtio-net-pci,netdev=net0,mac=" + mac);
-
- // Disk.
- //
- add ("-drive", "if=none,id=disk0,file=disk.img,format=raw");
- add ("-device", "virtio-blk-pci,scsi=off,drive=disk0");
-
- //"-drive", "if=none,id=disk0,format=raw,file=disk.img"
- //"-device", "virtio-scsi-pci,id=scsi"
- //"-device", "scsi-hd,drive=disk0"
- }
-
- // Start the VM.
- //
- // Notes:
- //
- // 1. echo system_powerdown | socat - UNIX-CONNECT:.../monitor
- //
- proc = run_io_start (
- trace,
- fdnull (),
- 2,
- 2,
- md, // Run from the machine's directory.
- kvm,
- "-boot", "c", // Boot from disk.
- "-no-reboot", // Exit on VM reboot.
- "-m", to_string (ram / 1024) + "M",
- "-cpu", "host",
- "-smp", (to_string (cpu) +
- ",sockets=" + to_string (sockets) +
- ",cores=" + to_string (cores) +
- ",threads=" + to_string (threads)),
- os,
- "-vnc", "127.0.0.1:" + to_string (tc_num), // 5900 + tc_num
- "-monitor", "unix:" + monitor.string () + ",server,nowait");
- }
-
- // Connect to the QEMU monitor via the UNIX socket and send system_reset.
- // You may be wondering why not system_powerdown? The reason is that while
- // not all OS know how to power-down the machine, pretty much all of them
- // can reboot. So combined with the -no-reboot option above, we get the
- // same result in a more robust way.
- //
- // Note that this setup has one side effect: if the VM decided to reboot,
- // say, during bootstrap, then we will interpret it as a shutdown. Current
- // thinking saying this is good since we don't want our VMs to reboot
- // uncontrollably for security and predictability reasons (e.g., we don't
- // want Windows to decide to install updates -- this stuff should all be
- // disabled during the VM preparation).
- //
- // Actually, this turned out not to be entirely accurate: reset appears to
- // be a "hard reset" while powerdown causes a clean shutdown. So we use
- // powerdown to implement shutdown() and reset/-no-reboot for implement
- // forcedown().
- //
- bool kvm_machine::
- shutdown (size_t& seconds)
- {
- monitor_command ("system_powerdown");
-
- // Wait for up to the specified number if seconds for the machine to
- // shutdown.
- //
- return wait (seconds);
- }
-
- void kvm_machine::
- forcedown (bool fh)
- {
- monitor_command ("system_reset", fh);
- wait (fh);
- }
-
- void kvm_machine::
- suspend ()
- {
- monitor_command ("stop");
- }
-
- void kvm_machine::
- print_info (diag_record& dr)
- {
- dr << info << "qemu pid: " << proc.id ()
- << info << "qemu vnc: " << vnc
- << info << "qemu monitor: unix:" << monitor;
- }
-
- bool kvm_machine::
- wait (size_t& sec, bool fh)
- {
- try
- {
- tracer trace ("kvm_machine::wait");
-
- bool t;
- for (; !(t = proc.try_wait ()) && sec != 0; --sec)
- sleep (1);
-
- if (t)
- {
- run_io_finish (trace, proc, kvm, fh);
- net.destroy (); //@@ Always fails hard.
- try_rmfile (monitor, true); // QEMU doesn't seem to remove it.
- }
-
- return t;
- }
- catch (const process_error& e)
- {
- fail (fh) << "unable to execute " << kvm << ": " << e << endf;
- }
- }
-
- void kvm_machine::
- monitor_command (const string& c, bool fh)
- {
- try
- {
- sockaddr_un addr;
- addr.sun_family = AF_LOCAL;
- strcpy (addr.sun_path, monitor.string ().c_str ()); // Size check in ctor
-
- auto_fd sock (socket (AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0));
-
- if (sock.get () == -1)
- throw_system_error (errno);
-
- if (connect (sock.get (),
- reinterpret_cast<sockaddr*> (&addr),
- sizeof (addr)) == -1)
- throw_system_error (errno);
-
- // Read until we get something.
- //
- auto readsome = [&sock] ()
- {
- ifdstream ifs (move (sock),
- fdstream_mode::non_blocking,
- ostream::badbit);
-
- char buf[256];
- for (streamsize n (0), m (0);
- n == 0 || m != 0;
- m = ifs.readsome (buf, sizeof (buf) - 1))
- {
- if (m != 0)
- {
- n += m;
-
- //buf[m] = '\0';
- //text << buf;
- }
- }
-
- sock = ifs.release ();
- };
-
- // Read QEMU welcome.
- //
- readsome ();
-
- // Write our command.
- //
- {
- ofdstream ofs (move (sock), fdstream_mode::blocking);
- ofs << c << endl;
- sock = ofs.release ();
- }
-
- // Read QEMU reply (may hit eof).
- //
- readsome ();
- return;
- }
- catch (const system_error& e)
- {
- fail (fh) << "unable to communicate with qemu monitor: " << e;
- }
- }
-
- unique_ptr<machine>
- start_machine (const dir_path& md,
- const machine_manifest& mm,
- const optional<string>& mac,
- const string& br_iface,
- uint16_t tftp_port)
- {
- switch (mm.type)
- {
- case machine_type::kvm:
- return make_unique<kvm_machine> (md, mm, mac, br_iface, tftp_port);
- case machine_type::nspawn:
- assert (false); //@@ TODO
- }
-
- return nullptr;
- }
-}