Call qemu directly when host packages support it
Bug: 79170615
Test: Run on gce
Change-Id: Ic082ff9fc1c34687fbe895ffe6d155f362c61250
Merged-In: Ic082ff9fc1c34687fbe895ffe6d155f362c61250
(cherry picked from commit a8142f95a29b5c3d2b0a80457062428bc582916d)
diff --git a/common/vsoc/lib/vsoc_memory.cpp b/common/vsoc/lib/vsoc_memory.cpp
index 04f90bd..76ecb26 100644
--- a/common/vsoc/lib/vsoc_memory.cpp
+++ b/common/vsoc/lib/vsoc_memory.cpp
@@ -198,6 +198,7 @@
}
region.SetRegionSize(new_min_size);
+ LOG(INFO) << region_name << ": resized to " << new_min_size;
// Get new offset for next region
auto offset = region.begin_offset() + region.region_size();
diff --git a/host/commands/launch/main.cc b/host/commands/launch/main.cc
index 55ba4a9..1a2cffe 100644
--- a/host/commands/launch/main.cc
+++ b/host/commands/launch/main.cc
@@ -48,23 +48,7 @@
#include "host/libs/monitor/kernel_log_server.h"
#include "host/libs/usbip/server.h"
#include "host/libs/vadb/virtual_adb_server.h"
-#include "host/libs/vm_manager/libvirt_manager.h"
-
-namespace {
-std::string StringFromEnv(const char* varname, std::string defval) {
- const char* const valstr = getenv(varname);
- if (!valstr) {
- return defval;
- }
- return valstr;
-}
-
-std::string DefaultHostArtifactsPath(const char* file_name) {
- return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) + "/") +
- file_name;
-}
-
-} // namespace
+#include "host/libs/vm_manager/vm_manager.h"
using vsoc::GetPerInstanceDefault;
@@ -103,18 +87,25 @@
std::string g_default_mempath{GetPerInstanceDefault("/var/run/shm/cvd-")};
DEFINE_string(mempath, g_default_mempath.c_str(),
"Target location for the shmem file.");
-std::string g_default_mobile_interface{GetPerInstanceDefault("cvd-mobile-")};
+// The cvd-mobile-{tap|br}-xx interfaces are created by default, but libvirt
+// needs to create its own on tap interfaces on every run so we use a different
+// set for it.
+std::string g_default_mobile_interface{
+ vsoc::HostSupportsQemuCli() ? GetPerInstanceDefault("cvd-mbr-")
+ : GetPerInstanceDefault("cvd-mobile-")};
DEFINE_string(mobile_interface, g_default_mobile_interface.c_str(),
"Network interface to use for mobile networking");
-DEFINE_string(mobile_tap_name, GetPerInstanceDefault("amobile"),
+std::string g_default_mobile_tap_interface =
+ vsoc::HostSupportsQemuCli() ? GetPerInstanceDefault("cvd-mtap-")
+ : GetPerInstanceDefault("amobile");
+DEFINE_string(mobile_tap_name, g_default_mobile_tap_interface.c_str(),
"The name of the tap interface to use for mobile");
std::string g_default_serial_number{GetPerInstanceDefault("CUTTLEFISHCVD")};
DEFINE_string(serial_number, g_default_serial_number.c_str(),
"Serial number to use for the device");
DEFINE_string(instance_dir, vsoc::GetDefaultPerInstanceDir(),
"A directory to put all instance specific files");
-DEFINE_string(system_image_dir,
- StringFromEnv("ANDROID_PRODUCT_OUT", StringFromEnv("HOME", ".")),
+DEFINE_string(system_image_dir, vsoc::DefaultGuestImagePath(""),
"Location of the system partition images.");
DEFINE_string(vendor_image, "", "Location of the vendor partition image.");
@@ -123,12 +114,12 @@
" Will be deprecated soon.");
DEFINE_bool(start_vnc_server, true, "Whether to start the vnc server process.");
DEFINE_string(vnc_server_binary,
- DefaultHostArtifactsPath("/bin/vnc_server"),
+ vsoc::DefaultHostArtifactsPath("bin/vnc_server"),
"Location of the vnc server binary.");
DEFINE_int32(vnc_server_port, GetPerInstanceDefault(6444),
"The port on which the vnc server should listen");
DEFINE_string(socket_forward_proxy_binary,
- DefaultHostArtifactsPath("/bin/socket_forward_proxy"),
+ vsoc::DefaultHostArtifactsPath("bin/socket_forward_proxy"),
"Location of the socket_forward_proxy binary.");
DEFINE_string(adb_mode, "tunnel",
"Mode for adb connection. Can be usb for usb forwarding, or "
@@ -143,7 +134,7 @@
"MAC address of the wifi interface running on the host.");
DEFINE_bool(start_wifi_relay, true, "Whether to start the wifi_relay process.");
DEFINE_string(wifi_relay_binary,
- DefaultHostArtifactsPath("/bin/wifi_relay"),
+ vsoc::DefaultHostArtifactsPath("bin/wifi_relay"),
"Location of the wifi_relay binary.");
std::string g_default_wifi_interface{GetPerInstanceDefault("cvd-wifi-")};
DEFINE_string(wifi_interface, g_default_wifi_interface.c_str(),
@@ -683,9 +674,9 @@
PreLaunchInitializers::Initialize();
// Start the guest VM
- vm_manager::LibvirtManager libvirt;
- if (!libvirt.Start()) {
- LOG(FATAL) << "Unable to start libvirt";
+ auto vm_manager = vm_manager::VmManager::Get();
+ if (!vm_manager->Start()) {
+ LOG(FATAL) << "Unable to start vm_manager";
return -1;
}
diff --git a/host/commands/stop_cvd/Android.bp b/host/commands/stop_cvd/Android.bp
index 3607e8e..e9db668 100644
--- a/host/commands/stop_cvd/Android.bp
+++ b/host/commands/stop_cvd/Android.bp
@@ -24,6 +24,7 @@
shared_libs: [
"libbase",
"libcuttlefish_fs",
+ "libcuttlefish_utils",
"cuttlefish_auto_resources",
"libicuuc",
],
diff --git a/host/commands/stop_cvd/main.cc b/host/commands/stop_cvd/main.cc
index 441ec60..ce2d02d 100644
--- a/host/commands/stop_cvd/main.cc
+++ b/host/commands/stop_cvd/main.cc
@@ -36,7 +36,7 @@
#include <glog/logging.h>
#include "host/libs/config/cuttlefish_config.h"
-#include "host/libs/vm_manager/libvirt_manager.h"
+#include "host/libs/vm_manager/vm_manager.h"
namespace {
void RunCommand(const char* command) {
@@ -55,8 +55,8 @@
int exit_code = 0;
// TODO(b/78512938): Should ask the monitor to do the shutdown instead
- vm_manager::LibvirtManager libvirt_manager;
- if (!libvirt_manager.Stop()) {
+ auto vm_manager = vm_manager::VmManager::Get();
+ if (!vm_manager->Stop()) {
LOG(ERROR)
<< "Error when stopping guest virtual machine. Is it still running?";
exit_code = 1;
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 178ff0f..a2a3e0b 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -449,12 +449,34 @@
int GetPerInstanceDefault(int base) { return base + GetInstance() - 1; }
std::string GetDefaultPerInstanceDir() {
- // TODO(79170615): Change to a directory in home once libvirt is no longer
- // default.
std::ostringstream stream;
- stream << "/var/run/libvirt-" << kDefaultUuidPrefix << std::setfill('0')
- << std::setw(2) << GetInstance();
+ if (HostSupportsQemuCli()) {
+ stream << std::getenv("HOME") << "/runfiles";
+ } else {
+ stream << "/var/run/libvirt-" << kDefaultUuidPrefix << std::setfill('0')
+ << std::setw(2) << GetInstance();
+ }
return stream.str();
}
+std::string DefaultHostArtifactsPath(const std::string& file_name) {
+ return (cvd::StringFromEnv("ANDROID_HOST_OUT",
+ cvd::StringFromEnv("HOME", ".")) +
+ "/") +
+ file_name;
+}
+
+std::string DefaultGuestImagePath(const std::string& file_name) {
+ return (cvd::StringFromEnv("ANDROID_PRODUCT_OUT",
+ cvd::StringFromEnv("HOME", ".")) +
+ "/") +
+ file_name;
+}
+
+bool HostSupportsQemuCli() {
+ static bool supported =
+ std::system(
+ "/usr/lib/cuttlefish-common/bin/capability_query.py qemu_cli") == 0;
+ return supported;
+}
} // namespace vsoc
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 4451653..3eb5532 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -32,7 +32,7 @@
// Saves the configuration object in a file, it can then be read in other
// processes by passing the --config_file option.
- bool SaveToFile(const std::string& file) const ;
+ bool SaveToFile(const std::string& file) const;
// Returns the path to a file with the given name in the instance directory..
std::string PerInstancePath(const char* file_name) const;
@@ -188,4 +188,10 @@
std::string GetDefaultPerInstanceDir();
+std::string DefaultHostArtifactsPath(const std::string& file);
+std::string DefaultGuestImagePath(const std::string& file);
+
+// Whether the installed host packages support calling qemu directly instead of
+// through libvirt
+bool HostSupportsQemuCli();
} // namespace vsoc
diff --git a/host/libs/vm_manager/Android.bp b/host/libs/vm_manager/Android.bp
index a4bfc66..3ab302d 100644
--- a/host/libs/vm_manager/Android.bp
+++ b/host/libs/vm_manager/Android.bp
@@ -17,6 +17,8 @@
name: "libcuttlefish_vm_manager",
srcs: [
"libvirt_manager.cpp",
+ "qemu_manager.cpp",
+ "vm_manager.cpp",
],
header_libs: [
"cuttlefish_glog",
@@ -35,4 +37,10 @@
"libjsoncpp",
],
defaults: ["cuttlefish_host_only"],
+}
+
+cc_prebuilt_binary {
+ name: "cf_qemu.sh",
+ srcs: ["cf_qemu.sh"],
+ defaults: ["cuttlefish_host_only"],
}
\ No newline at end of file
diff --git a/host/libs/vm_manager/cf_qemu.sh b/host/libs/vm_manager/cf_qemu.sh
new file mode 100644
index 0000000..8d49b04
--- /dev/null
+++ b/host/libs/vm_manager/cf_qemu.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+default_instance_number() {
+ if [[ "${USER::5}" == "vsoc-" ]]; then
+ echo "${USER: -2}"
+ else
+ echo "01"
+ fi
+}
+CUTTLEFISH_INSTANCE="${CUTTLEFISH_INSTANCE:-$(default_instance_number)}"
+default_instance_name="cvd-${CUTTLEFISH_INSTANCE}"
+default_uuid="699acfc4-c8c4-11e7-882b-5065f31dc1${CUTTLEFISH_INSTANCE}"
+default_dir="${HOME}/runfiles"
+default_mobile_tap_name="cvd-mtap-${CUTTLEFISH_INSTANCE}"
+
+if [[ -z "${ivshmem_vector_count}" ]]; then
+ echo "The required ivshmem_vector_count environment variable is not set" >&2
+ exit 1
+fi
+
+exec "${qemu_binary=/usr/bin/qemu-system-x86_64}" \
+ -enable-kvm \
+ -name "guest=${instance_name=${default_instance_name}},debug-threads=on" \
+ -machine "pc-i440fx-2.8,accel=kvm,usb=off,dump-guest-core=off" \
+ -m "${memory_mb=2048}" \
+ -realtime mlock=off \
+ -smp "${cpus=2},sockets=${cpus=2},cores=1,threads=1" \
+ -uuid "${uuid=${default_uuid}}"\
+ -display none \
+ -no-user-config \
+ -nodefaults \
+ -chardev "socket,id=charmonitor,path=${monitor_path=${default_dir}/qemu_monitor.sock},server,nowait" \
+ -mon "chardev=charmonitor,id=monitor,mode=control" \
+ -rtc "base=utc" \
+ -no-shutdown \
+ -boot "strict=on" \
+ -kernel "${kernel_image_path=${HOME}/kernel}" \
+ -initrd "${ramdisk_image_path=${HOME}/ramdisk.img}" \
+ -append "${kernel_args="loop.max_part=7 console=ttyS0 androidboot.console=ttyS1 androidboot.hardware=vsoc enforcing=0 audit=1 androidboot.selinux=permissive mac80211_hwsim.radios=0 security=selinux buildvariant=userdebug androidboot.serialno=CUTTLEFISHCVD01 androidboot.lcd_density=160"}" \
+ -dtb "${dtb_path=${HOME}/config/cuttlefish.dtb}" \
+ -device "piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2" \
+ -device "virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x3" \
+ -drive "file=${system_image_path=${HOME}/system.img},format=raw,if=none,id=drive-virtio-disk0,aio=threads" \
+ -device "virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1" \
+ -drive "file=${data_image_path=${HOME}/userdata.img},format=raw,if=none,id=drive-virtio-disk1,aio=threads" \
+ -device "virtio-blk-pci,scsi=off,bus=pci.0,addr=0x5,drive=drive-virtio-disk1,id=virtio-disk1" \
+ -drive "file=${cache_image_path=${HOME}/cache.img},format=raw,if=none,id=drive-virtio-disk2,aio=threads" \
+ -device "virtio-blk-pci,scsi=off,bus=pci.0,addr=0x6,drive=drive-virtio-disk2,id=virtio-disk2" \
+ -drive "file=${vendor_image_path=${HOME}/vendor.img},format=raw,if=none,id=drive-virtio-disk3,aio=threads" \
+ -device "virtio-blk-pci,scsi=off,bus=pci.0,addr=0x7,drive=drive-virtio-disk3,id=virtio-disk3" \
+ -netdev "tap,id=hostnet0,ifname=${mobile_tap_name=${default_mobile_tap_name}},script=no,downscript=no" \
+ -device "virtio-net-pci,netdev=hostnet0,id=net0,mac=00:43:56:44:01:01,bus=pci.0,addr=0x2" \
+ -chardev "socket,id=charserial0,path=${kernel_log_socket_name=${default_dir}/kernel-log}" \
+ -device "isa-serial,chardev=charserial0,id=serial0" \
+ -chardev "socket,id=charserial1,path=${console_path=${default_dir}/console},server,nowait" \
+ -device "isa-serial,chardev=charserial1,id=serial1" \
+ -chardev "file,id=charchannel0,path=${logcat_path=${default_dir}/logcat},append=on" \
+ -device "virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=cf-logcat" \
+ -device "virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x8" \
+ -object "rng-random,id=objrng0,filename=/dev/urandom" \
+ -device "virtio-rng-pci,rng=objrng0,id=rng0,max-bytes=1024,period=2000,bus=pci.0,addr=0x9" \
+ -chardev "socket,path=${ivshmem_qemu_socket_path=${default_dir}/ivshmem_socket_qemu},id=ivsocket" \
+ -device "ivshmem-doorbell,chardev=ivsocket,vectors=${ivshmem_vector_count}" \
+ -cpu host \
+ -msg "timestamp=on"
diff --git a/host/libs/vm_manager/libvirt_manager.cpp b/host/libs/vm_manager/libvirt_manager.cpp
index bebe12d..306f130 100644
--- a/host/libs/vm_manager/libvirt_manager.cpp
+++ b/host/libs/vm_manager/libvirt_manager.cpp
@@ -20,11 +20,14 @@
#include <cstdlib>
#include <iomanip>
#include <sstream>
+#include <string>
#include <gflags/gflags.h>
#include <glog/logging.h>
#include <libxml/tree.h>
+#include "host/libs/config/cuttlefish_config.h"
+
DEFINE_string(hypervisor_uri, "qemu:///system", "Hypervisor cannonical uri.");
DEFINE_bool(log_xml, false, "Log the XML machine configuration");
@@ -259,9 +262,7 @@
return cmd;
}
-} // namespace
-
-std::string LibvirtManager::BuildXmlConfig() const {
+std::string BuildXmlConfig() {
auto config = vsoc::CuttlefishConfig::Get();
std::string instance_name = config->instance_name();
@@ -328,6 +329,8 @@
xmlFree(tgt);
return out;
}
+} // namespace
+
bool LibvirtManager::Start() const {
std::string start_command = GetLibvirtCommand();
diff --git a/host/libs/vm_manager/libvirt_manager.h b/host/libs/vm_manager/libvirt_manager.h
index dbe7dcf..9537af6 100644
--- a/host/libs/vm_manager/libvirt_manager.h
+++ b/host/libs/vm_manager/libvirt_manager.h
@@ -15,22 +15,17 @@
*/
#pragma once
-#include <string>
-
-#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/vm_manager.h"
namespace vm_manager {
-class LibvirtManager {
+class LibvirtManager : public VmManager {
public:
LibvirtManager() = default;
- ~LibvirtManager() = default;
+ virtual ~LibvirtManager() = default;
- bool Start() const;
- bool Stop() const;
-
- protected:
- std::string BuildXmlConfig() const;
+ bool Start() const override;
+ bool Stop() const override;
};
} // namespace vm_manager
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
new file mode 100644
index 0000000..a12bc61
--- /dev/null
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/vm_manager/qemu_manager.h"
+
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <gflags/gflags.h>
+#include <glog/logging.h>
+
+#include "common/libs/utils/subprocess.h"
+#include "host/libs/config/cuttlefish_config.h"
+
+extern char** environ;
+
+DEFINE_string(qemu_binary,
+ "/usr/bin/qemu-system-x86_64",
+ "The qemu binary to use");
+
+namespace vm_manager {
+
+namespace {
+
+std::string GetMonitorPath() {
+ return vsoc::CuttlefishConfig::Get()->PerInstancePath("qemu_monitor.sock");
+}
+
+int BuildAndRunQemuCmd() {
+ auto config = vsoc::CuttlefishConfig::Get();
+ std::vector<std::string> qemu_envp;
+ // Copy this process' environment
+ char** env = environ;
+ while (*env) {
+ qemu_envp.push_back(*env);
+ ++env;
+ }
+ // Set the config values in the environment
+ qemu_envp.push_back("qemu_binary=" + FLAGS_qemu_binary);
+ qemu_envp.push_back("instance_name=" + config->instance_name());
+ qemu_envp.push_back("memory_mb=" + std::to_string(config->memory_mb()));
+ qemu_envp.push_back("cpus=" + std::to_string(config->cpus()));
+ qemu_envp.push_back("uuid=" + config->uuid());
+ qemu_envp.push_back("monitor_path=" +
+ config->PerInstancePath("qemu_monitor.sock"));
+ qemu_envp.push_back("kernel_image_path=" + config->kernel_image_path());
+ qemu_envp.push_back("ramdisk_image_path=" + config->ramdisk_image_path());
+ qemu_envp.push_back("kernel_args=" + config->kernel_args());
+ qemu_envp.push_back("dtb_path=" + config->dtb_path());
+ qemu_envp.push_back("system_image_path=" + config->system_image_path());
+ qemu_envp.push_back("data_image_path=" + config->data_image_path());
+ qemu_envp.push_back("cache_image_path=" + config->cache_image_path());
+ qemu_envp.push_back("vendor_image_path=" + config->vendor_image_path());
+ qemu_envp.push_back("mobile_tap_name=" + config->mobile_tap_name());
+ qemu_envp.push_back("kernel_log_socket_name=" +
+ config->kernel_log_socket_name());
+ qemu_envp.push_back("console_path=" + config->console_path());
+ qemu_envp.push_back("logcat_path=" + config->logcat_path());
+ qemu_envp.push_back("ivshmem_qemu_socket_path=" +
+ config->ivshmem_qemu_socket_path());
+ qemu_envp.push_back("ivshmem_vector_count=" +
+ std::to_string(config->ivshmem_vector_count()));
+ for (const auto& arg : qemu_envp) {
+ LOG(INFO) << arg;
+ }
+ return cvd::execute({vsoc::DefaultHostArtifactsPath("bin/cf_qemu.sh")},
+ qemu_envp);
+}
+
+} // namespace
+
+bool QemuManager::Start() const {
+ // Create a thread that will make the launcher abort if the qemu process
+ // crashes, this avoids having the launcher waiting forever for
+ // VIRTUAL_DEVICE_BOOT_COMPLETED in this cases.
+ std::thread waiting_thread([]() {
+ int status = BuildAndRunQemuCmd();
+ if (status != 0) {
+ LOG(FATAL) << "Qemu process exited prematurely";
+ } else {
+ LOG(ERROR) << "Qemu process exited normally, it shouldn't happen";
+ }
+ });
+ waiting_thread.detach();
+ return true;
+}
+bool QemuManager::Stop() const {
+ int errno_;
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ errno_ = errno;
+ LOG(ERROR) << "Error creating socket: " << strerror(errno_);
+ return false;
+ }
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ std::string monitor_path = GetMonitorPath();
+ strncpy(addr.sun_path, monitor_path.c_str(), sizeof(addr.sun_path) - 1);
+
+ if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
+ errno_ = errno;
+ LOG(ERROR) << "Error connecting to qemu monitor: " << strerror(errno_);
+ return false;
+ }
+
+ char msg[] = "{\"execute\":\"qmp_capabilities\"}{\"execute\":\"quit\"}";
+ ssize_t len = sizeof(msg) - 1;
+ while (len > 0) {
+ int tmp = TEMP_FAILURE_RETRY(write(fd, msg, len));
+ if (tmp < 0) {
+ LOG(ERROR) << "Error writing to socket";
+ }
+ len -= tmp;
+ }
+ // Log the reply
+ char buff[1000];
+ while ((len = TEMP_FAILURE_RETRY(read(fd, buff, sizeof(buff) - 1))) > 0) {
+ buff[len] = '\0';
+ LOG(INFO) << "From qemu monitor: " << buff;
+ }
+
+ return true;
+}
+
+} // namespace vm_manager
diff --git a/host/libs/vm_manager/qemu_manager.h b/host/libs/vm_manager/qemu_manager.h
new file mode 100644
index 0000000..566c106
--- /dev/null
+++ b/host/libs/vm_manager/qemu_manager.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "host/libs/vm_manager/vm_manager.h"
+
+namespace vm_manager {
+
+// Starts a guest VM using the qemu command directly. It requires the host
+// package to support the qemu-cli capability.
+class QemuManager : public VmManager {
+ public:
+ QemuManager() = default;
+ virtual ~QemuManager() = default;
+
+ bool Start() const override;
+ bool Stop() const override;
+};
+
+} // namespace vm_manager
diff --git a/host/libs/vm_manager/vm_manager.cpp b/host/libs/vm_manager/vm_manager.cpp
new file mode 100644
index 0000000..787e90b
--- /dev/null
+++ b/host/libs/vm_manager/vm_manager.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "host/libs/vm_manager/vm_manager.h"
+
+#include "host/libs/config/cuttlefish_config.h"
+#include "host/libs/vm_manager/libvirt_manager.h"
+#include "host/libs/vm_manager/qemu_manager.h"
+
+namespace vm_manager {
+std::shared_ptr<VmManager> VmManager::Get() {
+ static std::shared_ptr<VmManager> vm_manager(
+ vsoc::HostSupportsQemuCli()
+ ? static_cast<VmManager*>(new QemuManager())
+ : static_cast<VmManager*>(new LibvirtManager()));
+ return vm_manager;
+}
+} // namespace vm_manager
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
new file mode 100644
index 0000000..a329571
--- /dev/null
+++ b/host/libs/vm_manager/vm_manager.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <memory>
+#include <string>
+
+namespace vm_manager {
+
+// Superclass of every guest VM manager. It provides a static getter that
+// chooses the best subclass to instantiate based on the capabilities supported
+// by the host packages.
+class VmManager {
+ public:
+ // Returns the most suitable vm manager as a singleton.
+ static std::shared_ptr<VmManager> Get();
+ virtual ~VmManager() = default;
+
+ virtual bool Start() const = 0;
+ virtual bool Stop() const = 0;
+};
+
+} // namespace vm_manager