diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 0978536da4..3b34b106ea 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -154,6 +154,8 @@ class Utils : public Singleton // virtual machine helpers virtual void wait_for_cloud_init(VirtualMachine* virtual_machine, std::chrono::milliseconds timeout, const SSHKeyProvider& key_provider) const; + virtual Path derive_instances_dir(const Path& data_dir, const Path& backend_directory_name, + const Path& instances_subdir) const; // system info helpers virtual std::string get_kernel_version() const; diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 2b02b31098..84d068740c 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -20,6 +20,7 @@ #include "disabled_copy_move.h" #include "ip_address.h" +#include "path.h" #include #include @@ -91,11 +92,11 @@ class VirtualMachine : private DisabledCopyMove virtual int get_num_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr get_snapshot(const std::string& name) = 0; - virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, - const std::string& name, const std::string& comment) = 0; - virtual void delete_snapshot(const QDir& snapshot_dir, const std::string& name) = 0; - virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; - virtual void load_snapshots(const QDir& snapshot_dir) = 0; + virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) = 0; + virtual void delete_snapshot(const std::string& name) = 0; + virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; + virtual void load_snapshots() = 0; virtual std::vector get_childrens_names(const Snapshot* parent) const = 0; VirtualMachine::State state; @@ -106,8 +107,12 @@ class VirtualMachine : private DisabledCopyMove bool shutdown_while_starting{false}; protected: - VirtualMachine(VirtualMachine::State state, const std::string& vm_name) : state{state}, vm_name{vm_name} {}; - VirtualMachine(const std::string& vm_name) : VirtualMachine(State::off, vm_name){}; + const QDir instance_dir; + + VirtualMachine(VirtualMachine::State state, const std::string& vm_name, const Path& instance_dir) + : state{state}, vm_name{vm_name}, instance_dir{QDir{instance_dir}} {}; + VirtualMachine(const std::string& vm_name, const Path& instance_dir) + : VirtualMachine(State::off, vm_name, instance_dir){}; }; } // namespace multipass #endif // MULTIPASS_VIRTUAL_MACHINE_H diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 60380dff71..123e354d86 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -59,8 +59,9 @@ class VirtualMachineFactory : private DisabledCopyMove virtual VMImage prepare_source_image(const VMImage& source_image) = 0; virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0; virtual void hypervisor_health_check() = 0; - virtual QString get_backend_directory_name() = 0; - virtual QString get_backend_version_string() = 0; + virtual QString get_backend_directory_name() const = 0; + virtual Path get_instance_directory(const std::string& name) const = 0; + virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, const days& days_to_expire) = 0; diff --git a/include/multipass/vm_image_vault.h b/include/multipass/vm_image_vault.h index 6f31f54fae..e4a24f1bc0 100644 --- a/include/multipass/vm_image_vault.h +++ b/include/multipass/vm_image_vault.h @@ -82,7 +82,7 @@ class VMImageVault : private DisabledCopyMove virtual ~VMImageVault() = default; virtual VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum) = 0; + const std::optional& checksum, const Path& save_dir) = 0; virtual void remove(const std::string& name) = 0; virtual bool has_record_for(const std::string& name) = 0; virtual void prune_expired_images() = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 89d2041df8..5f653ea73e 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -437,19 +437,15 @@ QJsonObject vm_spec_to_json(const mp::VMSpecs& specs) return json; } -auto fetch_image_for(const std::string& name, const mp::FetchType& fetch_type, mp::VMImageVault& vault) +auto fetch_image_for(const std::string& name, mp::VirtualMachineFactory& factory, mp::VMImageVault& vault) { auto stub_prepare = [](const mp::VMImage&) -> mp::VMImage { return {}; }; auto stub_progress = [](int download_type, int progress) { return true; }; mp::Query query{name, "", false, "", mp::Query::Type::Alias, false}; - return vault.fetch_image(fetch_type, query, stub_prepare, stub_progress, false, std::nullopt); -} - -QDir instance_directory(const std::string& instance_name, const mp::DaemonConfig& config) -{ // TODO should we establish a more direct way to get to the instance's directory? - return mp::utils::base_dir(fetch_image_for(instance_name, config.factory->fetch_type(), *config.vault).image_path); + return vault.fetch_image(factory.fetch_type(), query, stub_prepare, stub_progress, false, std::nullopt, + factory.get_instance_directory(name)); } auto try_mem_size(const std::string& val) -> std::optional @@ -1383,7 +1379,7 @@ mp::Daemon::Daemon(std::unique_ptr the_config) continue; } - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto vm_image = fetch_image_for(name, *config->factory, *config->vault); if (!vm_image.image_path.isEmpty() && !QFile::exists(vm_image.image_path)) { mpl::log(mpl::Level::warning, category, @@ -1410,7 +1406,7 @@ mp::Daemon::Daemon(std::unique_ptr the_config) auto& instance_record = spec.deleted ? deleted_instances : operative_instances; auto instance = instance_record[name] = config->factory->create_virtual_machine(vm_desc, *this); - instance->load_snapshots(instance_directory(name, *config)); + instance->load_snapshots(); allocated_mac_addrs = std::move(new_macs); // Add the new macs to the daemon's list only if we got this far @@ -1818,7 +1814,7 @@ try // clang-format on entry->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); // FIXME: Set the release to the cached current version when supported - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto vm_image = fetch_image_for(name, *config->factory, *config->vault); auto current_release = vm_image.original_release; if (!vm_image.id.empty() && current_release.empty()) @@ -2264,7 +2260,7 @@ try // clang-format on assert(purge && "precondition: snapshots can only be purged"); for (const auto& snapshot_name : pick) - vm_it->second->delete_snapshot(instance_directory(instance_name, *config), snapshot_name); + vm_it->second->delete_snapshot(snapshot_name); } } } @@ -2499,8 +2495,7 @@ try SnapshotReply reply; { - const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, - snapshot_name, request->comment()); + const auto snapshot = vm_ptr->take_snapshot(spec_it->second, snapshot_name, request->comment()); reply.set_snapshot(snapshot->get_name()); } @@ -2550,7 +2545,6 @@ try // Only need to check if the snapshot exists so the result is discarded vm_ptr->get_snapshot(request->snapshot()); - const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { RestoreReply confirm_action{}; @@ -2566,8 +2560,8 @@ try { reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); - const auto snapshot = vm_ptr->take_snapshot(vm_dir, vm_specs, "", - fmt::format("Before restoring {}", request->snapshot())); + const auto snapshot = + vm_ptr->take_snapshot(vm_specs, "", fmt::format("Before restoring {}", request->snapshot())); reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), /* sticky = */ true); @@ -2577,7 +2571,7 @@ try // Actually restore snapshot reply_msg(server, "Restoring snapshot"); auto old_specs = vm_specs; - vm_ptr->restore_snapshot(vm_dir, request->snapshot(), vm_specs); + vm_ptr->restore_snapshot(request->snapshot(), vm_specs); auto mounts_it = mounts.find(instance_name); assert(mounts_it != mounts.end() && "uninitialized mounts"); @@ -2662,8 +2656,8 @@ void mp::Daemon::persist_instances() void mp::Daemon::release_resources(const std::string& instance) { - config->factory->remove_resources_for(instance); config->vault->remove(instance); + config->factory->remove_resources_for(instance); auto spec_it = vm_instance_specs.find(instance); if (spec_it != cend(vm_instance_specs)) @@ -2921,8 +2915,9 @@ void mp::Daemon::create_vm(const CreateRequest* request, if (!vm_desc.image.id.empty()) checksum = vm_desc.image.id; - auto vm_image = config->vault->fetch_image(fetch_type, query, prepare_action, progress_monitor, - launch_from_blueprint, checksum); + auto vm_image = + config->vault->fetch_image(fetch_type, query, prepare_action, progress_monitor, launch_from_blueprint, + checksum, config->factory->get_instance_directory(name)); const auto image_size = config->vault->minimum_image_size_for(vm_image.id); vm_desc.disk_space = compute_final_image_size( @@ -3363,7 +3358,7 @@ void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem else info->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto vm_image = fetch_image_for(name, *config->factory, *config->vault); auto original_release = vm_image.original_release; if (!vm_image.id.empty() && original_release.empty()) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 85be7bf183..7f6d9d73a6 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -218,15 +218,27 @@ mp::MemorySize get_image_size(const mp::Path& image_path) return image_size; } + +template +void persist_records(const T& records, const QString& path) +{ + QJsonObject json_records; + for (const auto& record : records) + { + auto key = QString::fromStdString(record.first); + json_records.insert(key, record_to_json(record.second)); + } + mp::write_json(json_records, path); +} } // namespace mp::DefaultVMImageVault::DefaultVMImageVault(std::vector image_hosts, URLDownloader* downloader, - mp::Path cache_dir_path, mp::Path data_dir_path, mp::days days_to_expire) + const mp::Path& cache_dir_path, const mp::Path& data_dir_path, + const mp::days& days_to_expire) : BaseVMImageVault{image_hosts}, url_downloader{downloader}, cache_dir{QDir(cache_dir_path).filePath("vault")}, data_dir{QDir(data_dir_path).filePath("vault")}, - instances_dir(data_dir.filePath("instances")), images_dir(cache_dir.filePath("images")), days_to_expire{days_to_expire}, prepared_image_records{load_db(cache_dir.filePath(image_db_name))}, @@ -241,7 +253,8 @@ mp::DefaultVMImageVault::~DefaultVMImageVault() mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, - const bool unlock, const std::optional& checksum) + const bool unlock, const std::optional& checksum, + const mp::Path& save_dir) { { std::lock_guard lock{fetch_mutex}; @@ -269,11 +282,11 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (source_image.image_path.endsWith(".xz")) { - source_image.image_path = extract_image_from(query.name, source_image, monitor); + source_image.image_path = extract_image_from(source_image, monitor, save_dir); } else { - source_image = image_instance_from(query.name, source_image); + source_image = image_instance_from(source_image, save_dir); } vm_image = prepare(source_image); @@ -314,7 +327,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (last_modified.isValid() && (last_modified.toString().toStdString() == record.image.release_date)) { - return finalize_image_records(query, record.image, id); + return finalize_image_records(query, record.image, id, save_dir); } } @@ -376,7 +389,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co const auto prepared_image = record.second.image; try { - return finalize_image_records(query, prepared_image, record.first); + return finalize_image_records(query, prepared_image, record.first, save_dir); } catch (const std::exception& e) { @@ -414,7 +427,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co auto prepared_image = future.result(); std::lock_guard lock{fetch_mutex}; in_progress_image_fetches.erase(id); - return finalize_image_records(query, prepared_image, id); + return finalize_image_records(query, prepared_image, id, save_dir); } catch (const std::exception&) { @@ -431,10 +444,6 @@ void mp::DefaultVMImageVault::remove(const std::string& name) if (name_entry == instance_image_records.end()) return; - QDir instance_dir{instances_dir}; - if (instance_dir.cd(QString::fromStdString(name))) - instance_dir.removeRecursively(); - instance_image_records.erase(name); persist_instance_records(); } @@ -523,7 +532,8 @@ void mp::DefaultVMImageVault::update_images(const FetchType& fetch_type, const P mpl::log(mpl::Level::info, category, fmt::format("Updating {} source image to latest", record.query.release)); try { - fetch_image(fetch_type, record.query, prepare, monitor, false, std::nullopt); + fetch_image(fetch_type, record.query, prepare, monitor, false, std::nullopt, + QFileInfo{record.image.image_path}.absolutePath()); // Remove old image std::lock_guard lock{fetch_mutex}; @@ -620,25 +630,22 @@ mp::VMImage mp::DefaultVMImageVault::download_and_prepare_source_image( } } -QString mp::DefaultVMImageVault::extract_image_from(const std::string& instance_name, const VMImage& source_image, - const ProgressMonitor& monitor) +QString mp::DefaultVMImageVault::extract_image_from(const VMImage& source_image, const ProgressMonitor& monitor, + const mp::Path& dest_dir) { - const auto name = QString::fromStdString(instance_name); - const QDir output_dir{MP_UTILS.make_dir(instances_dir, name)}; + MP_UTILS.make_dir(dest_dir); QFileInfo file_info{source_image.image_path}; const auto image_name = file_info.fileName().remove(".xz"); - const auto image_path = output_dir.filePath(image_name); + const auto image_path = QDir(dest_dir).filePath(image_name); return mp::vault::extract_image(image_path, monitor); } -mp::VMImage mp::DefaultVMImageVault::image_instance_from(const std::string& instance_name, - const VMImage& prepared_image) +mp::VMImage mp::DefaultVMImageVault::image_instance_from(const VMImage& prepared_image, const mp::Path& dest_dir) { - auto name = QString::fromStdString(instance_name); - auto output_dir = MP_UTILS.make_dir(instances_dir, name); + MP_UTILS.make_dir(dest_dir); - return {mp::vault::copy(prepared_image.image_path, output_dir), + return {mp::vault::copy(prepared_image.image_path, dest_dir), prepared_image.id, prepared_image.original_release, prepared_image.current_release, @@ -658,13 +665,13 @@ std::optional> mp::DefaultVMImageVault::get_image_future(co } mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, const VMImage& prepared_image, - const std::string& id) + const std::string& id, const mp::Path& dest_dir) { VMImage vm_image; if (!query.name.empty()) { - vm_image = image_instance_from(query.name, prepared_image); + vm_image = image_instance_from(prepared_image, dest_dir); instance_image_records[query.name] = {vm_image, query, std::chrono::system_clock::now()}; } @@ -679,21 +686,6 @@ mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, return vm_image; } -namespace -{ -template -void persist_records(const T& records, const QString& path) -{ - QJsonObject json_records; - for (const auto& record : records) - { - auto key = QString::fromStdString(record.first); - json_records.insert(key, record_to_json(record.second)); - } - mp::write_json(json_records, path); -} -} // namespace - void mp::DefaultVMImageVault::persist_instance_records() { persist_records(instance_image_records, data_dir.filePath(instance_db_name)); diff --git a/src/daemon/default_vm_image_vault.h b/src/daemon/default_vm_image_vault.h index 5a00887370..bea7e74c83 100644 --- a/src/daemon/default_vm_image_vault.h +++ b/src/daemon/default_vm_image_vault.h @@ -45,13 +45,14 @@ class VaultRecord class DefaultVMImageVault final : public BaseVMImageVault { public: - DefaultVMImageVault(std::vector image_host, URLDownloader* downloader, multipass::Path cache_dir_path, - multipass::Path data_dir_path, multipass::days days_to_expire); + DefaultVMImageVault(std::vector image_host, URLDownloader* downloader, + const multipass::Path& cache_dir_path, const multipass::Path& data_dir_path, + const multipass::days& days_to_expire); ~DefaultVMImageVault(); VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum) override; + const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + const Path& save_dir) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; void prune_expired_images() override; @@ -60,21 +61,20 @@ class DefaultVMImageVault final : public BaseVMImageVault MemorySize minimum_image_size_for(const std::string& id) override; private: - VMImage image_instance_from(const std::string& name, const VMImage& prepared_image); + VMImage image_instance_from(const VMImage& prepared_image, const Path& dest_dir); VMImage download_and_prepare_source_image(const VMImageInfo& info, std::optional& existing_source_image, const QDir& image_dir, const FetchType& fetch_type, const PrepareAction& prepare, const ProgressMonitor& monitor); - QString extract_image_from(const std::string& instance_name, const VMImage& source_image, - const ProgressMonitor& monitor); + QString extract_image_from(const VMImage& source_image, const ProgressMonitor& monitor, const Path& dest_dir); std::optional> get_image_future(const std::string& id); - VMImage finalize_image_records(const Query& query, const VMImage& prepared_image, const std::string& id); + VMImage finalize_image_records(const Query& query, const VMImage& prepared_image, const std::string& id, + const Path& dest_dir); void persist_image_records(); void persist_instance_records(); URLDownloader* const url_downloader; const QDir cache_dir; const QDir data_dir; - const QDir instances_dir; const QDir images_dir; const days days_to_expire; std::mutex fetch_mutex; diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index e95d360b7d..db73972ce8 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -259,8 +259,9 @@ void update_max_and_property(virDomainPtr domain_ptr, Updater* fun_ptr, Integer mp::LibVirtVirtualMachine::LibVirtVirtualMachine(const mp::VirtualMachineDescription& desc, const std::string& bridge_name, mp::VMStatusMonitor& monitor, - const mp::LibvirtWrapper::UPtr& libvirt_wrapper) - : BaseVirtualMachine{desc.vm_name}, + const mp::LibvirtWrapper::UPtr& libvirt_wrapper, + const mp::Path& instance_dir) + : BaseVirtualMachine{desc.vm_name, instance_dir}, username{desc.ssh_username}, desc{desc}, monitor{&monitor}, diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index e2691c15fb..eca09c0cdc 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -36,7 +36,8 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine using NetworkUPtr = std::unique_ptr; LibVirtVirtualMachine(const VirtualMachineDescription& desc, const std::string& bridge_name, - VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper); + VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper, + const mp::Path& instance_dir); ~LibVirtVirtualMachine(); void start() override; diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 358b4f3e95..dfdb429037 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -108,7 +108,9 @@ auto make_libvirt_wrapper(const std::string& libvirt_object_path) mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir, const std::string& libvirt_object_path) - : libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)}, + : BaseVirtualMachineFactory( + MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir)), + libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)}, data_dir{data_dir}, bridge_name{enable_libvirt_network(data_dir, libvirt_wrapper)}, libvirt_object_path{libvirt_object_path} @@ -126,7 +128,8 @@ mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machin if (bridge_name.empty()) bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); - return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper); + return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper, + get_instance_directory(desc.vm_name)); } mp::LibVirtVirtualMachineFactory::~LibVirtVirtualMachineFactory() @@ -141,7 +144,7 @@ mp::LibVirtVirtualMachineFactory::~LibVirtVirtualMachineFactory() } } -void mp::LibVirtVirtualMachineFactory::remove_resources_for(const std::string& name) +void mp::LibVirtVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { auto connection = LibVirtVirtualMachine::open_libvirt_connection(libvirt_wrapper); @@ -175,7 +178,7 @@ void mp::LibVirtVirtualMachineFactory::hypervisor_health_check() bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); } -QString mp::LibVirtVirtualMachineFactory::get_backend_version_string() +QString mp::LibVirtVirtualMachineFactory::get_backend_version_string() const { try { diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h index 5959a5eb13..f26fbc9a9a 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h @@ -37,15 +37,17 @@ class LibVirtVirtualMachineFactory final : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) override; - void remove_resources_for(const std::string& name) override; VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; - QString get_backend_version_string() override; + QString get_backend_version_string() const override; // Making this public makes this modifiable which is necessary for testing LibvirtWrapper::UPtr libvirt_wrapper; +protected: + void remove_resources_for_impl(const std::string& name) override; + private: const Path data_dir; std::string bridge_name; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 964604a4e7..5a9d00054a 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -168,8 +168,9 @@ bool uses_default_id_mappings(const multipass::VMMount& mount) mp::LXDVirtualMachine::LXDVirtualMachine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor, NetworkAccessManager* manager, const QUrl& base_url, - const QString& bridge_name, const QString& storage_pool) - : BaseVirtualMachine{desc.vm_name}, + const QString& bridge_name, const QString& storage_pool, + const mp::Path& instance_dir) + : BaseVirtualMachine{desc.vm_name, instance_dir}, name{QString::fromStdString(desc.vm_name)}, username{desc.ssh_username}, monitor{&monitor}, diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index 59802cf868..8b842706f2 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -34,7 +34,8 @@ class LXDVirtualMachine : public BaseVirtualMachine { public: LXDVirtualMachine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor, NetworkAccessManager* manager, - const QUrl& base_url, const QString& bridge_name, const QString& storage_pool); + const QUrl& base_url, const QString& bridge_name, const QString& storage_pool, + const mp::Path& instance_dir); ~LXDVirtualMachine() override; void stop() override; void start() override; diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index e70096cddd..d7683b0239 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -81,13 +82,13 @@ mp::NetworkInterfaceInfo munch_network(std::map(desc, monitor, manager.get(), base_url, multipass_bridge_name, - storage_pool); + storage_pool, + MP_UTILS.make_dir(get_instance_directory(desc.vm_name))); } -void mp::LXDVirtualMachineFactory::remove_resources_for(const std::string& name) +void mp::LXDVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { - mpl::log(mpl::Level::trace, category, fmt::format("No resources to remove for \"{}\"", name)); + mpl::log(mpl::Level::trace, category, fmt::format("No further resources to remove for \"{}\"", name)); } auto mp::LXDVirtualMachineFactory::prepare_source_image(const VMImage& source_image) -> VMImage @@ -205,7 +207,7 @@ void mp::LXDVirtualMachineFactory::hypervisor_health_check() } } -QString mp::LXDVirtualMachineFactory::get_backend_version_string() +QString mp::LXDVirtualMachineFactory::get_backend_version_string() const { auto reply = lxd_request(manager.get(), "GET", base_url); diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.h b/src/platform/backends/lxd/lxd_virtual_machine_factory.h index f904633c8d..e2259de958 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.h +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.h @@ -37,15 +37,14 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory void prepare_networking(std::vector& extra_interfaces) override; VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) override; - void remove_resources_for(const std::string& name) override; VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; - QString get_backend_directory_name() override + QString get_backend_directory_name() const override { return "lxd"; }; - QString get_backend_version_string() override; + QString get_backend_version_string() const override; VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, const days& days_to_expire) override; @@ -54,11 +53,11 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory std::vector networks() const override; protected: + void remove_resources_for_impl(const std::string& name) override; std::string create_bridge_with(const NetworkInterfaceInfo& interface) override; private: NetworkAccessManager::UPtr manager; - const Path data_dir; const QUrl base_url; QString storage_pool; }; diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.cpp b/src/platform/backends/lxd/lxd_vm_image_vault.cpp index 7b3c8e5655..ab8669fd2e 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.cpp +++ b/src/platform/backends/lxd/lxd_vm_image_vault.cpp @@ -159,7 +159,8 @@ mp::LXDVMImageVault::LXDVMImageVault(std::vector image_hosts, URLD mp::VMImage mp::LXDVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, - const bool unlock, const std::optional& checksum) + const bool unlock, const std::optional& checksum, + const mp::Path& /* save_dir */) { // Look for an already existing instance and get its image info try diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.h b/src/platform/backends/lxd/lxd_vm_image_vault.h index fe6ade8271..ed4f63c856 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.h +++ b/src/platform/backends/lxd/lxd_vm_image_vault.h @@ -40,8 +40,8 @@ class LXDVMImageVault final : public BaseVMImageVault const QUrl& base_url, const QString& cache_dir_path, const multipass::days& days_to_expire); VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum) override; + const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + const Path& /* save_dir */) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; void prune_expired_images() override; diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index 953a161e72..ae8f37d049 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -50,7 +50,7 @@ class QemuPlatform : private DisabledCopyMove return {}; }; virtual QStringList vm_platform_args(const VirtualMachineDescription& vm_desc) = 0; - virtual QString get_directory_name() + virtual QString get_directory_name() const { return {}; }; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 5227bbd57c..24ae05e612 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -197,10 +197,10 @@ auto generate_metadata(const QStringList& platform_args, const QStringList& proc } // namespace mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, - VMStatusMonitor& monitor) + VMStatusMonitor& monitor, const mp::Path& instance_dir) : BaseVirtualMachine{mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag) ? State::suspended : State::off, - desc.vm_name}, + desc.vm_name, instance_dir}, desc{desc}, mac_addr{desc.default_mac_address}, username{desc.ssh_username}, diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index de1086c5ea..2928104e05 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -41,7 +41,8 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine public: using MountArgs = std::unordered_map>; - QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, VMStatusMonitor& monitor); + QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, VMStatusMonitor& monitor, + const Path& instance_dir); ~QemuVirtualMachine(); void start() override; @@ -69,7 +70,8 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine void on_reset_network(); protected: - QemuVirtualMachine(const std::string& name) : BaseVirtualMachine{name} + // TODO remove this, the onus of composing a VM of stubs should be on the stub VMs + QemuVirtualMachine(const std::string& name, const mp::Path& instance_dir) : BaseVirtualMachine{name, instance_dir} { } diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 8f5d9c2b93..42d1d865a2 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -37,17 +37,25 @@ constexpr auto category = "qemu factory"; } // namespace mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_dir) - : qemu_platform{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir)} + : QemuVirtualMachineFactory{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir), data_dir} +{ +} + +mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const mp::Path& data_dir) + : BaseVirtualMachineFactory( + MP_UTILS.derive_instances_dir(data_dir, qemu_platform->get_directory_name(), instances_subdir)), + qemu_platform{std::move(qemu_platform)} { } mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) { - return std::make_unique(desc, qemu_platform.get(), monitor); + return std::make_unique(desc, qemu_platform.get(), monitor, + get_instance_directory(desc.vm_name)); } -void mp::QemuVirtualMachineFactory::remove_resources_for(const std::string& name) +void mp::QemuVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { qemu_platform->remove_resources_for(name); } @@ -70,7 +78,7 @@ void mp::QemuVirtualMachineFactory::hypervisor_health_check() qemu_platform->platform_health_check(); } -QString mp::QemuVirtualMachineFactory::get_backend_version_string() +QString mp::QemuVirtualMachineFactory::get_backend_version_string() const { auto process = mp::platform::make_process(simple_process_spec(QString("qemu-system-%1").arg(HOST_ARCH), {"--version"})); @@ -109,7 +117,7 @@ QString mp::QemuVirtualMachineFactory::get_backend_version_string() return QString("qemu-unknown"); } -QString mp::QemuVirtualMachineFactory::get_backend_directory_name() +QString mp::QemuVirtualMachineFactory::get_backend_directory_name() const { return qemu_platform->get_directory_name(); } diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index b0e6b918c2..bc09eedf16 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -35,15 +35,19 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) override; - void remove_resources_for(const std::string& name) override; VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; - QString get_backend_version_string() override; - QString get_backend_directory_name() override; + QString get_backend_version_string() const override; + QString get_backend_directory_name() const override; std::vector networks() const override; +protected: + void remove_resources_for_impl(const std::string& name) override; + private: + QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir); + QemuPlatform::UPtr qemu_platform; }; } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1a808d88e3..2f64ec215b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -98,9 +98,12 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted namespace multipass { -BaseVirtualMachine::BaseVirtualMachine(St state, const std::string& vm_name) : VirtualMachine(state, vm_name){}; +BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, + const mp::Path& instance_dir) + : VirtualMachine(state, vm_name, instance_dir){}; -BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; +BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name, const mp::Path& instance_dir) + : VirtualMachine(vm_name, instance_dir){}; std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& key_provider) { @@ -197,8 +200,8 @@ auto BaseVirtualMachine::make_take_snapshot_rollback(SnapshotMap::iterator it) }); } -std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, - const std::string& name, const std::string& comment) +std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) { std::string snapshot_name; @@ -223,7 +226,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn ret->capture(); ++snapshot_count; - persist_head_snapshot(snapshot_dir); + persist_head_snapshot(); rollback_on_failure.dismiss(); log_latest_snapshot(std::move(lock)); @@ -272,14 +275,14 @@ auto BaseVirtualMachine::make_parent_update_rollback(const std::shared_ptr& snapshot) +void BaseVirtualMachine::delete_snapshot_helper(std::shared_ptr& snapshot) { // Remove snapshot file QTemporaryDir tmp_dir{}; if (!tmp_dir.isValid()) throw std::runtime_error{"Could not create temporary directory"}; - auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot->get_name()); + auto snapshot_fileinfo = find_snapshot_file(instance_dir, snapshot->get_name()); auto snapshot_filepath = snapshot_fileinfo.filePath(); auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); @@ -293,7 +296,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s // Update head if deleted auto wrote_head = false; - auto head_path = derive_head_path(snapshot_dir); + auto head_path = derive_head_path(instance_dir); auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); wrote_head = updated_deleted_head(snapshot, head_path); @@ -302,7 +305,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s updated_snapshot_paths.reserve(snapshots.size()); auto rollback_parent_updates = make_parent_update_rollback(snapshot, updated_snapshot_paths); - update_parents(snapshot_dir, snapshot, updated_snapshot_paths); + update_parents(snapshot, updated_snapshot_paths); // Erase the snapshot with the backend and dismiss rollbacks on success snapshot->erase(); @@ -311,7 +314,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s rollback_snapshot_file.dismiss(); } -void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, +void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) { auto new_parent = deleted_parent->get_parent(); @@ -322,14 +325,14 @@ void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_pt other->set_parent(new_parent); updated_snapshot_paths[other.get()]; - const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); + const auto other_filepath = find_snapshot_file(instance_dir, other->get_name()).filePath(); write_json(other->serialize(), other_filepath); updated_snapshot_paths[other.get()] = other_filepath; } } } -void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) +void BaseVirtualMachine::delete_snapshot(const std::string& name) { std::unique_lock lock{snapshot_mutex}; @@ -338,22 +341,22 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st throw NoSuchSnapshot{vm_name, name}; auto snapshot = it->second; - delete_snapshot_helper(snapshot_dir, snapshot); + delete_snapshot_helper(snapshot); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } -void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) +void BaseVirtualMachine::load_snapshots() { std::unique_lock lock{snapshot_mutex}; - auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*.%1"}.arg(snapshot_extension)}, + auto snapshot_files = MP_FILEOPS.entryInfoList(instance_dir, {QString{"*.%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) load_snapshot_from_file(finfo.filePath()); - load_generic_snapshot_info(snapshot_dir); + load_generic_snapshot_info(); } std::vector BaseVirtualMachine::get_childrens_names(const Snapshot* parent) const @@ -367,13 +370,13 @@ std::vector BaseVirtualMachine::get_childrens_names(const Snapshot* return children; } -void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) +void BaseVirtualMachine::load_generic_snapshot_info() { try { - snapshot_count = std::stoi(mpu::contents_of(snapshot_dir.filePath(count_filename))); + snapshot_count = std::stoi(mpu::contents_of(instance_dir.filePath(count_filename))); - auto head_name = mpu::contents_of(snapshot_dir.filePath(head_filename)); + auto head_name = mpu::contents_of(instance_dir.filePath(head_filename)); head_snapshot = head_name.empty() ? nullptr : get_snapshot(head_name); } catch (FileOpenFailedException&) @@ -453,16 +456,16 @@ void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, QFile& }); } -void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const +void BaseVirtualMachine::persist_head_snapshot() const { assert(head_snapshot); const auto snapshot_filename = derive_snapshot_filename(derive_index_string(snapshot_count), QString::fromStdString(head_snapshot->get_name())); - auto snapshot_filepath = snapshot_dir.filePath(snapshot_filename); - auto head_path = derive_head_path(snapshot_dir); - auto count_path = snapshot_dir.filePath(count_filename); + auto snapshot_filepath = instance_dir.filePath(snapshot_filename); + auto head_path = derive_head_path(instance_dir); + auto count_path = instance_dir.filePath(count_filename); auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filepath]() noexcept { QFile{snapshot_filepath}.remove(); // best effort, ignore return @@ -513,7 +516,7 @@ void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, const st } } -void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) +void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& specs) { std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition @@ -526,7 +529,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s snapshot->apply(); - const auto head_path = derive_head_path(snapshot_dir); + const auto head_path = derive_head_path(instance_dir); auto rollback = make_restore_rollback(head_path, specs); specs.state = snapshot->get_state(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 85a487994b..0e2d7aed3a 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -43,8 +43,8 @@ namespace multipass class BaseVirtualMachine : public VirtualMachine { public: - BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name); - BaseVirtualMachine(const std::string& vm_name); + BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, const mp::Path& instance_dir); + BaseVirtualMachine(const std::string& vm_name, const mp::Path& instance_dir); std::vector get_all_ipv4(const SSHKeyProvider& key_provider) override; std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, @@ -64,11 +64,11 @@ class BaseVirtualMachine : public VirtualMachine // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor - std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, - const std::string& name, const std::string& comment) override; - void delete_snapshot(const QDir& snapshot_dir, const std::string& name) override; - void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; - void load_snapshots(const QDir& snapshot_dir) override; + std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) override; + void delete_snapshot(const std::string& name) override; + void restore_snapshot(const std::string& name, VMSpecs& specs) override; + void load_snapshots() override; std::vector get_childrens_names(const Snapshot* parent) const override; protected: @@ -83,7 +83,7 @@ class BaseVirtualMachine : public VirtualMachine template void log_latest_snapshot(LockT lock) const; - void load_generic_snapshot_info(const QDir& snapshot_dir); + void load_generic_snapshot_info(); void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); @@ -93,7 +93,7 @@ class BaseVirtualMachine : public VirtualMachine auto make_head_file_rollback(const Path& head_path, QFile& head_file) const; void head_file_rollback_helper(const Path& head_path, QFile& head_file, const std::string& old_head, bool existed) const; - void persist_head_snapshot(const QDir& snapshot_dir) const; + void persist_head_snapshot() const; void persist_head_snapshot_name(const Path& head_path) const; std::string generate_snapshot_name() const; @@ -107,12 +107,12 @@ class BaseVirtualMachine : public VirtualMachine void deleted_head_rollback_helper(const Path& head_path, const bool& wrote_head, std::shared_ptr& old_head); - void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, + void update_parents(std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths); auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) const; - void delete_snapshot_helper(const QDir& snapshot_dir, std::shared_ptr& snapshot); + void delete_snapshot_helper(std::shared_ptr& snapshot); private: SnapshotMap snapshots; diff --git a/src/platform/backends/shared/base_virtual_machine_factory.cpp b/src/platform/backends/shared/base_virtual_machine_factory.cpp index 858b8e2293..b7241c2f8a 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.cpp +++ b/src/platform/backends/shared/base_virtual_machine_factory.cpp @@ -39,6 +39,10 @@ auto find_bridge_with(const NetworkContainer& networks, const std::string& membe } } // namespace +const mp::Path mp::BaseVirtualMachineFactory::instances_subdir = "vault/instances"; + +mp::BaseVirtualMachineFactory::BaseVirtualMachineFactory(const Path& instances_dir) : instances_dir{instances_dir} {}; + void mp::BaseVirtualMachineFactory::configure(VirtualMachineDescription& vm_desc) { auto instance_dir{mpu::base_dir(vm_desc.image.image_path)}; diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 0355b412e1..28377029da 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -33,18 +34,25 @@ constexpr auto log_category = "base factory"; class BaseVirtualMachineFactory : public VirtualMachineFactory { public: - BaseVirtualMachineFactory() = default; + explicit BaseVirtualMachineFactory(const Path& instances_dir); + + void remove_resources_for(const std::string& name) final; FetchType fetch_type() override { return FetchType::ImageOnly; }; - QString get_backend_directory_name() override + QString get_backend_directory_name() const override { return {}; }; + Path get_instance_directory(const std::string& name) const override + { + return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); + } + void prepare_networking(std::vector& /*extra_interfaces*/) override { // only certain backends need to do anything to prepare networking @@ -65,6 +73,9 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory throw NotImplementedOnThisBackendException("networks"); }; +protected: + static const Path instances_subdir; + protected: std::string create_bridge_with(const NetworkInterfaceInfo& interface) override { @@ -76,7 +87,19 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory virtual void prepare_interface(NetworkInterface& net, std::vector& host_nets, const std::string& bridge_type); + + virtual void remove_resources_for_impl(const std::string& name) = 0; + +private: + Path instances_dir; }; } // namespace multipass +inline void multipass::BaseVirtualMachineFactory::remove_resources_for(const std::string& name) +{ + remove_resources_for_impl(name); + QDir instance_dir{get_instance_directory(name)}; + instance_dir.removeRecursively(); +} + #endif // MULTIPASS_BASE_VIRTUAL_MACHINE_FACTORY_H diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 6351f92b92..56e73a26aa 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -674,3 +674,12 @@ void mp::utils::set_owner_for(mp::SSHSession& session, const std::string& root, fmt::format("sudo /bin/bash -c 'cd \"{}\" && chown -R {}:{} \"{}\"'", root, vm_user, vm_group, relative_target.substr(0, relative_target.find_first_of('/')))); } + +mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir, const mp::Path& backend_directory_name, + const mp::Path& instances_subdir) const +{ + if (backend_directory_name.isEmpty()) + return QDir(data_dir).filePath(instances_subdir); + else + return QDir(QDir(data_dir).filePath(backend_directory_name)).filePath(instances_subdir); +} diff --git a/tests/blueprint_test_lambdas.cpp b/tests/blueprint_test_lambdas.cpp index 0ea7bb8ede..91d2bb8a53 100644 --- a/tests/blueprint_test_lambdas.cpp +++ b/tests/blueprint_test_lambdas.cpp @@ -29,17 +29,19 @@ #include "common.h" #include "stub_virtual_machine.h" #include "stub_vm_image_vault.h" +#include "temp_dir.h" namespace mp = multipass; namespace mpt = multipass::test; std::function)> + const mp::ProgressMonitor&, const bool, const std::optional, const mp::Path&)> mpt::fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum) { return [&release, &remote, must_have_checksum]( const mp::FetchType& fetch_type, const mp::Query& query, const mp::VMImageVault::PrepareAction& prepare, - const mp::ProgressMonitor& monitor, const bool unlock, const std::optional& checksum) { + const mp::ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + const mp::Path& save_dir) { EXPECT_EQ(query.release, release); if (remote.empty()) { @@ -55,7 +57,7 @@ mpt::fetch_image_lambda(const std::string& release, const std::string& remote, c EXPECT_NE(checksum, std::nullopt); } - return mpt::StubVMImageVault().fetch_image(fetch_type, query, prepare, monitor, unlock, checksum); + return mpt::StubVMImageVault().fetch_image(fetch_type, query, prepare, monitor, unlock, checksum, save_dir); }; } diff --git a/tests/blueprint_test_lambdas.h b/tests/blueprint_test_lambdas.h index 345b38846b..8deb2952a9 100644 --- a/tests/blueprint_test_lambdas.h +++ b/tests/blueprint_test_lambdas.h @@ -39,7 +39,7 @@ namespace test { std::function)> + const bool, const std::optional, const multipass::Path&)> fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum = false); std::function diff --git a/tests/lxd/test_lxd_backend.cpp b/tests/lxd/test_lxd_backend.cpp index eeb5ad8303..cca1553ae0 100644 --- a/tests/lxd/test_lxd_backend.cpp +++ b/tests/lxd/test_lxd_backend.cpp @@ -80,6 +80,7 @@ struct LXDBackend : public Test mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); mpt::TempDir data_dir; + mpt::TempDir instance_dir; std::unique_ptr> mock_network_access_manager; QUrl base_url{"unix:///foo@1.0"}; const QString default_storage_pool{"default"}; @@ -444,8 +445,9 @@ TEST_F(LXDBackend, creates_in_stopped_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_TRUE(vm_created); EXPECT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -492,8 +494,9 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_start) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); machine.start(); @@ -542,8 +545,9 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_shutdown) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); machine.shutdown(); @@ -588,8 +592,8 @@ TEST_F(LXDBackend, machine_persists_internal_stopped_state_on_destruction) { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; ASSERT_EQ(machine.state, mp::VirtualMachine::State::running); } // Simulate multipass exiting by having the vm destruct @@ -645,8 +649,8 @@ TEST_F(LXDBackend, machine_does_not_update_state_in_dtor) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } EXPECT_TRUE(vm_shutdown); @@ -703,8 +707,8 @@ TEST_F(LXDBackend, machineLogsNotFoundExceptionInDtor) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.shutdown(); } @@ -753,8 +757,8 @@ TEST_F(LXDBackend, does_not_call_stop_when_snap_refresh_is_detected) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } EXPECT_FALSE(stop_requested); @@ -799,8 +803,8 @@ TEST_F(LXDBackend, calls_stop_when_snap_refresh_does_not_exist) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } EXPECT_TRUE(stop_requested); @@ -883,8 +887,9 @@ TEST_F(LXDBackend, posts_expected_data_when_creating_instance) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } TEST_F(LXDBackend, prepare_source_image_does_not_modify) @@ -953,10 +958,10 @@ TEST_F(LXDBackend, unimplemented_functions_logs_trace_message) const std::string name{"foo"}; - EXPECT_CALL( - *logger_scope.mock_logger, - log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), - mpt::MockLogger::make_cstring_matcher(StrEq(fmt::format("No resources to remove for \"{}\"", name))))); + EXPECT_CALL(*logger_scope.mock_logger, + log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), + mpt::MockLogger::make_cstring_matcher( + StrEq(fmt::format("No further resources to remove for \"{}\"", name))))); EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), @@ -1096,8 +1101,9 @@ TEST_P(LXDNetworkInfoSuite, returns_expected_network_info) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(), "10.217.27.168"); EXPECT_TRUE(machine.ipv6().empty()); @@ -1141,8 +1147,9 @@ TEST_F(LXDBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_THROW(machine.ssh_hostname(std::chrono::milliseconds(1)), std::runtime_error); EXPECT_EQ(machine.state, mp::VirtualMachine::State::unknown); @@ -1179,8 +1186,9 @@ TEST_F(LXDBackend, no_ip_address_returns_unknown) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(), "UNKNOWN"); } @@ -1522,8 +1530,9 @@ TEST_F(LXDBackend, unsupported_suspend_throws) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; MP_EXPECT_THROW_THAT(machine.suspend(), std::runtime_error, mpt::match_what(StrEq("suspend is currently not supported"))); @@ -1555,8 +1564,9 @@ TEST_F(LXDBackend, start_while_frozen_unfreezes) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::info), mpt::MockLogger::make_cstring_matcher(StrEq("pied-piper-valley")), @@ -1583,8 +1593,9 @@ TEST_F(LXDBackend, shutdown_while_stopped_does_nothing_and_logs_debug) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -1615,8 +1626,9 @@ TEST_F(LXDBackend, shutdown_while_frozen_does_nothing_and_logs_info) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::suspended); @@ -1666,8 +1678,9 @@ TEST_F(LXDBackend, ensure_vm_running_does_not_throw_starting) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1719,8 +1732,9 @@ TEST_F(LXDBackend, shutdown_while_starting_throws_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1772,8 +1786,9 @@ TEST_F(LXDBackend, start_failure_while_starting_throws_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1826,8 +1841,9 @@ TEST_F(LXDBackend, reboots_while_starting_does_not_throw_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1849,8 +1865,9 @@ TEST_F(LXDBackend, current_state_connection_error_logs_warning_and_sets_unknown_ throw mp::LocalSocketConnectionException(exception_message); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::warning), mpt::MockLogger::make_cstring_matcher(StrEq("pied-piper-valley")), @@ -1903,8 +1920,9 @@ TEST_P(LXDInstanceStatusTestSuite, lxd_state_returns_expected_VirtualMachine_sta } } - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_EQ(machine.current_state(), expected_state); } @@ -2135,8 +2153,9 @@ TEST_F(LXDBackend, posts_extra_network_devices) auto json_matcher = ResultOf(&extract_devices, devices_matcher); setup_vm_creation_expectations(*mock_network_access_manager, request_data_matcher(json_matcher)); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } TEST_F(LXDBackend, posts_network_data_config_if_available) @@ -2153,8 +2172,9 @@ TEST_F(LXDBackend, posts_network_data_config_if_available) setup_vm_creation_expectations(*mock_network_access_manager, request_data_matcher(json_matcher)); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } namespace diff --git a/tests/lxd/test_lxd_image_vault.cpp b/tests/lxd/test_lxd_image_vault.cpp index 79e5bd0b12..2934d31191 100644 --- a/tests/lxd/test_lxd_image_vault.cpp +++ b/tests/lxd/test_lxd_image_vault.cpp @@ -70,6 +70,7 @@ struct LXDImageVault : public Test mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; mpt::StubURLDownloader stub_url_downloader; mpt::TempDir cache_dir; + mpt::TempDir save_dir; }; } // namespace @@ -95,7 +96,7 @@ TEST_F(LXDImageVault, instance_exists_fetch_returns_expected_image_info) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -123,7 +124,7 @@ TEST_F(LXDImageVault, instance_exists_custom_image_returns_expected_image_info) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, "6937ddd3f4c3329182855843571fc91ae4fee24e8e0eb0f7cdcf2c22feed4dab"); EXPECT_EQ(image.original_release, "Snapcraft builder for Core 20"); @@ -152,7 +153,7 @@ TEST_F(LXDImageVault, instance_exists_uses_cached_release_title) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "Fake Title"); @@ -182,7 +183,7 @@ TEST_F(LXDImageVault, instance_exists_no_cached_release_title_info_for_fails) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, ""); @@ -212,7 +213,7 @@ TEST_F(LXDImageVault, returns_expected_info_with_valid_remote) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt)); + std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -240,11 +241,11 @@ TEST_F(LXDImageVault, throws_with_invalid_alias) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - MP_EXPECT_THROW_THAT( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), - std::runtime_error, - mpt::match_what( - StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", alias, "release")))); + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()), + std::runtime_error, + mpt::match_what(StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", + alias, "release")))); } TEST_F(LXDImageVault, throws_with_invalid_remote) @@ -259,9 +260,10 @@ TEST_F(LXDImageVault, throws_with_invalid_remote) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - MP_EXPECT_THROW_THAT( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), - std::runtime_error, mpt::match_what(HasSubstr(fmt::format("Remote \'{}\' is not found.", remote)))); + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()), + std::runtime_error, + mpt::match_what(HasSubstr(fmt::format("Remote \'{}\' is not found.", remote)))); } TEST_F(LXDImageVault, does_not_download_if_image_exists) @@ -289,7 +291,7 @@ TEST_F(LXDImageVault, does_not_download_if_image_exists) base_url, cache_dir.path(), mp::days{0}}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt)); + std::nullopt, save_dir.path())); } TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) @@ -326,7 +328,7 @@ TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, save_dir.path())); EXPECT_FALSE(download_requested); EXPECT_EQ(image.original_release, mpt::default_release_info); } @@ -353,7 +355,7 @@ TEST_F(LXDImageVault, requests_download_if_image_does_not_exist) base_url, cache_dir.path(), mp::days{0}}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt)); + std::nullopt, save_dir.path())); EXPECT_TRUE(download_requested); } @@ -380,8 +382,8 @@ TEST_F(LXDImageVault, sets_fingerprint_with_hash_query) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "e3b0c44298fc1c1", false, "release", mp::Query::Type::Alias}; - EXPECT_NO_THROW( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt)); + EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path())); } TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) @@ -419,9 +421,9 @@ TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_THROW( - image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, std::nullopt), - mp::AbortedDownloadException); + EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, + std::nullopt, save_dir.path()), + mp::AbortedDownloadException); EXPECT_TRUE(delete_requested); } @@ -457,9 +459,9 @@ TEST_F(LXDImageVault, percent_complete_returns_negative_on_metadata_download) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_THROW( - image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, std::nullopt), - mp::AbortedDownloadException); + EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, + std::nullopt, save_dir.path()), + mp::AbortedDownloadException); } TEST_F(LXDImageVault, delete_requested_on_instance_remove) @@ -832,8 +834,8 @@ TEST_F(LXDImageVault, custom_image_found_returns_expected_info) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "snapcraft", false, "release", mp::Query::Type::Alias}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, mpt::lxd_snapcraft_image_id); EXPECT_EQ(image.original_release, mpt::snapcraft_release_info); @@ -890,8 +892,8 @@ TEST_F(LXDImageVault, custom_image_downloads_and_creates_correct_upload) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "custom", false, "release", mp::Query::Type::Alias}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, mpt::lxd_custom_image_id); EXPECT_EQ(image.original_release, mpt::custom_release_info); @@ -916,7 +918,7 @@ TEST_F(LXDImageVault, fetch_image_unable_to_connect_logs_error_and_returns_blank StrEq(fmt::format("{} - returning blank image info", exception_message))))); auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt); + std::nullopt, save_dir.path()); EXPECT_TRUE(image.id.empty()); EXPECT_TRUE(image.original_release.empty()); @@ -1043,8 +1045,8 @@ TEST_F(LXDImageVault, http_based_image_downloads_and_creates_correct_upload) const std::string download_url{"http://www.foo.com/images/foo.img"}; const mp::Query query{"", download_url, false, "", mp::Query::Type::HttpDownload}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1109,8 +1111,8 @@ TEST_F(LXDImageVault, file_based_fetch_copies_image_and_returns_expected_info) auto current_time = QDateTime::currentDateTime(); const mp::Query query{"", file.url().toStdString(), false, "", mp::Query::Type::LocalFile}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1131,9 +1133,10 @@ TEST_F(LXDImageVault, invalid_local_file_image_throws) const std::string missing_file{"/foo"}; const mp::Query query{"", fmt::format("file://{}", missing_file), false, "", mp::Query::Type::LocalFile}; - MP_EXPECT_THROW_THAT( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), - std::runtime_error, mpt::match_what(StrEq(fmt::format("Custom image `{}` does not exist.", missing_file)))); + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, save_dir.path()), + std::runtime_error, + mpt::match_what(StrEq(fmt::format("Custom image `{}` does not exist.", missing_file)))); } TEST_F(LXDImageVault, updateImagesThrowsOnMissingImage) diff --git a/tests/lxd/test_lxd_mount_handler.cpp b/tests/lxd/test_lxd_mount_handler.cpp index 8bd3efc103..8c6608c79d 100644 --- a/tests/lxd/test_lxd_mount_handler.cpp +++ b/tests/lxd/test_lxd_mount_handler.cpp @@ -22,9 +22,11 @@ #include "tests/mock_virtual_machine.h" #include "tests/stub_ssh_key_provider.h" #include "tests/stub_status_monitor.h" +#include "tests/temp_dir.h" #include "src/platform/backends/lxd/lxd_mount_handler.h" +#include #include #include #include @@ -165,8 +167,9 @@ TEST_F(LXDMountHandlerTestFixture, stopThrowsIfVMIsRunning) TEST_P(LXDMountHandlerInvalidGidUidParameterTests, mountWithGidOrUid) { - mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, - bridge_name, default_storage_pool}; + mpt::TempDir instance_dir{}; + mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; const auto& [host_gid, instance_gid, host_uid, instance_uid] = GetParam(); const mp::VMMount vm_mount{ source_path, {{host_gid, instance_gid}}, {{host_uid, instance_uid}}, mp::VMMount::MountType::Native}; @@ -182,8 +185,9 @@ INSTANTIATE_TEST_SUITE_P(mountWithGidOrUidInstantiation, LXDMountHandlerInvalidG TEST_P(LXDMountHandlerValidGidUidParameterTests, mountWithGidOrUid) { - mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, - bridge_name, default_storage_pool}; + mpt::TempDir instance_dir{}; + mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; const auto& [host_gid, host_uid] = GetParam(); const int default_instance_id = -1; const mp::VMMount vm_mount{source_path, diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 2aa59ce4f9..9eddb810f9 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -19,6 +19,7 @@ #define MULTIPASS_MOCK_VIRTUAL_MACHINE_H #include "common.h" +#include "temp_dir.h" #include #include @@ -34,7 +35,13 @@ template - MockVirtualMachineT(Args&&... args) : T{std::forward(args)...} + MockVirtualMachineT(Args&&... args) : MockVirtualMachineT{std::make_unique(), std::forward(args)...} + { + } + + template + MockVirtualMachineT(std::unique_ptr&& tmp_dir, Args&&... args) + : T{std::forward(args)..., tmp_dir->path()} { ON_CALL(*this, current_state()).WillByDefault(Return(multipass::VirtualMachine::State::off)); ON_CALL(*this, ssh_port()).WillByDefault(Return(42)); @@ -71,11 +78,13 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (override)); MOCK_METHOD(std::shared_ptr, take_snapshot, - (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); - MOCK_METHOD(void, delete_snapshot, (const QDir& snapshot_dir, const std::string& name), (override)); - MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); - MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); + (const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); + MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); + MOCK_METHOD(void, load_snapshots, (), (override)); MOCK_METHOD(std::vector, get_childrens_names, (const Snapshot*), (const, override)); + + std::unique_ptr tmp_dir; }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/mock_virtual_machine_factory.h b/tests/mock_virtual_machine_factory.h index b178ea6741..37ad0d89d9 100644 --- a/tests/mock_virtual_machine_factory.h +++ b/tests/mock_virtual_machine_factory.h @@ -40,8 +40,9 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory MOCK_METHOD(VMImage, prepare_source_image, (const VMImage&), (override)); MOCK_METHOD(void, prepare_instance_image, (const VMImage&, const VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); - MOCK_METHOD(QString, get_backend_directory_name, (), (override)); - MOCK_METHOD(QString, get_backend_version_string, (), (override)); + MOCK_METHOD(QString, get_backend_directory_name, (), (const, override)); + MOCK_METHOD(QString, get_instance_directory, (const std::string&), (const, override)); + MOCK_METHOD(QString, get_backend_version_string, (), (const, override)); MOCK_METHOD(VMImageVault::UPtr, create_image_vault, (std::vector, URLDownloader*, const Path&, const Path&, const days&), (override)); MOCK_METHOD(void, configure, (VirtualMachineDescription&), (override)); diff --git a/tests/mock_vm_image_vault.h b/tests/mock_vm_image_vault.h index 1241a19b83..435319f3e0 100644 --- a/tests/mock_vm_image_vault.h +++ b/tests/mock_vm_image_vault.h @@ -35,8 +35,8 @@ class MockVMImageVault : public VMImageVault public: MockVMImageVault() { - ON_CALL(*this, fetch_image(_, _, _, _, _, _)) - .WillByDefault([this](auto, auto, const PrepareAction& prepare, auto, auto, auto) { + ON_CALL(*this, fetch_image(_, _, _, _, _, _, _)) + .WillByDefault([this](auto, auto, const PrepareAction& prepare, auto, auto, auto, auto) { return VMImage{dummy_image.name(), {}, {}, {}, {}, {}}; }); ON_CALL(*this, has_record_for(_)).WillByDefault(Return(true)); @@ -45,7 +45,7 @@ class MockVMImageVault : public VMImageVault MOCK_METHOD(VMImage, fetch_image, (const FetchType&, const Query&, const PrepareAction&, const ProgressMonitor&, const bool, - const std::optional&), + const std::optional&, const mp::Path&), (override)); MOCK_METHOD(void, remove, (const std::string&), (override)); MOCK_METHOD(bool, has_record_for, (const std::string&), (override)); diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 9ae08016b6..8a84e6b5fe 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -41,7 +41,7 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(void, platform_health_check, (), (override)); MOCK_METHOD(QStringList, vmstate_platform_args, (), (override)); MOCK_METHOD(QStringList, vm_platform_args, (const VirtualMachineDescription&), (override)); - MOCK_METHOD(QString, get_directory_name, (), (override)); + MOCK_METHOD(QString, get_directory_name, (), (const, override)); }; struct MockQemuPlatformFactory : public QemuPlatformFactory diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 8e49ef59af..9e4a754d99 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -80,6 +80,7 @@ struct QemuBackend : public mpt::TestWithMockedBinPath {}, {}}; mpt::TempDir data_dir; + mpt::TempDir instance_dir; const std::string tap_device{"tapfoo"}; const QString bridge_name{"dummy-bridge"}; const std::string subnet{"192.168.64"}; @@ -623,7 +624,7 @@ TEST_F(QemuBackend, ssh_hostname_returns_expected_value) return std::optional{expected_ip}; }); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -638,7 +639,7 @@ TEST_F(QemuBackend, gets_management_ip) EXPECT_CALL(mock_qemu_platform, get_ip_for(_)).WillOnce(Return(expected_ip)); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -652,7 +653,7 @@ TEST_F(QemuBackend, fails_to_get_management_ip_if_dnsmasq_does_not_return_an_ip) EXPECT_CALL(mock_qemu_platform, get_ip_for(_)).WillOnce(Return(std::nullopt)); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -666,7 +667,7 @@ TEST_F(QemuBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) ON_CALL(mock_qemu_platform, get_ip_for(_)).WillByDefault([](auto...) { return std::nullopt; }); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -732,11 +733,13 @@ TEST_F(QemuBackend, get_backend_directory_name_calls_qemu_platform) bool get_directory_name_called{false}; const QString backend_dir_name{"foo"}; - EXPECT_CALL(*mock_qemu_platform, get_directory_name()).WillOnce([&get_directory_name_called, &backend_dir_name] { - get_directory_name_called = true; + EXPECT_CALL(*mock_qemu_platform, get_directory_name()) + .Times(2) + .WillRepeatedly([&get_directory_name_called, &backend_dir_name] { + get_directory_name_called = true; - return backend_dir_name; - }); + return backend_dir_name; + }); EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { return std::move(mock_qemu_platform); diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index fd27a2b7d6..3d06a8a060 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -20,6 +20,8 @@ #include "stub_mount_handler.h" #include "stub_snapshot.h" +#include "temp_dir.h" + #include namespace multipass @@ -32,7 +34,12 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } - StubVirtualMachine(const std::string& name) : VirtualMachine{name} + StubVirtualMachine(const std::string& name) : StubVirtualMachine{name, std::make_unique()} + { + } + + StubVirtualMachine(const std::string& name, std::unique_ptr&& tmp_dir) + : VirtualMachine{name, tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} { } @@ -138,21 +145,20 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - std::shared_ptr take_snapshot(const QDir&, const VMSpecs&, const std::string&, - const std::string&) override + std::shared_ptr take_snapshot(const VMSpecs&, const std::string&, const std::string&) override { return {}; } - void delete_snapshot(const QDir& snapshot_dir, const std::string&) override + void delete_snapshot(const std::string&) override { } - void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override + void restore_snapshot(const std::string& name, VMSpecs& specs) override { } - void load_snapshots(const QDir&) override + void load_snapshots() override { } @@ -162,6 +168,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine } StubSnapshot snapshot; + std::unique_ptr tmp_dir; }; } // namespace test } // namespace multipass diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index 89a42e6e73..93305b094d 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -20,6 +20,7 @@ #include "stub_virtual_machine.h" #include "stub_vm_image_vault.h" +#include "temp_dir.h" #include @@ -29,13 +30,22 @@ namespace test { struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory { + StubVirtualMachineFactory() : StubVirtualMachineFactory{std::make_unique()} + { + } + + StubVirtualMachineFactory(std::unique_ptr&& tmp_dir) + : mp::BaseVirtualMachineFactory{tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + { + } + multipass::VirtualMachine::UPtr create_virtual_machine(const multipass::VirtualMachineDescription&, multipass::VMStatusMonitor&) override { return std::make_unique(); } - void remove_resources_for(const std::string& name) override + void remove_resources_for_impl(const std::string& name) override { } @@ -58,12 +68,17 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory { } - QString get_backend_directory_name() override + QString get_backend_directory_name() const override { return {}; } - QString get_backend_version_string() override + QString get_instance_directory(const std::string& name) const override + { + return tmp_dir->path(); + } + + QString get_backend_version_string() const override { return "stub-5678"; } @@ -74,6 +89,8 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory { return std::make_unique(); } + + std::unique_ptr tmp_dir; }; } } diff --git a/tests/stub_vm_image_vault.h b/tests/stub_vm_image_vault.h index c8a7687ea2..535390510a 100644 --- a/tests/stub_vm_image_vault.h +++ b/tests/stub_vm_image_vault.h @@ -29,8 +29,8 @@ namespace test struct StubVMImageVault final : public multipass::VMImageVault { multipass::VMImage fetch_image(const multipass::FetchType&, const multipass::Query&, const PrepareAction& prepare, - const multipass::ProgressMonitor&, const bool, - const std::optional&) override + const multipass::ProgressMonitor&, const bool, const std::optional&, + const multipass::Path&) override { return prepare({dummy_image.name(), {}, {}, {}, {}, {}}); }; diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index a6dab1b360..f05e48dd7a 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -579,11 +579,14 @@ TEST_P(DaemonAliasTestsuite, purge_removes_purged_instance_aliases_and_scripts) auto mock_image_vault = std::make_unique>(); EXPECT_CALL(*mock_image_vault, remove(_)).WillRepeatedly(Return()); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillRepeatedly(Return(mp::VMImage{})); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillRepeatedly(Return(mp::VMImage{})); EXPECT_CALL(*mock_image_vault, prune_expired_images()).WillRepeatedly(Return()); EXPECT_CALL(*mock_image_vault, has_record_for(_)).WillRepeatedly(Return(true)); config_builder.vault = std::move(mock_image_vault); + auto mock_factory = use_a_mock_vm_factory(); + + EXPECT_CALL(*mock_factory, remove_resources_for(_)).WillRepeatedly(Return()); std::string json_contents = make_instance_json(std::nullopt, {}, {"primary"}); diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index e5348f9c70..7f37bfd993 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -18,6 +18,7 @@ #include "common.h" #include "dummy_ssh_key_provider.h" #include "mock_ssh_test_fixture.h" +#include "temp_dir.h" #include @@ -33,12 +34,17 @@ namespace { struct StubBaseVirtualMachine : public mp::BaseVirtualMachine { - StubBaseVirtualMachine(const mp::VirtualMachine::State s = mp::VirtualMachine::State::off) - : mp::BaseVirtualMachine("stub") + StubBaseVirtualMachine(mp::VirtualMachine::State s = mp::VirtualMachine::State::off) + : StubBaseVirtualMachine{s, std::make_unique()} { state = s; } + StubBaseVirtualMachine(mp::VirtualMachine::State s, std::unique_ptr&& tmp_dir) + : mp::BaseVirtualMachine{"stub", tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + { + } + void stop() override { state = mp::VirtualMachine::State::off; @@ -125,6 +131,8 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine { return nullptr; } + + std::unique_ptr&& tmp_dir; }; struct BaseVM : public Test diff --git a/tests/test_base_virtual_machine_factory.cpp b/tests/test_base_virtual_machine_factory.cpp index c0a2fe5398..25a9c624a5 100644 --- a/tests/test_base_virtual_machine_factory.cpp +++ b/tests/test_base_virtual_machine_factory.cpp @@ -37,13 +37,21 @@ namespace { struct MockBaseFactory : mp::BaseVirtualMachineFactory { + MockBaseFactory() : MockBaseFactory{std::make_unique()} + { + } + + MockBaseFactory(std::unique_ptr&& tmp_dir) + : mp::BaseVirtualMachineFactory{tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + { + } + MOCK_METHOD(mp::VirtualMachine::UPtr, create_virtual_machine, (const mp::VirtualMachineDescription&, mp::VMStatusMonitor&), (override)); - MOCK_METHOD(void, remove_resources_for, (const std::string&), (override)); MOCK_METHOD(mp::VMImage, prepare_source_image, (const mp::VMImage&), (override)); MOCK_METHOD(void, prepare_instance_image, (const mp::VMImage&, const mp::VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); - MOCK_METHOD(QString, get_backend_version_string, (), (override)); + MOCK_METHOD(QString, get_backend_version_string, (), (const, override)); MOCK_METHOD(void, prepare_networking, (std::vector&), (override)); MOCK_METHOD(std::vector, networks, (), (const, override)); MOCK_METHOD(std::string, create_bridge_with, (const mp::NetworkInterfaceInfo&), (override)); @@ -51,6 +59,7 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory (mp::NetworkInterface & net, std::vector& host_nets, const std::string& bridge_type), (override)); + MOCK_METHOD(void, remove_resources_for_impl, (const std::string&), (override)); std::string base_create_bridge_with(const mp::NetworkInterfaceInfo& interface) { @@ -68,6 +77,8 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory { return mp::BaseVirtualMachineFactory::prepare_interface(net, host_nets, bridge_type); // protected } + + std::unique_ptr tmp_dir; }; struct BaseFactory : public Test diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 852d79b594..2fed9fd1cc 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -663,7 +663,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundPassesExpectedAliases) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -702,7 +702,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundMountsWorkspace) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -736,7 +736,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundMountsWorkspaceConfined) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -774,7 +774,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundMountsWorkspaceInExisting EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -817,7 +817,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotMountUnwrittableWo EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -861,7 +861,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundButCannotMount) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -909,7 +909,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundPassesExpectedAliasesWith EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, command_line_name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -956,7 +956,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotOverwriteAliases) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -1005,7 +1005,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotOverwriteAliasesIf EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -1046,7 +1046,7 @@ TEST_P(DaemonCreateLaunchTestSuite, blueprint_found_passes_expected_data) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce(mpt::fetch_blueprint_for_lambda(num_cores, mem_size, disk_space, release, remote)); @@ -1074,7 +1074,7 @@ TEST_P(DaemonCreateLaunchTestSuite, blueprint_not_found_passes_expected_data) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, empty)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, empty)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, empty)); config_builder.vault = std::move(mock_image_vault); mp::Daemon daemon{config_builder.build()}; @@ -1329,7 +1329,7 @@ TEST_F(DaemonCreateLaunchTestSuite, blueprintFromFileCallsCorrectFunction) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)).Times(1); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).Times(1); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).Times(1); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)).Times(0); @@ -1679,9 +1679,9 @@ TEST_F(Daemon, ctor_drops_removed_instances) config_builder.data_directory = temp_dir->path(); auto mock_image_vault = std::make_unique>(); - EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, stayed), _, _, _, _)) + EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, stayed), _, _, _, _, _)) .WillRepeatedly(DoDefault()); // returns an image that can be verified to exist for this instance - EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, gone), _, _, _, _)) + EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, gone), _, _, _, _, _)) .WillOnce(Return(mp::VMImage{"/path/to/nowhere", "", "", "", "", {}})); // an image that can't be verified to // exist for this instance config_builder.vault = std::move(mock_image_vault); diff --git a/tests/test_daemon_launch.cpp b/tests/test_daemon_launch.cpp index 8db790c38e..5b3740e4fe 100644 --- a/tests/test_daemon_launch.cpp +++ b/tests/test_daemon_launch.cpp @@ -72,7 +72,7 @@ TEST_F(TestDaemonLaunch, blueprintFoundMountsWorkspaceWithNameOverride) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, command_line_name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -127,7 +127,7 @@ TEST_F(TestDaemonLaunch, v2BlueprintFoundPropagatesSha) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, command_line_name)); // The expectation of this test is set in fetch_image_lambda(). - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)) + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)) .WillOnce(mpt::fetch_image_lambda(release, remote, true)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index 26a417e77d..3b81584172 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -168,7 +168,9 @@ struct ImageVault : public testing::Test [](const mp::VMImage& source_image) -> mp::VMImage { return source_image; }}; mpt::TempDir cache_dir; mpt::TempDir data_dir; + mpt::TempDir save_dir; std::string instance_name{"valley-pied-piper"}; + QString instance_dir = save_dir.filePath(QString::fromStdString(instance_name)); mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; }; } // namespace @@ -176,8 +178,8 @@ struct ImageVault : public testing::Test TEST_F(ImageVault, downloads_image) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(host.image.url())); @@ -186,8 +188,8 @@ TEST_F(ImageVault, downloads_image) TEST_F(ImageVault, returned_image_contains_instance_name) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_TRUE(vm_image.image_path.contains(QString::fromStdString(instance_name))); } @@ -201,8 +203,8 @@ TEST_F(ImageVault, calls_prepare) prepare_called = true; return source_image; }; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_TRUE(prepare_called); } @@ -215,10 +217,10 @@ TEST_F(ImageVault, records_instanced_images) ++prepare_called_count; return source_image; }; - auto vm_image1 = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); - auto vm_image2 = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); + auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -234,13 +236,13 @@ TEST_F(ImageVault, caches_prepared_images) ++prepare_called_count; return source_image; }; - auto vm_image1 = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; - auto vm_image2 = - vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, + std::nullopt, save_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -258,12 +260,12 @@ TEST_F(ImageVault, remembers_instance_images) }; mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image1 = - first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image2 = - another_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -279,14 +281,15 @@ TEST_F(ImageVault, remembers_prepared_images) }; mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image1 = - first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image2 = - another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt); + another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt, + save_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -307,8 +310,8 @@ TEST_F(ImageVault, uses_image_from_prepare) }; mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); const auto image_data = mp::utils::contents_of(vm_image.image_path); EXPECT_THAT(image_data, StrEq(expected_data)); @@ -326,8 +329,8 @@ TEST_F(ImageVault, image_purged_expired) mpt::make_file_with_content(file_name); return {file_name, source_image.id, "", "", "", {}}; }; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -347,8 +350,8 @@ TEST_F(ImageVault, image_exists_not_expired) mpt::make_file_with_content(file_name); return {file_name, source_image.id, "", "", "", {}}; }; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, instance_dir); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -383,7 +386,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_fetch_copies_image_an query.release = file.url().toStdString(); query.query_type = mp::Query::Type::LocalFile; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir); EXPECT_TRUE(QFileInfo::exists(vm_image.image_path)); EXPECT_EQ(vm_image.id, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); @@ -397,7 +401,8 @@ TEST_F(ImageVault, invalid_custom_image_file_throws) query.release = "file://foo"; query.query_type = mp::Query::Type::LocalFile; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir), std::runtime_error); } @@ -409,7 +414,7 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(custom_image_url_downloads)) query.release = "http://www.foo.com/fake.img"; query.query_type = mp::Query::Type::HttpDownload; - vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(QString::fromStdString(query.release))); @@ -419,18 +424,18 @@ TEST_F(ImageVault, missing_downloaded_image_throws) { mpt::StubURLDownloader stub_url_downloader; mp::DefaultVMImageVault vault{hosts, &stub_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::CreateImageException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir), + mp::CreateImageException); } TEST_F(ImageVault, hash_mismatch_throws) { BadURLDownloader bad_url_downloader; mp::DefaultVMImageVault vault{hosts, &bad_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::CreateImageException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir), + mp::CreateImageException); } TEST_F(ImageVault, invalid_remote_throws) @@ -441,7 +446,8 @@ TEST_F(ImageVault, invalid_remote_throws) query.remote_name = "foo"; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir), std::runtime_error); } @@ -453,7 +459,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(invalid_image_alias_throw)) query.release = "foo"; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir), mp::CreateImageException); } @@ -466,8 +473,8 @@ TEST_F(ImageVault, valid_remote_and_alias_returns_valid_image_info) query.remote_name = "release"; mp::VMImage image; - EXPECT_NO_THROW( - image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt)); + EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir)); EXPECT_THAT(image.original_release, Eq("18.04 LTS")); EXPECT_THAT(image.id, Eq("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); @@ -482,8 +489,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i mp::Query query{instance_name, image_url, false, "", mp::Query::Type::HttpDownload}; mp::VMImage image; - EXPECT_NO_THROW( - image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt)); + EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir)); // Hash is based on image url EXPECT_THAT(image.id, Eq("7404f51c9b4f40312fa048a0ad36e07b74b718a2d3a5a08e8cca906c69059ddf")); @@ -493,7 +500,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i TEST_F(ImageVault, image_update_creates_new_dir_and_removes_old) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir); auto original_file{url_downloader.downloaded_files[0]}; auto original_absolute_path{QFileInfo(original_file).absolutePath()}; @@ -524,9 +532,9 @@ TEST_F(ImageVault, aborted_download_throws) running_url_downloader.abort_all_downloads(); - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::AbortedDownloadException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir), + mp::AbortedDownloadException); } TEST_F(ImageVault, minimum_image_size_returns_expected_size) @@ -537,8 +545,8 @@ TEST_F(ImageVault, minimum_image_size_returns_expected_size) auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -559,7 +567,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_minimum_size_returns_ query.release = file.url().toStdString(); query.query_type = mp::Query::Type::LocalFile; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -582,8 +591,8 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_crashes) auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("with output")))); @@ -596,8 +605,8 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_cannot_find_the_i auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("Could not find")))); @@ -610,8 +619,8 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_does_not_understa auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(HasSubstr("Could not obtain image's virtual size"))); @@ -682,7 +691,8 @@ TEST_F(ImageVault, updateImagesLogsWarningOnUnsupportedImage) { mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir); EXPECT_CALL(host, info_for(_)).WillOnce(Throw(mp::UnsupportedImageException(default_query.release))); @@ -699,7 +709,8 @@ TEST_F(ImageVault, updateImagesLogsWarningOnEmptyVault) { mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir); EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); @@ -719,9 +730,9 @@ TEST_F(ImageVault, fetchLocalImageThrowsOnEmptyVault) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::ImageNotFoundException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, instance_dir), + mp::ImageNotFoundException); } TEST_F(ImageVault, fetchRemoteImageThrowsOnMissingKernel) @@ -731,6 +742,7 @@ TEST_F(ImageVault, fetchRemoteImageThrowsOnMissingKernel) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + instance_dir), mp::ImageNotFoundException); }