From cad30f2f8cef2d180151e68a4f1b4b3e48d1494d Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 23 Jan 2025 17:09:49 +0100 Subject: [PATCH 01/21] [az] make vms and vm factories aware of zones --- include/multipass/virtual_machine.h | 17 +++++--- .../multipass/virtual_machine_description.h | 1 + include/multipass/virtual_machine_factory.h | 7 +++- include/multipass/vm_specs.h | 40 +++++++++---------- .../libvirt/libvirt_virtual_machine.cpp | 4 +- .../libvirt/libvirt_virtual_machine.h | 1 + .../libvirt_virtual_machine_factory.cpp | 4 +- .../libvirt/libvirt_virtual_machine_factory.h | 3 +- .../backends/lxd/lxd_virtual_machine.cpp | 3 +- .../backends/lxd/lxd_virtual_machine.h | 1 + .../lxd/lxd_virtual_machine_factory.cpp | 4 +- .../lxd/lxd_virtual_machine_factory.h | 3 +- .../backends/qemu/qemu_virtual_machine.cpp | 2 + .../backends/qemu/qemu_virtual_machine.h | 8 +++- .../qemu/qemu_virtual_machine_factory.cpp | 8 +++- .../qemu/qemu_virtual_machine_factory.h | 6 ++- .../backends/shared/base_virtual_machine.cpp | 14 ++++++- .../backends/shared/base_virtual_machine.h | 12 +++++- .../shared/base_virtual_machine_factory.cpp | 8 ++-- .../shared/base_virtual_machine_factory.h | 9 +++-- 20 files changed, 105 insertions(+), 50 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 57e5b302b1..2f65b69e66 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -36,6 +36,7 @@ namespace multipass { +class AvailabilityZone; class MemorySize; class VMMount; struct VMSpecs; @@ -55,7 +56,8 @@ class VirtualMachine : private DisabledCopyMove delayed_shutdown, suspending, suspended, - unknown + unknown, + unavailable, }; enum class ShutdownPolicy @@ -73,6 +75,7 @@ class VirtualMachine : private DisabledCopyMove virtual void start() = 0; virtual void shutdown(ShutdownPolicy shutdown_policy = ShutdownPolicy::Powerdown) = 0; virtual void suspend() = 0; + virtual void make_available(bool available) = 0; virtual State current_state() = 0; virtual int ssh_port() = 0; virtual std::string ssh_hostname() @@ -129,14 +132,18 @@ class VirtualMachine : private DisabledCopyMove std::mutex state_mutex; std::optional management_ip; bool shutdown_while_starting{false}; + AvailabilityZone& zone; protected: 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){}; + VirtualMachine(VirtualMachine::State state, + const std::string& vm_name, + AvailabilityZone& zone, + const Path& instance_dir) + : state{state}, vm_name{vm_name}, zone{zone}, instance_dir{QDir{instance_dir}} {}; + VirtualMachine(const std::string& vm_name, AvailabilityZone& zone, const Path& instance_dir) + : VirtualMachine(State::off, vm_name, zone, instance_dir){}; }; } // namespace multipass diff --git a/include/multipass/virtual_machine_description.h b/include/multipass/virtual_machine_description.h index 37c3624ab5..c6d9ed449c 100644 --- a/include/multipass/virtual_machine_description.h +++ b/include/multipass/virtual_machine_description.h @@ -40,6 +40,7 @@ class VirtualMachineDescription MemorySize mem_size; MemorySize disk_space; std::string vm_name; + std::string zone; std::string default_mac_address; std::vector extra_interfaces; std::string ssh_username; diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 80fc916676..7299f627ab 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -18,6 +18,7 @@ #ifndef MULTIPASS_VIRTUAL_MACHINE_FACTORY_H #define MULTIPASS_VIRTUAL_MACHINE_FACTORY_H +#include "availability_zone_manager.h" #include "days.h" #include "disabled_copy_move.h" #include "fetch_type.h" @@ -48,14 +49,16 @@ class VirtualMachineFactory : private DisabledCopyMove virtual ~VirtualMachineFactory() = default; virtual VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) = 0; + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) = 0; virtual VirtualMachine::UPtr clone_bare_vm(const VMSpecs& src_spec, const VMSpecs& dest_spec, const std::string& src_name, const std::string& dest_name, const VMImage& dest_image, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) = 0; + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) = 0; /** Removes any resources associated with a VM of the given name. * diff --git a/include/multipass/vm_specs.h b/include/multipass/vm_specs.h index 155715a39a..ba4483390f 100644 --- a/include/multipass/vm_specs.h +++ b/include/multipass/vm_specs.h @@ -45,31 +45,29 @@ struct VMSpecs bool deleted; QJsonObject metadata; int clone_count = 0; // tracks the number of cloned vm from this source vm (regardless of deletes) + std::string zone; }; inline bool operator==(const VMSpecs& a, const VMSpecs& b) { - return std::tie(a.num_cores, - a.mem_size, - a.disk_space, - a.default_mac_address, - a.extra_interfaces, - a.ssh_username, - a.state, - a.mounts, - a.deleted, - a.metadata, - a.clone_count) == std::tie(b.num_cores, - b.mem_size, - b.disk_space, - b.default_mac_address, - b.extra_interfaces, - b.ssh_username, - b.state, - b.mounts, - b.deleted, - b.metadata, - a.clone_count); + const auto properties_of = [](const VMSpecs& spec) { + return std::tuple{ + spec.num_cores, + spec.mem_size, + spec.disk_space, + spec.default_mac_address, + spec.extra_interfaces, + spec.ssh_username, + spec.state, + spec.mounts, + spec.deleted, + spec.metadata, + spec.clone_count, + spec.zone, + }; + }; + + return properties_of(a) == properties_of(b); } inline bool operator!=(const VMSpecs& a, const VMSpecs& b) // TODO drop in C++20 diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index ba36a15773..eb1dfa3aa2 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -17,6 +17,7 @@ #include "libvirt_virtual_machine.h" +#include #include #include #include @@ -277,8 +278,9 @@ mp::LibVirtVirtualMachine::LibVirtVirtualMachine(const VirtualMachineDescription VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir) - : BaseVirtualMachine{desc.vm_name, key_provider, instance_dir}, + : BaseVirtualMachine{desc.vm_name, key_provider, zone, 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 c352de9b39..e17d817a41 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -40,6 +40,7 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir); ~LibVirtVirtualMachine(); diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 903eaaf76d..793d1e242b 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -124,7 +124,8 @@ mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& d mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) { if (bridge_name.empty()) bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); @@ -134,6 +135,7 @@ mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machin monitor, libvirt_wrapper, key_provider, + az_manager.get_zone(desc.zone), get_instance_directory(desc.vm_name)); } diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h index 9538044de3..1e57386c6d 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h @@ -37,7 +37,8 @@ class LibVirtVirtualMachineFactory final : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) override; + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) 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; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 866feb7d6d..62dd598fe0 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -177,8 +177,9 @@ mp::LXDVirtualMachine::LXDVirtualMachine(const VirtualMachineDescription& desc, const QString& bridge_name, const QString& storage_pool, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const mp::Path& instance_dir) - : BaseVirtualMachine{desc.vm_name, key_provider, instance_dir}, + : BaseVirtualMachine{desc.vm_name, key_provider, zone, 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 b928d4d94d..f0e936e39d 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -39,6 +39,7 @@ class LXDVirtualMachine : public BaseVirtualMachine const QString& bridge_name, const QString& storage_pool, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir); ~LXDVirtualMachine() override; diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 5cfe2d8d23..c46aaf5d1e 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -92,7 +92,8 @@ mp::LXDVirtualMachineFactory::LXDVirtualMachineFactory(const mp::Path& data_dir, mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) { return std::make_unique(desc, monitor, @@ -101,6 +102,7 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co multipass_bridge_name, storage_pool, key_provider, + az_manager.get_zone(desc.zone), MP_UTILS.make_dir(get_instance_directory(desc.vm_name))); } diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.h b/src/platform/backends/lxd/lxd_virtual_machine_factory.h index 16a2e7c58a..9901a7e07e 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.h +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.h @@ -36,7 +36,8 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) override; + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) 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; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index bfe1a70e90..182cf39261 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -244,12 +244,14 @@ mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc QemuPlatform* qemu_platform, VMStatusMonitor& monitor, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir, bool remove_snapshots) : BaseVirtualMachine{mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag) ? State::suspended : State::off, desc.vm_name, key_provider, + zone, instance_dir}, desc{desc}, qemu_platform{qemu_platform}, diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 7b0d73d28c..5213000212 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -46,6 +46,7 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine QemuPlatform* qemu_platform, VMStatusMonitor& monitor, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir, bool remove_snapshots = false); ~QemuVirtualMachine(); @@ -77,8 +78,11 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine protected: // TODO remove this, the onus of composing a VM of stubs should be on the stub VMs - QemuVirtualMachine(const std::string& name, const SSHKeyProvider& key_provider, const Path& instance_dir) - : BaseVirtualMachine{name, key_provider, instance_dir} + QemuVirtualMachine(const std::string& name, + const SSHKeyProvider& key_provider, + AvailabilityZone& zone, + const Path& instance_dir) + : BaseVirtualMachine{name, key_provider, zone, 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 0817e6a783..58a0cc5813 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -50,12 +50,14 @@ mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(QemuPlatform::UPtr qemu mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) { return std::make_unique(desc, qemu_platform.get(), monitor, key_provider, + az_manager.get_zone(desc.zone), get_instance_directory(desc.vm_name)); } @@ -161,12 +163,14 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::clone_vm_impl(const std: const multipass::VMSpecs& /*src_vm_specs*/, const VirtualMachineDescription& desc, VMStatusMonitor& monitor, - const SSHKeyProvider& key_provider) + const SSHKeyProvider& key_provider, + AvailabilityZoneManager& az_manager) { return std::make_unique(desc, qemu_platform.get(), monitor, key_provider, + az_manager.get_zone(desc.zone), get_instance_directory(desc.vm_name), true); } diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index ff779dfd3a..911daac712 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -35,7 +35,8 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) override; + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) 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; @@ -56,7 +57,8 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory const multipass::VMSpecs& src_vm_specs, const VirtualMachineDescription& desc, VMStatusMonitor& monitor, - const SSHKeyProvider& key_provider) override; + const SSHKeyProvider& key_provider, + AvailabilityZoneManager& az_manager) override; QemuPlatform::UPtr qemu_platform; }; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index db5c3f3832..787f4cd02f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -17,6 +17,7 @@ #include "base_virtual_machine.h" +#include #include #include #include @@ -143,16 +144,25 @@ std::optional wait_until_ssh_up_helper(mp::VirtualMachine* virtu mp::BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir) - : VirtualMachine{state, vm_name, instance_dir}, key_provider{key_provider} + : VirtualMachine{state, vm_name, zone, instance_dir}, key_provider{key_provider} { + zone.add_vm(*this); } mp::BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir) - : VirtualMachine{vm_name, instance_dir}, key_provider{key_provider} + : VirtualMachine{vm_name, zone, instance_dir}, key_provider{key_provider} { + zone.add_vm(*this); +} + +mp::BaseVirtualMachine::~BaseVirtualMachine() +{ + zone.remove_vm(*this); } void mp::BaseVirtualMachine::apply_extra_interfaces_and_instance_id_to_cloud_init( diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 2ee0f9b709..7067e6f993 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -45,11 +45,21 @@ class BaseVirtualMachine : public VirtualMachine BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, const SSHKeyProvider& key_provider, + AvailabilityZone& zone, const Path& instance_dir); - BaseVirtualMachine(const std::string& vm_name, const SSHKeyProvider& key_provider, const Path& instance_dir); + BaseVirtualMachine(const std::string& vm_name, + const SSHKeyProvider& key_provider, + AvailabilityZone& zone, + const Path& instance_dir); + ~BaseVirtualMachine(); virtual std::string ssh_exec(const std::string& cmd, bool whisper = false) override; + void make_available(bool available) override + { + throw NotImplementedOnThisBackendException("availability zones"); + } + void wait_until_ssh_up(std::chrono::milliseconds timeout) override; void wait_for_cloud_init(std::chrono::milliseconds timeout) override; diff --git a/src/platform/backends/shared/base_virtual_machine_factory.cpp b/src/platform/backends/shared/base_virtual_machine_factory.cpp index 480fcce202..4cb748380c 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.cpp +++ b/src/platform/backends/shared/base_virtual_machine_factory.cpp @@ -90,7 +90,8 @@ mp::VirtualMachine::UPtr mp::BaseVirtualMachineFactory::clone_bare_vm(const VMSp const std::string& dest_name, const VMImage& dest_image, const multipass::SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) { const std::filesystem::path src_instance_dir{get_instance_directory(src_name).toStdString()}; const std::filesystem::path dest_instance_dir{get_instance_directory(dest_name).toStdString()}; @@ -109,6 +110,7 @@ mp::VirtualMachine::UPtr mp::BaseVirtualMachineFactory::clone_bare_vm(const VMSp dest_spec.mem_size, dest_spec.disk_space, dest_name, + dest_spec.zone, dest_spec.default_mac_address, dest_spec.extra_interfaces, dest_spec.ssh_username, @@ -119,9 +121,7 @@ mp::VirtualMachine::UPtr mp::BaseVirtualMachineFactory::clone_bare_vm(const VMSp {}, {}}; - mp::VirtualMachine::UPtr cloned_instance = clone_vm_impl(src_name, src_spec, dest_vm_desc, monitor, key_provider); - - return cloned_instance; + return clone_vm_impl(src_name, src_spec, dest_vm_desc, monitor, key_provider, az_manager); } void mp::BaseVirtualMachineFactory::copy_instance_dir_with_essential_files(const fs::path& source_instance_dir_path, diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 880c2abf0d..d2e4442594 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -41,7 +41,8 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory const std::string& dest_name, const VMImage& dest_image, const SSHKeyProvider& key_provider, - VMStatusMonitor& monitor) override final; + VMStatusMonitor& monitor, + AvailabilityZoneManager& az_manager) override final; void remove_resources_for(const std::string& name) final; @@ -99,7 +100,8 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory const multipass::VMSpecs& src_vm_specs, const VirtualMachineDescription& desc, VMStatusMonitor& monitor, - const SSHKeyProvider& key_provider); + const SSHKeyProvider& key_provider, + AvailabilityZoneManager& az_manager); static void copy_instance_dir_with_essential_files(const fs::path& source_instance_dir_path, const fs::path& dest_instance_dir_path); @@ -134,7 +136,8 @@ inline multipass::VirtualMachine::UPtr multipass::BaseVirtualMachineFactory::clo const VMSpecs& src_vm_specs, const VirtualMachineDescription& desc, VMStatusMonitor& monitor, - const SSHKeyProvider& key_provider) + const SSHKeyProvider& key_provider, + AvailabilityZoneManager& az_manager) { throw NotImplementedOnThisBackendException{"clone"}; } From a6156f6ad97a6b56f0e0f6fa58d3620b0b8146d4 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 23 Jan 2025 17:10:57 +0100 Subject: [PATCH 02/21] [az] add az implementation --- include/multipass/base_availability_zone.h | 61 ++++++ src/platform/backends/shared/CMakeLists.txt | 1 + .../shared/base_availability_zone.cpp | 184 ++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 include/multipass/base_availability_zone.h create mode 100644 src/platform/backends/shared/base_availability_zone.cpp diff --git a/include/multipass/base_availability_zone.h b/include/multipass/base_availability_zone.h new file mode 100644 index 0000000000..e7e8b8b5c0 --- /dev/null +++ b/include/multipass/base_availability_zone.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MULTIPASS_BASE_AVAILABILITY_ZONE_H +#define MULTIPASS_BASE_AVAILABILITY_ZONE_H + +#include "availability_zone.h" + +#include +#include +#include +#include + +namespace multipass +{ +class BaseAvailabilityZone final : public AvailabilityZone +{ +public: + BaseAvailabilityZone(const std::string& name, const std::filesystem::path& az_directory); + + const std::string& get_name() const override; + const std::string& get_subnet() const override; + bool is_available() const override; + void set_available(bool new_available) override; + void add_vm(VirtualMachine& vm) override; + void remove_vm(VirtualMachine& vm) override; + +private: + void serialize() const; + + // we store all the data in one struct so that it can be created from one function call in the initializer list + struct data + { + const std::string name{}; + const std::filesystem::path file_path{}; + const std::string subnet{}; + + mutable std::recursive_mutex mutex{}; + bool available{}; + std::vector> vms{}; + } m; + + static data make(const std::string& name, const std::filesystem::path& az_directory); +}; +} // namespace multipass + +#endif // MULTIPASS_BASE_AVAILABILITY_ZONE_H diff --git a/src/platform/backends/shared/CMakeLists.txt b/src/platform/backends/shared/CMakeLists.txt index 8981b6c04f..5b6a754259 100644 --- a/src/platform/backends/shared/CMakeLists.txt +++ b/src/platform/backends/shared/CMakeLists.txt @@ -13,6 +13,7 @@ # along with this program. If not, see . add_library(shared STATIC + base_availability_zone.cpp base_snapshot.cpp base_virtual_machine.cpp base_virtual_machine_factory.cpp diff --git a/src/platform/backends/shared/base_availability_zone.cpp b/src/platform/backends/shared/base_availability_zone.cpp new file mode 100644 index 0000000000..12d59bc650 --- /dev/null +++ b/src/platform/backends/shared/base_availability_zone.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "multipass/base_availability_zone.h" +#include "multipass/exceptions/availability_zone_exceptions.h" +#include "multipass/file_ops.h" +#include "multipass/logging/log.h" + +#include + +#include + +namespace multipass +{ + +namespace mpl = logging; + +constexpr auto subnet_key = "subnet"; +constexpr auto available_key = "available"; + +BaseAvailabilityZone::data BaseAvailabilityZone::make(const std::string& name, const fs::path& az_directory) +{ + const auto file_path{az_directory / (name + ".json")}; + + mpl::log(mpl::Level::info, name, "creating zone"); + + std::error_code err; + const auto file_type = MP_FILEOPS.status(file_path, err).type(); + if (err && file_type != fs::file_type::not_found) + { + throw AvailabilityZoneDeserializationError{ + "AZ file {:?} is not accessible: {}.", + file_path.string(), + err.message(), + }; + } + + if (file_type == fs::file_type::not_found) + { + mpl::log(mpl::Level::info, name, fmt::format("AZ file {:?} not found, using defaults", file_path.string())); + return { + .name = name, + .file_path = file_path, + // TODO GET ACTUAL SUBNET + .subnet = "", + .available = true, + }; + } + + if (file_type != fs::file_type::regular) + throw AvailabilityZoneDeserializationError{"AZ file {:?} is not a regular file.", file_path.string()}; + + mpl::log(mpl::Level::info, name, fmt::format("reading AZ from file {:?}", file_path.string())); + const auto file = MP_FILEOPS.open_read(file_path); + const auto data = QString::fromStdString(std::string{std::istreambuf_iterator{*file}, {}}).toUtf8(); + if (file->fail()) + { + throw AvailabilityZoneDeserializationError{ + "failed to read AZ file {:?}: {}", + file_path.string(), + std::strerror(errno), + }; + } + const auto json = QJsonDocument::fromJson(data).object(); + + const auto deserialize_subnet = [&] { + if (auto json_subnet = json[subnet_key].toString().toStdString(); !json_subnet.empty()) + return json_subnet; + + mpl::log(mpl::Level::warning, + name, + fmt::format("subnet missing from AZ file {:?}, using default", file_path.string())); + // TODO GET ACTUAL SUBNET + return std::string{}; + }; + + const auto deserialize_available = [&] { + if (const auto json_available = json[available_key]; json_available.isBool()) + return json_available.toBool(); + + mpl::log(mpl::Level::warning, + name, + fmt::format("availability missing from AZ file {:?}, using default", file_path.string())); + return true; + }; + + return { + .name = name, + .file_path = file_path, + .subnet = deserialize_subnet(), + .available = deserialize_available(), + }; +} + +BaseAvailabilityZone::BaseAvailabilityZone(const std::string& name, const fs::path& az_directory) + : m{make(name, az_directory)} +{ + serialize(); +} + +const std::string& BaseAvailabilityZone::get_name() const +{ + return m.name; +} + +const std::string& BaseAvailabilityZone::get_subnet() const +{ + return m.subnet; +} + +bool BaseAvailabilityZone::is_available() const +{ + const std::unique_lock lock{m.mutex}; + return m.available; +} + +void BaseAvailabilityZone::set_available(const bool new_available) +{ + + const std::unique_lock lock{m.mutex}; + if (m.available == new_available) + return; + + mpl::log(mpl::Level::info, m.name, fmt::format("making AZ {}available", new_available ? "" : "un")); + m.available = new_available; + serialize(); + + for (auto& vm : m.vms) + vm.get().make_available(m.available); +} + +void BaseAvailabilityZone::add_vm(VirtualMachine& vm) +{ + const std::unique_lock lock{m.mutex}; + mpl::log(mpl::Level::info, m.name, fmt::format("adding vm {:?} to AZ", vm.vm_name)); + m.vms.emplace_back(vm); +} + +void BaseAvailabilityZone::remove_vm(VirtualMachine& vm) +{ + const std::unique_lock lock{m.mutex}; + mpl::log(mpl::Level::info, m.name, fmt::format("removing vm {:?} from AZ", vm.vm_name)); + // as of now, we use vm names to uniquely identify vms, so we can do the same here + const auto to_remove = std::remove_if(m.vms.begin(), m.vms.end(), [&](const auto& some_vm) { + return some_vm.get().vm_name == vm.vm_name; + }); + m.vms.erase(to_remove, m.vms.end()); +} + +void BaseAvailabilityZone::serialize() const +{ + const std::unique_lock lock{m.mutex}; + mpl::log(mpl::Level::info, m.name, fmt::format("writing AZ to file {:?}", m.file_path.string())); + + const QJsonObject json{ + {subnet_key, QString::fromStdString(m.subnet)}, + {available_key, m.available}, + }; + const auto json_bytes = QJsonDocument{json}.toJson(); + + if (MP_FILEOPS.open_write(m.file_path)->write(json_bytes.data(), json_bytes.size()).fail()) + { + throw AvailabilityZoneSerializationError{ + "failed to write AZ file {:?}: {}", + m.file_path.string(), + std::strerror(errno), + }; + } +} +} // namespace multipass From adfaccd3677b5fe12c5fac5cb8118dd977734002 Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 23 Jan 2025 17:11:13 +0100 Subject: [PATCH 03/21] [az] add az manager implementation --- .../base_availability_zone_manager.h | 56 ++++ src/platform/backends/shared/CMakeLists.txt | 1 + .../shared/base_availability_zone_manager.cpp | 243 ++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 include/multipass/base_availability_zone_manager.h create mode 100644 src/platform/backends/shared/base_availability_zone_manager.cpp diff --git a/include/multipass/base_availability_zone_manager.h b/include/multipass/base_availability_zone_manager.h new file mode 100644 index 0000000000..af7b899b13 --- /dev/null +++ b/include/multipass/base_availability_zone_manager.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MULTIPASS_BASE_AVAILABILITY_ZONE_MANAGER_H +#define MULTIPASS_BASE_AVAILABILITY_ZONE_MANAGER_H + +#include "availability_zone_manager.h" + +#include +#include +#include + +namespace multipass +{ +class BaseAvailabilityZoneManager final : public AvailabilityZoneManager +{ +public: + explicit BaseAvailabilityZoneManager(const std::filesystem::path& data_dir); + + AvailabilityZone& get_zone(const std::string& name) override; + std::vector> get_zones() override; + std::string get_automatic_zone_name() override; + std::string get_default_zone_name() const override; + +private: + void serialize() const; + + // we store all the data in one struct so that it can be created from one function call in the initializer list + struct data + { + const std::filesystem::path file_path{}; + const std::map zones{}; + + mutable std::recursive_mutex mutex{}; + std::string automatic_zone{}; + } m; + + static data make(const std::filesystem::path& data_dir); +}; +} // namespace multipass + +#endif // MULTIPASS_BASE_AVAILABILITY_ZONE_MANAGER_H diff --git a/src/platform/backends/shared/CMakeLists.txt b/src/platform/backends/shared/CMakeLists.txt index 5b6a754259..e65d52cbe4 100644 --- a/src/platform/backends/shared/CMakeLists.txt +++ b/src/platform/backends/shared/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(shared STATIC base_availability_zone.cpp + base_availability_zone_manager.cpp base_snapshot.cpp base_virtual_machine.cpp base_virtual_machine_factory.cpp diff --git a/src/platform/backends/shared/base_availability_zone_manager.cpp b/src/platform/backends/shared/base_availability_zone_manager.cpp new file mode 100644 index 0000000000..66f572927e --- /dev/null +++ b/src/platform/backends/shared/base_availability_zone_manager.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "multipass/base_availability_zone_manager.h" +#include "multipass/base_availability_zone.h" +#include "multipass/exceptions/availability_zone_exceptions.h" +#include "multipass/file_ops.h" +#include "multipass/logging/log.h" + +#include + +#include + +#include + +namespace multipass +{ + +namespace mpl = logging; + +constexpr auto category = "az-manager"; +constexpr auto automatic_zone_key = "automatic_zone"; + +BaseAvailabilityZoneManager::data BaseAvailabilityZoneManager::make(const fs::path& data_dir) +{ + std::error_code err; + const auto zones_directory = data_dir / "zones"; + + mpl::log(mpl::Level::info, category, "creating AZ manager"); + + const auto read_zone_names = [&] { + std::set default_zones{"zone1", "zone2", "zone3"}; + + const auto zones_dir_iter = MP_FILEOPS.dir_iterator(zones_directory, err); + if (err.value() == static_cast(std::errc::no_such_file_or_directory)) + { + mpl::log(mpl::Level::info, + category, + fmt::format("{:?} is missing, attempting to create it", zones_directory.string())); + MP_FILEOPS.create_directory(zones_directory, err); + if (err) + { + throw AvailabilityZoneManagerDeserializationError{ + "failed to create {:?}: {}", + zones_directory.string(), + err.message(), + }; + } + + mpl::log(mpl::Level::info, category, "using default zones"); + return default_zones; + } + + if (err) + { + throw AvailabilityZoneManagerDeserializationError{ + "failed to access {:?}: {}", + zones_directory.string(), + err.message(), + }; + } + + std::set zone_names{}; + while (zones_dir_iter->hasNext()) + { + const auto& entry = zones_dir_iter->next(); + if (!entry.is_regular_file()) + continue; + if (entry.path().extension() != ".json") + continue; + + mpl::log(mpl::Level::info, category, fmt::format("found AZ file {:?}", entry.path().string())); + zone_names.insert(entry.path().stem()); + } + if (zone_names.empty()) + { + mpl::log(mpl::Level::info, category, "no zones found, using defaults"); + return default_zones; + } + return zone_names; + }; + + const auto zone_names = read_zone_names(); + if (zone_names.empty()) + { + throw AvailabilityZoneManagerDeserializationError{"no zone names retrieved"}; + } + + const auto create_zones = [&] { + std::map zones; + for (const auto& name : zone_names) + zones[name] = std::make_unique(name, zones_directory); + return zones; + }; + + const auto file_path{data_dir / "az_manager.json"}; + const auto file_type = MP_FILEOPS.status(file_path, err).type(); + if (err && file_type != fs::file_type::not_found) + { + throw AvailabilityZoneManagerDeserializationError{ + "AZ manager file {:?} is not accessible: {}.", + file_path.string(), + err.message(), + }; + } + + if (file_type == fs::file_type::not_found) + { + mpl::log(mpl::Level::info, + category, + fmt::format("AZ manager file {:?} not found, using defaults", file_path.string())); + return { + .file_path = file_path, + .zones = create_zones(), + .automatic_zone = *zone_names.begin(), + }; + } + + if (file_type != fs::file_type::regular) + { + throw AvailabilityZoneManagerDeserializationError{ + "AZ manager file {:?} is not a regular file.", + file_path.string(), + }; + } + + mpl::log(mpl::Level::info, category, fmt::format("reading AZ manager from file {:?}", file_path.string())); + const auto file = MP_FILEOPS.open_read(file_path); + if (file->fail()) + { + throw AvailabilityZoneManagerDeserializationError{ + "failed to read AZ manager file {:?}: {}", + file_path.string(), + std::strerror(errno), + }; + } + + const std::string data{std::istreambuf_iterator{*file}, {}}; + const auto qdata = QString::fromStdString(data).toUtf8(); + const auto json = QJsonDocument::fromJson(qdata).object(); + + const auto deserialize_automatic_zone = [&] { + auto json_automatic_zone = json[automatic_zone_key].toString().toStdString(); + if (zone_names.find(json_automatic_zone) != zone_names.end()) + return json_automatic_zone; + + mpl::log(mpl::Level::warning, + category, + fmt::format("automatic zone {:?} not known, using default", json_automatic_zone)); + return *zone_names.begin(); + }; + + return { + .file_path = file_path, + .zones = create_zones(), + .automatic_zone = deserialize_automatic_zone(), + }; +} + +BaseAvailabilityZoneManager::BaseAvailabilityZoneManager(const fs::path& data_dir) : m{make(data_dir)} +{ + serialize(); +} + +AvailabilityZone& BaseAvailabilityZoneManager::get_zone(const std::string& name) +{ + if (const auto it = m.zones.find(name); it != m.zones.end()) + return *it->second; + throw AvailabilityZoneNotFound{name}; +} + +std::string BaseAvailabilityZoneManager::get_automatic_zone_name() +{ + const std::unique_lock lock{m.mutex}; + // round-robin until we get an available zone + const auto start = m.zones.find(m.automatic_zone); + auto current = start; + do + { + if (const auto& [zone_name, zone] = *current; zone->is_available()) + { + const auto result = zone_name; + if (++current == m.zones.end()) + current = m.zones.begin(); + m.automatic_zone = current->first; + serialize(); + return result; + } + + if (++current == m.zones.end()) + current = m.zones.begin(); + } while (current != start); + + throw NoAvailabilityZoneAvailable{}; +} + +std::vector> BaseAvailabilityZoneManager::get_zones() +{ + std::vector> zone_list; + zone_list.reserve(m.zones.size()); + for (auto& [_, zone] : m.zones) + zone_list.emplace_back(*zone); + return zone_list; +} +std::string BaseAvailabilityZoneManager::get_default_zone_name() const +{ + return m.zones.begin()->first; +} + +void BaseAvailabilityZoneManager::serialize() const +{ + const std::unique_lock lock{m.mutex}; + mpl::log(mpl::Level::info, category, fmt::format("writing AZ manager to file {:?}", m.file_path.string())); + + const QJsonObject json{ + {automatic_zone_key, QString::fromStdString(m.automatic_zone)}, + }; + const auto json_bytes = QJsonDocument{json}.toJson(); + + if (MP_FILEOPS.open_write(m.file_path)->write(json_bytes.data(), json_bytes.size()).fail()) + { + throw AvailabilityZoneManagerSerializationError{ + "failed to write to AZ manager file {:?}: {}", + m.file_path.string(), + std::strerror(errno), + }; + } +} +} // namespace multipass From c70fc1f5661290ac6d174a7a9e3f6bc71dc77e9d Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 23 Jan 2025 17:12:36 +0100 Subject: [PATCH 04/21] [daemon] integrate zones in daemon --- src/daemon/daemon.cpp | 104 +++++++++++++++++++++++++++-------- src/daemon/daemon_config.cpp | 27 +++++++-- src/daemon/daemon_config.h | 3 + 3 files changed, 106 insertions(+), 28 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index eaaccdffdc..3192a66ba9 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -217,7 +218,9 @@ auto name_from(const std::string& requested_name, const std::string& blueprint_n } } -std::unordered_map load_db(const mp::Path& data_path, const mp::Path& cache_path) +std::unordered_map load_db(const mp::Path& data_path, + const mp::Path& cache_path, + const mp::AvailabilityZoneManager& az_manager) { QDir data_dir{data_path}; QDir cache_dir{cache_path}; @@ -255,6 +258,8 @@ std::unordered_map load_db(const mp::Path& data_path, auto deleted = record["deleted"].toBool(); auto metadata = record["metadata"].toObject(); auto clone_count = record["clone_count"].toInt(); + auto zone = record["zone"].toString().toStdString(); + zone = zone.empty() ? az_manager.get_default_zone_name() : zone; if (!num_cores && !deleted && ssh_username.empty() && metadata.isEmpty() && !mp::MemorySize{mem_size}.in_bytes() && !mp::MemorySize{disk_space}.in_bytes()) @@ -292,7 +297,9 @@ std::unordered_map load_db(const mp::Path& data_path, mounts, deleted, metadata, - clone_count}; + clone_count, + zone, + }; } return reconstructed_records; } @@ -323,6 +330,7 @@ QJsonObject vm_spec_to_json(const mp::VMSpecs& specs) json.insert("mounts", json_mounts); json.insert("clone_count", specs.clone_count); + json.insert("zone", QString::fromStdString(specs.zone)); return json; } @@ -496,7 +504,8 @@ void validate_image(const mp::LaunchRequest* request, const mp::VMImageVault& va auto validate_create_arguments(const mp::LaunchRequest* request, const mp::DaemonConfig* config) { - assert(config && config->factory && config->blueprint_provider && config->vault && "null ptr somewhere..."); + assert(config && config->factory && config->blueprint_provider && config->vault && config->az_manager && + "null ptr somewhere..."); validate_image(request, *config->vault, *config->blueprint_provider); static const auto min_mem = try_mem_size(mp::min_memory_size); @@ -506,6 +515,7 @@ auto validate_create_arguments(const mp::LaunchRequest* request, const mp::Daemo auto mem_size_str = request->mem_size(); auto disk_space_str = request->disk_space(); auto instance_name = request->instance_name(); + auto zone_name = request->zone(); auto option_errors = mp::LaunchError{}; const auto opt_mem_size = try_mem_size(mem_size_str.empty() ? mp::default_memory_size : mem_size_str); @@ -535,6 +545,16 @@ auto validate_create_arguments(const mp::LaunchRequest* request, const mp::Daemo if (!instance_name.empty() && !mp::utils::valid_hostname(instance_name)) option_errors.add_error_codes(mp::LaunchError::INVALID_HOSTNAME); + try + { + if (!zone_name.empty() && !config->az_manager->get_zone(zone_name).is_available()) + option_errors.add_error_codes(mp::LaunchError::ZONE_UNAVAILABLE); + } + catch (const mp::AvailabilityZoneNotFound& e) + { + option_errors.add_error_codes(mp::LaunchError::INVALID_ZONE); + } + std::vector nets_need_bridging; auto extra_interfaces = validate_extra_interfaces(request, *config->factory, nets_need_bridging, option_errors); @@ -543,11 +563,19 @@ auto validate_create_arguments(const mp::LaunchRequest* request, const mp::Daemo mp::MemorySize mem_size; std::optional disk_space; std::string instance_name; + std::string zone_name; std::vector extra_interfaces; std::vector nets_need_bridging; mp::LaunchError option_errors; - } ret{std::move(mem_size), std::move(disk_space), std::move(instance_name), - std::move(extra_interfaces), std::move(nets_need_bridging), std::move(option_errors)}; + } ret{ + std::move(mem_size), + std::move(disk_space), + std::move(instance_name), + std::move(zone_name), + std::move(extra_interfaces), + std::move(nets_need_bridging), + std::move(option_errors), + }; return ret; } @@ -1293,7 +1321,8 @@ mp::Daemon::Daemon(std::unique_ptr the_config) : config{std::move(the_config)}, vm_instance_specs{load_db( mp::utils::backend_directory_path(config->data_directory, config->factory->get_backend_directory_name()), - mp::utils::backend_directory_path(config->cache_directory, config->factory->get_backend_directory_name()))}, + mp::utils::backend_directory_path(config->cache_directory, config->factory->get_backend_directory_name()), + *config->az_manager)}, daemon_rpc{config->server_address, *config->cert_provider, config->client_cert_store.get()}, instance_mod_handler{register_instance_mod( vm_instance_specs, @@ -1359,6 +1388,7 @@ mp::Daemon::Daemon(std::unique_ptr the_config) spec.mem_size, spec.disk_space, name, + spec.zone, spec.default_mac_address, spec.extra_interfaces, spec.ssh_username, @@ -1371,7 +1401,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, *config->ssh_key_provider, *this); + config->factory->create_virtual_machine(vm_desc, *config->ssh_key_provider, *this, *config->az_manager); 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 @@ -2810,7 +2840,8 @@ try destination_name, dest_vm_image, *config->ssh_key_provider, - *this); + *this, + *config->az_manager); ++src_spec.clone_count; // preparing instance is done preparing_instances.erase(destination_name); @@ -2863,6 +2894,13 @@ try // clang-format on ZonesReply response{}; + for (const auto& zone : config->az_manager->get_zones()) + { + const auto reply_zone = response.add_zones(); + reply_zone->set_name(zone.get().get_name()); + reply_zone->set_available(zone.get().is_available()); + } + server->Write(response); status_promise->set_value(grpc::Status{}); } @@ -2878,6 +2916,11 @@ try // clang-format on { mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; + for (const auto& zone_name : request->zones()) + { + config->az_manager->get_zone(zone_name).set_available(request->available()); + } + status_promise->set_value(grpc::Status{}); } catch (const std::exception& e) @@ -3018,6 +3061,8 @@ void mp::Daemon::create_vm(const CreateRequest* request, } auto name = name_from(checked_args.instance_name, blueprint_name, *config->name_generator, operative_instances); + auto zone_name = + checked_args.zone_name.empty() ? config->az_manager->get_automatic_zone_name() : checked_args.zone_name; auto [instance_trail, status] = find_instance_and_react(operative_instances, deleted_instances, name, require_missing_instances_reaction); @@ -3055,18 +3100,24 @@ void mp::Daemon::create_vm(const CreateRequest* request, auto& vm_aliases = vm_client_data.aliases_to_be_created; auto& vm_workspaces = vm_client_data.workspaces_to_be_created; - vm_instance_specs[name] = {vm_desc.num_cores, - vm_desc.mem_size, - vm_desc.disk_space, - vm_desc.default_mac_address, - vm_desc.extra_interfaces, - config->ssh_username, - VirtualMachine::State::off, - {}, - false, - QJsonObject()}; - operative_instances[name] = - config->factory->create_virtual_machine(vm_desc, *config->ssh_key_provider, *this); + vm_instance_specs[name] = { + vm_desc.num_cores, + vm_desc.mem_size, + vm_desc.disk_space, + vm_desc.default_mac_address, + vm_desc.extra_interfaces, + config->ssh_username, + VirtualMachine::State::off, + {}, + false, + QJsonObject(), + 0, + vm_desc.zone, + }; + operative_instances[name] = config->factory->create_virtual_machine(vm_desc, + *config->ssh_key_provider, + *this, + *config->az_manager); preparing_instances.erase(name); persist_instances(); @@ -3136,7 +3187,8 @@ void mp::Daemon::create_vm(const CreateRequest* request, delete prepare_future_watcher; }); - auto make_vm_description = [this, server, request, name, checked_args, log_level]() mutable -> VMFullDescription { + auto make_vm_description = + [this, server, request, name, zone_name, checked_args, log_level]() mutable -> VMFullDescription { mpl::ClientLogger logger{log_level, *config->logger, server}; try @@ -3151,6 +3203,7 @@ void mp::Daemon::create_vm(const CreateRequest* request, MemorySize{request->mem_size().empty() ? "0b" : request->mem_size()}, MemorySize{request->disk_space().empty() ? "0b" : request->disk_space()}, name, + zone_name, "", {}, config->ssh_username, @@ -3158,9 +3211,12 @@ void mp::Daemon::create_vm(const CreateRequest* request, "", YAML::Node{}, YAML::Node{}, - make_cloud_init_vendor_config(*config->ssh_key_provider, config->ssh_username, - config->factory->get_backend_version_string().toStdString(), request), - YAML::Node{}}; + make_cloud_init_vendor_config(*config->ssh_key_provider, + config->ssh_username, + config->factory->get_backend_version_string().toStdString(), + request), + YAML::Node{}, + }; ClientLaunchData client_launch_data; diff --git a/src/daemon/daemon_config.cpp b/src/daemon/daemon_config.cpp index 164b74bdbc..2de3539ca9 100644 --- a/src/daemon/daemon_config.cpp +++ b/src/daemon/daemon_config.cpp @@ -20,6 +20,7 @@ #include "custom_image_host.h" #include "ubuntu_image_host.h" +#include #include #include #include @@ -237,9 +238,27 @@ std::unique_ptr mp::DaemonConfigBuilder::build() fs::perms::owner_all | fs::perms::group_exec | fs::perms::others_exec), server_name_from(server_address)); + if (az_manager == nullptr) + az_manager = std::make_unique(data_directory.toStdString()); + return std::unique_ptr(new DaemonConfig{ - std::move(url_downloader), std::move(factory), std::move(image_hosts), std::move(vault), - std::move(name_generator), std::move(ssh_key_provider), std::move(cert_provider), std::move(client_cert_store), - std::move(update_prompt), multiplexing_logger, std::move(network_proxy), std::move(blueprint_provider), - cache_directory, data_directory, server_address, ssh_username, image_refresh_timer}); + std::move(url_downloader), + std::move(factory), + std::move(image_hosts), + std::move(vault), + std::move(name_generator), + std::move(ssh_key_provider), + std::move(cert_provider), + std::move(client_cert_store), + std::move(update_prompt), + multiplexing_logger, + std::move(network_proxy), + std::move(blueprint_provider), + std::move(az_manager), + cache_directory, + data_directory, + server_address, + ssh_username, + image_refresh_timer, + }); } diff --git a/src/daemon/daemon_config.h b/src/daemon/daemon_config.h index 6f1b48df51..5b6d2a0c4e 100644 --- a/src/daemon/daemon_config.h +++ b/src/daemon/daemon_config.h @@ -18,6 +18,7 @@ #ifndef MULTIPASS_DAEMON_CONFIG_H #define MULTIPASS_DAEMON_CONFIG_H +#include #include #include #include @@ -56,6 +57,7 @@ struct DaemonConfig const std::shared_ptr logger; const std::unique_ptr network_proxy; const std::unique_ptr blueprint_provider; + const AvailabilityZoneManager::UPtr az_manager; const multipass::Path cache_directory; const multipass::Path data_directory; const std::string server_address; @@ -77,6 +79,7 @@ struct DaemonConfigBuilder std::unique_ptr logger; std::unique_ptr network_proxy; std::unique_ptr blueprint_provider; + AvailabilityZoneManager::UPtr az_manager; multipass::Path cache_directory; multipass::Path data_directory; std::string server_address; From c06bfa860389c29cb67c6cb3e7356f4239c4b64e Mon Sep 17 00:00:00 2001 From: Andrei Toterman Date: Thu, 23 Jan 2025 17:21:03 +0100 Subject: [PATCH 05/21] [tests] make stub az classes and put them where they are needed # Conflicts: # tests/test_blueprint_provider.cpp # tests/test_daemon.cpp # tests/test_delayed_shutdown.cpp # tests/test_sshfs_mount_handler.cpp --- tests/blueprint_test_lambdas.cpp | 9 +- tests/blueprint_test_lambdas.h | 6 +- tests/libvirt/test_libvirt_backend.cpp | 39 ++++--- tests/lxd/test_lxd_backend.cpp | 35 +++++- tests/lxd/test_lxd_mount_handler.cpp | 93 ++++++++++------ tests/mock_virtual_machine.h | 1 + tests/mock_virtual_machine_factory.h | 8 +- tests/qemu/test_qemu_backend.cpp | 115 ++++++++++++-------- tests/qemu/test_qemu_mount_handler.cpp | 12 +- tests/qemu/test_qemu_snapshot.cpp | 24 +++- tests/qemu/test_qemu_vm_process_spec.cpp | 1 + tests/stub_availability_zone.h | 70 ++++++++++++ tests/stub_availability_zone_manager.h | 57 ++++++++++ tests/stub_virtual_machine.h | 7 +- tests/stub_virtual_machine_factory.h | 3 +- tests/test_alias_dict.cpp | 3 + tests/test_base_snapshot.cpp | 4 +- tests/test_base_virtual_machine.cpp | 67 +++++++----- tests/test_base_virtual_machine_factory.cpp | 6 +- tests/test_blueprint_provider.cpp | 48 ++++---- tests/test_daemon.cpp | 38 ++++--- tests/test_daemon_authenticate.cpp | 3 + tests/test_daemon_clone.cpp | 6 +- tests/test_daemon_find.cpp | 3 + tests/test_daemon_launch.cpp | 3 + tests/test_daemon_mount.cpp | 18 +-- tests/test_daemon_snapshot_restore.cpp | 6 +- tests/test_daemon_start.cpp | 17 ++- tests/test_daemon_suspend.cpp | 8 +- tests/test_daemon_umount.cpp | 12 +- tests/test_delayed_shutdown.cpp | 6 +- tests/test_instance_settings_handler.cpp | 4 +- tests/test_sshfs_mount_handler.cpp | 10 +- tests/unix/test_daemon_rpc.cpp | 2 + 34 files changed, 527 insertions(+), 217 deletions(-) create mode 100644 tests/stub_availability_zone.h create mode 100644 tests/stub_availability_zone_manager.h diff --git a/tests/blueprint_test_lambdas.cpp b/tests/blueprint_test_lambdas.cpp index 9c9f80aedb..eaf06108cd 100644 --- a/tests/blueprint_test_lambdas.cpp +++ b/tests/blueprint_test_lambdas.cpp @@ -67,8 +67,10 @@ mpt::fetch_image_lambda(const std::string& release, const std::string& remote, c }; } -std::function< - mp::VirtualMachine::UPtr(const mp::VirtualMachineDescription&, const mp::SSHKeyProvider&, mp::VMStatusMonitor&)> +std::function mpt::create_virtual_machine_lambda(const int& num_cores, const mp::MemorySize& mem_size, const mp::MemorySize& disk_space, @@ -76,7 +78,8 @@ mpt::create_virtual_machine_lambda(const int& num_cores, { return [&num_cores, &mem_size, &disk_space, &name](const mp::VirtualMachineDescription& vm_desc, const mp::SSHKeyProvider&, - mp::VMStatusMonitor&) { + mp::VMStatusMonitor&, + const mp::AvailabilityZoneManager& az_manager) { EXPECT_EQ(vm_desc.num_cores, num_cores); EXPECT_EQ(vm_desc.mem_size, mem_size); EXPECT_EQ(vm_desc.disk_space, disk_space); diff --git a/tests/blueprint_test_lambdas.h b/tests/blueprint_test_lambdas.h index a69d5d63a5..041402fe23 100644 --- a/tests/blueprint_test_lambdas.h +++ b/tests/blueprint_test_lambdas.h @@ -19,6 +19,7 @@ #define MULTIPASS_BLUEPRINT_TEST_LAMBDAS_H #include +#include #include #include #include @@ -47,7 +48,10 @@ std::function fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum = false); -std::function +std::function create_virtual_machine_lambda(const int& num_cores, const MemorySize& mem_size, const MemorySize& disk_space, diff --git a/tests/libvirt/test_libvirt_backend.cpp b/tests/libvirt/test_libvirt_backend.cpp index 54df51bcef..01f2ea4ae7 100644 --- a/tests/libvirt/test_libvirt_backend.cpp +++ b/tests/libvirt/test_libvirt_backend.cpp @@ -21,6 +21,7 @@ #include "tests/mock_logger.h" #include "tests/mock_ssh.h" #include "tests/mock_status_monitor.h" +#include "tests/stub_availability_zone_manager.h" #include "tests/stub_ssh_key_provider.h" #include "tests/stub_status_monitor.h" #include "tests/temp_dir.h" @@ -52,6 +53,7 @@ struct LibVirtBackend : public Test "pied-piper-valley", "", {}, + {}, "ubuntu", {dummy_image.name(), "", "", "", "", {}}, dummy_cloud_init_iso.name(), @@ -61,6 +63,7 @@ struct LibVirtBackend : public Test {}}; mpt::TempDir data_dir; mpt::StubSSHKeyProvider key_provider; + mpt::StubAvailabilityZoneManager az_manager{}; // This indicates that LibvirtWrapper should open the test executable std::string fake_libvirt_path{""}; @@ -111,7 +114,7 @@ TEST_F(LibVirtBackend, creates_in_off_state) mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path}; mpt::StubVMStatusMonitor stub_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off)); } @@ -122,7 +125,7 @@ TEST_F(LibVirtBackend, creates_in_suspended_state_with_managed_save) backend.libvirt_wrapper->virDomainHasManagedSaveImage = [](auto...) { return 1; }; mpt::StubVMStatusMonitor stub_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::suspended)); } @@ -144,7 +147,7 @@ TEST_F(LibVirtBackend, machine_sends_monitoring_events) }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, on_resume()); machine->start(); @@ -167,7 +170,7 @@ TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_start) { mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path}; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, persist_state_for(_, _)); machine->start(); @@ -189,7 +192,7 @@ TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_shutdown) }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, persist_state_for(_, _)); machine->shutdown(); @@ -210,7 +213,7 @@ TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_suspend) }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, persist_state_for(_, _)); machine->suspend(); @@ -230,7 +233,7 @@ TEST_F(LibVirtBackend, start_with_broken_libvirt_connection_throws) backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_THROW(machine->start(), std::runtime_error); @@ -243,7 +246,7 @@ TEST_F(LibVirtBackend, shutdown_with_broken_libvirt_connection_throws) backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_THROW(machine->shutdown(), std::runtime_error); @@ -256,7 +259,7 @@ TEST_F(LibVirtBackend, suspend_with_broken_libvirt_connection_throws) backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_THROW(machine->suspend(), std::runtime_error); @@ -269,7 +272,7 @@ TEST_F(LibVirtBackend, current_state_with_broken_libvirt_unknown) backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::unknown)); } @@ -283,7 +286,7 @@ TEST_F(LibVirtBackend, current_state_delayed_shutdown_domain_running) }; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); machine->state = mp::VirtualMachine::State::delayed_shutdown; EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::delayed_shutdown)); @@ -293,7 +296,7 @@ TEST_F(LibVirtBackend, current_state_delayed_shutdown_domain_off) { mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path}; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); machine->state = mp::VirtualMachine::State::delayed_shutdown; EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off)); @@ -303,7 +306,7 @@ TEST_F(LibVirtBackend, current_state_off_domain_starts_running) { mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path}; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off)); @@ -386,7 +389,7 @@ TEST_F(LibVirtBackend, ssh_hostname_returns_expected_value) return static_virNetworkGetDHCPLeases(network, mac, leases, flags); }; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); backend.libvirt_wrapper->virDomainGetState = [](auto, auto state, auto, auto) { @@ -404,7 +407,7 @@ TEST_F(LibVirtBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) backend.libvirt_wrapper->virNetworkGetDHCPLeases = [](auto...) { return 0; }; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); backend.libvirt_wrapper->virDomainGetState = [](auto, auto state, auto, auto) { @@ -437,7 +440,7 @@ TEST_F(LibVirtBackend, shutdown_while_starting_throws_and_sets_correct_state) backend.libvirt_wrapper->virDomainDestroy = [](virDomainPtr domain) { return static_virDomainDestroy(domain); }; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); @@ -464,7 +467,7 @@ TEST_F(LibVirtBackend, machineInOffStateLogsAndIgnoresShutdown) { mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path}; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off)); @@ -487,7 +490,7 @@ TEST_F(LibVirtBackend, machineNoForceCannotShutdownLogsAndThrows) mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path}; NiceMock mock_monitor; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); backend.libvirt_wrapper->virDomainGetState = [](auto, auto state, auto, auto) { *state = VIR_DOMAIN_RUNNING; diff --git a/tests/lxd/test_lxd_backend.cpp b/tests/lxd/test_lxd_backend.cpp index 60e6a69957..dae669b7b7 100644 --- a/tests/lxd/test_lxd_backend.cpp +++ b/tests/lxd/test_lxd_backend.cpp @@ -25,6 +25,8 @@ #include "tests/mock_logger.h" #include "tests/mock_platform.h" #include "tests/mock_status_monitor.h" +#include "tests/stub_availability_zone.h" +#include "tests/stub_availability_zone_manager.h" #include "tests/stub_ssh_key_provider.h" #include "tests/stub_status_monitor.h" #include "tests/stub_url_downloader.h" @@ -70,6 +72,7 @@ struct LXDBackend : public Test mp::MemorySize{"3M"}, mp::MemorySize{}, // not used "pied-piper-valley", + "zone1", "00:16:3e:fe:f2:b9", {}, "yoda", @@ -87,6 +90,8 @@ struct LXDBackend : public Test std::unique_ptr> mock_network_access_manager; QUrl base_url{"unix:///foo@1.0"}; const QString default_storage_pool{"default"}; + mpt::StubAvailabilityZone zone{}; + mpt::StubAvailabilityZoneManager az_manager{}; }; struct LXDInstanceStatusTestSuite : LXDBackend, WithParamInterface @@ -383,7 +388,7 @@ TEST_F(LXDBackend, factory_creates_valid_virtual_machine_ptr) mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); EXPECT_NE(nullptr, machine); } @@ -455,6 +460,7 @@ TEST_F(LXDBackend, creates_in_stopped_state) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_TRUE(vm_created); @@ -509,6 +515,7 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_start) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); @@ -565,6 +572,7 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_shutdown) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); @@ -616,6 +624,7 @@ TEST_F(LXDBackend, machine_persists_internal_stopped_state_on_destruction) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; ASSERT_EQ(machine.state, mp::VirtualMachine::State::running); @@ -678,6 +687,7 @@ TEST_F(LXDBackend, machine_does_not_update_state_in_dtor) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; } @@ -740,6 +750,7 @@ TEST_F(LXDBackend, machineLogsNotFoundExceptionInDtor) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; machine.shutdown(); } @@ -795,6 +806,7 @@ TEST_F(LXDBackend, does_not_call_stop_when_snap_refresh_is_detected) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; } @@ -846,6 +858,7 @@ TEST_F(LXDBackend, calls_stop_when_snap_refresh_does_not_exist) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; } @@ -936,6 +949,7 @@ TEST_F(LXDBackend, posts_expected_data_when_creating_instance) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; } @@ -1162,6 +1176,7 @@ TEST_P(LXDNetworkInfoSuite, returns_expected_network_info) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(), "10.217.27.168"); @@ -1213,6 +1228,7 @@ TEST_F(LXDBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_THROW(machine.ssh_hostname(std::chrono::milliseconds(1)), std::runtime_error); @@ -1257,6 +1273,7 @@ TEST_F(LXDBackend, no_ip_address_returns_unknown) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(), "UNKNOWN"); @@ -1592,6 +1609,7 @@ TEST_F(LXDBackend, unsupported_suspend_throws) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; MP_EXPECT_THROW_THAT(machine.suspend(), @@ -1632,6 +1650,7 @@ TEST_F(LXDBackend, start_while_frozen_unfreezes) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, @@ -1665,6 +1684,7 @@ TEST_F(LXDBackend, shutdown_while_stopped_does_nothing_and_logs_debug) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -1704,6 +1724,7 @@ TEST_F(LXDBackend, shutdown_while_frozen_throws_and_logs_info) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::suspended); @@ -1758,6 +1779,7 @@ TEST_F(LXDBackend, ensure_vm_running_does_not_throw_starting) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; machine.start(); @@ -1817,6 +1839,7 @@ TEST_F(LXDBackend, shutdown_while_starting_throws_and_sets_correct_state) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; machine.start(); @@ -1878,6 +1901,7 @@ TEST_F(LXDBackend, start_failure_while_starting_throws_and_sets_correct_state) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; machine.start(); @@ -1938,6 +1962,7 @@ TEST_F(LXDBackend, reboots_while_starting_does_not_throw_and_sets_correct_state) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; machine.start(); @@ -1967,6 +1992,7 @@ TEST_F(LXDBackend, current_state_connection_error_logs_warning_and_sets_unknown_ bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, @@ -2026,6 +2052,7 @@ TEST_P(LXDInstanceStatusTestSuite, lxd_state_returns_expected_VirtualMachine_sta bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; EXPECT_EQ(machine.current_state(), expected_state); @@ -2263,6 +2290,7 @@ TEST_F(LXDBackend, posts_extra_network_devices) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; } @@ -2287,6 +2315,7 @@ TEST_F(LXDBackend, posts_network_data_config_if_available) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; } @@ -2374,7 +2403,7 @@ TEST_F(LXDBackend, addsNetworkInterface) mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->shutdown(); @@ -2393,5 +2422,5 @@ TEST_F(LXDBackend, converts_http_to_https) }); mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), QUrl{"http://bar"}}; - backend.create_virtual_machine(default_description, key_provider, stub_monitor); + backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); } diff --git a/tests/lxd/test_lxd_mount_handler.cpp b/tests/lxd/test_lxd_mount_handler.cpp index c207fb3f1b..e879ef088b 100644 --- a/tests/lxd/test_lxd_mount_handler.cpp +++ b/tests/lxd/test_lxd_mount_handler.cpp @@ -20,6 +20,7 @@ #include "tests/mock_file_ops.h" #include "tests/mock_logger.h" #include "tests/mock_virtual_machine.h" +#include "tests/stub_availability_zone.h" #include "tests/stub_ssh_key_provider.h" #include "tests/stub_status_monitor.h" #include "tests/temp_dir.h" @@ -46,14 +47,18 @@ class MockLXDVirtualMachine : public mpt::MockVirtualMachineT{desc, - monitor, - manager, - base_url, - bridge_name, - storage_pool, - key_provider} + const mp::SSHKeyProvider& key_provider, + mp::AvailabilityZone& zone) + : mpt::MockVirtualMachineT{ + desc, + monitor, + manager, + base_url, + bridge_name, + storage_pool, + key_provider, + zone, + } { } }; @@ -92,10 +97,12 @@ struct LXDMountHandlerTestFixture : public testing::Test const QString default_storage_pool{"default"}; mpt::StubVMStatusMonitor stub_monitor; const QString bridge_name{"mpbr0"}; + mpt::StubAvailabilityZone zone{}; const mp::VirtualMachineDescription default_description{2, mp::MemorySize{"3M"}, mp::MemorySize{}, // not used "pied-piper-valley", + "zone1", "00:16:3e:fe:f2:b9", {}, "yoda", @@ -120,13 +127,16 @@ struct LXDMountHandlerValidGidUidParameterTests : public LXDMountHandlerTestFixt TEST_F(LXDMountHandlerTestFixture, startDoesNotThrowIfVMIsStopped) { - NiceMock lxd_vm{default_description, - stub_monitor, - &mock_network_access_manager, - base_url, - bridge_name, - default_storage_pool, - key_provider}; + NiceMock lxd_vm{ + default_description, + stub_monitor, + &mock_network_access_manager, + base_url, + bridge_name, + default_storage_pool, + key_provider, + zone, + }; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); @@ -138,13 +148,16 @@ TEST_F(LXDMountHandlerTestFixture, startDoesNotThrowIfVMIsStopped) TEST_F(LXDMountHandlerTestFixture, startThrowsIfVMIsRunning) { - NiceMock lxd_vm{default_description, - stub_monitor, - &mock_network_access_manager, - base_url, - bridge_name, - default_storage_pool, - key_provider}; + NiceMock lxd_vm{ + default_description, + stub_monitor, + &mock_network_access_manager, + base_url, + bridge_name, + default_storage_pool, + key_provider, + zone, + }; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); EXPECT_CALL(lxd_vm, current_state).WillOnce(Return(multipass::VirtualMachine::State::running)); @@ -156,13 +169,16 @@ TEST_F(LXDMountHandlerTestFixture, startThrowsIfVMIsRunning) TEST_F(LXDMountHandlerTestFixture, stopDoesNotThrowIfVMIsStopped) { - NiceMock lxd_vm{default_description, - stub_monitor, - &mock_network_access_manager, - base_url, - bridge_name, - default_storage_pool, - key_provider}; + NiceMock lxd_vm{ + default_description, + stub_monitor, + &mock_network_access_manager, + base_url, + bridge_name, + default_storage_pool, + key_provider, + zone, + }; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); EXPECT_CALL(lxd_vm, current_state) @@ -176,13 +192,16 @@ TEST_F(LXDMountHandlerTestFixture, stopDoesNotThrowIfVMIsStopped) TEST_F(LXDMountHandlerTestFixture, stopThrowsIfVMIsRunning) { - NiceMock lxd_vm{default_description, - stub_monitor, - &mock_network_access_manager, - base_url, - bridge_name, - default_storage_pool, - key_provider}; + NiceMock lxd_vm{ + default_description, + stub_monitor, + &mock_network_access_manager, + base_url, + bridge_name, + default_storage_pool, + key_provider, + zone, + }; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); @@ -206,6 +225,7 @@ TEST_P(LXDMountHandlerInvalidGidUidParameterTests, mountWithGidOrUid) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; const auto& [host_gid, instance_gid, host_uid, instance_uid] = GetParam(); const mp::VMMount vm_mount{ @@ -230,6 +250,7 @@ TEST_P(LXDMountHandlerValidGidUidParameterTests, mountWithGidOrUid) bridge_name, default_storage_pool, key_provider, + zone, instance_dir.path()}; const auto& [host_gid, host_uid] = GetParam(); const int default_instance_id = -1; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index c9e5ccad64..6433954a8a 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -58,6 +58,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, start, (), (override)); MOCK_METHOD(void, shutdown, (VirtualMachine::ShutdownPolicy), (override)); MOCK_METHOD(void, suspend, (), (override)); + MOCK_METHOD(void, make_available, (bool), (override)); MOCK_METHOD(multipass::VirtualMachine::State, current_state, (), (override)); MOCK_METHOD(int, ssh_port, (), (override)); MOCK_METHOD(std::string, ssh_hostname, (), (override)); diff --git a/tests/mock_virtual_machine_factory.h b/tests/mock_virtual_machine_factory.h index 4de0dce053..fbb99b6268 100644 --- a/tests/mock_virtual_machine_factory.h +++ b/tests/mock_virtual_machine_factory.h @@ -33,7 +33,10 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory { MOCK_METHOD(VirtualMachine::UPtr, create_virtual_machine, - (const VirtualMachineDescription&, const SSHKeyProvider&, VMStatusMonitor&), + (const VirtualMachineDescription&, + const SSHKeyProvider&, + VMStatusMonitor&, + AvailabilityZoneManager& az_manager), (override)); MOCK_METHOD(VirtualMachine::UPtr, clone_bare_vm, @@ -43,7 +46,8 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory const std::string&, const VMImage&, const SSHKeyProvider&, - VMStatusMonitor&), + VMStatusMonitor&, + AvailabilityZoneManager& az_manager), (override)); MOCK_METHOD(void, remove_resources_for, (const std::string&), (override)); diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index df0f8075fa..8095a6c1af 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -27,6 +27,8 @@ #include "tests/mock_status_monitor.h" #include "tests/mock_virtual_machine.h" #include "tests/path.h" +#include "tests/stub_availability_zone.h" +#include "tests/stub_availability_zone_manager.h" #include "tests/stub_process_factory.h" #include "tests/stub_ssh_key_provider.h" #include "tests/stub_status_monitor.h" @@ -80,6 +82,7 @@ struct QemuBackend : public mpt::TestWithMockedBinPath mp::MemorySize{"3M"}, mp::MemorySize{}, // not used "pied-piper-valley", + "zone1", "", {}, "", @@ -96,6 +99,8 @@ struct QemuBackend : public mpt::TestWithMockedBinPath const std::string subnet{"192.168.64"}; mpt::StubSSHKeyProvider key_provider{}; mpt::StubVMStatusMonitor stub_monitor{}; + mpt::StubAvailabilityZoneManager az_manager{}; + mpt::StubAvailabilityZone zone{}; mpt::MockProcessFactory::Callback handle_external_process_calls = [](mpt::MockProcess* process) { // Have "qemu-img snapshot" return a string with the suspend tag in it @@ -195,7 +200,7 @@ TEST_F(QemuBackend, creates_in_off_state) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off)); } @@ -207,7 +212,7 @@ TEST_F(QemuBackend, machine_in_off_state_handles_shutdown) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off)); machine->shutdown(); @@ -225,7 +230,7 @@ TEST_F(QemuBackend, machine_start_shutdown_sends_monitoring_events) process_factory->register_callback(handle_qemu_system); - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, persist_state_for(_, _)); EXPECT_CALL(mock_monitor, on_resume()); @@ -249,7 +254,7 @@ TEST_F(QemuBackend, machine_start_suspend_sends_monitoring_event) process_factory->register_callback(handle_qemu_system); - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, persist_state_for(_, _)); EXPECT_CALL(mock_monitor, on_resume()); @@ -279,7 +284,7 @@ TEST_F(QemuBackend, throws_when_shutdown_while_starting) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); ASSERT_EQ(machine->state, mp::VirtualMachine::State::starting); @@ -319,7 +324,7 @@ TEST_F(QemuBackend, throws_on_shutdown_timeout) mpt::StubVMStatusMonitor stub_monitor; mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); @@ -355,7 +360,7 @@ TEST_F(QemuBackend, includes_error_when_shutdown_while_starting) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); // we need this so that Process signals get connected to their handlers EXPECT_EQ(machine->state, mp::VirtualMachine::State::starting); @@ -393,7 +398,7 @@ TEST_F(QemuBackend, machine_unknown_state_properly_shuts_down) process_factory->register_callback(handle_qemu_system); - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); EXPECT_CALL(mock_monitor, persist_state_for(_, _)); EXPECT_CALL(mock_monitor, on_resume()); @@ -418,7 +423,7 @@ TEST_F(QemuBackend, suspendedStateNoForceShutdownThrows) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::suspended; @@ -438,7 +443,7 @@ TEST_F(QemuBackend, suspendingStateNoForceShutdownThrows) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::suspending; @@ -460,7 +465,7 @@ TEST_F(QemuBackend, startingStateNoForceShutdownThrows) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::starting; @@ -504,7 +509,7 @@ TEST_F(QemuBackend, forceShutdownKillsProcessAndLogs) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); // we need this so that Process signals get connected to their handlers @@ -529,7 +534,7 @@ TEST_F(QemuBackend, forceShutdownNoProcessLogs) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::unknown; @@ -560,7 +565,7 @@ TEST_F(QemuBackend, forceShutdownSuspendDeletesSuspendImageAndOffState) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::suspended; machine->shutdown(mp::VirtualMachine::ShutdownPolicy::Poweroff); @@ -585,7 +590,7 @@ TEST_F(QemuBackend, forceShutdownSuspendedStateButNoSuspensionSnapshotInImage) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::suspended; machine->shutdown(mp::VirtualMachine::ShutdownPolicy::Poweroff); @@ -615,7 +620,7 @@ TEST_F(QemuBackend, forceShutdownRunningStateButWithSuspensionSnapshotInImage) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->state = mp::VirtualMachine::State::running; machine->shutdown(mp::VirtualMachine::ShutdownPolicy::Poweroff); @@ -636,7 +641,7 @@ TEST_F(QemuBackend, verify_dnsmasq_qemuimg_and_qemu_processes_created) auto factory = mpt::StubProcessFactory::Inject(); mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); machine->state = mp::VirtualMachine::State::running; @@ -669,7 +674,7 @@ TEST_F(QemuBackend, verify_some_common_qemu_arguments) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); machine->state = mp::VirtualMachine::State::running; @@ -694,7 +699,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); machine->start(); machine->state = mp::VirtualMachine::State::running; @@ -725,7 +730,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image_uses_metad mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); machine->start(); machine->state = mp::VirtualMachine::State::running; @@ -765,7 +770,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_from_metadata_are_used) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor, az_manager); machine->start(); machine->state = mp::VirtualMachine::State::running; @@ -891,6 +896,7 @@ TEST_F(QemuBackend, ssh_hostname_returns_expected_value) &mock_qemu_platform, stub_monitor, key_provider, + zone, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -909,6 +915,7 @@ TEST_F(QemuBackend, gets_management_ip) &mock_qemu_platform, stub_monitor, key_provider, + zone, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -926,6 +933,7 @@ TEST_F(QemuBackend, fails_to_get_management_ip_if_dnsmasq_does_not_return_an_ip) &mock_qemu_platform, stub_monitor, key_provider, + zone, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -943,6 +951,7 @@ TEST_F(QemuBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) &mock_qemu_platform, stub_monitor, key_provider, + zone, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -969,11 +978,14 @@ TEST_F(QemuBackend, logsErrorOnFailureToConvertToQcow2V3UponConstruction) logger_scope.mock_logger->screen_logs(mpl::Level::error); logger_scope.mock_logger->expect_log(mpl::Level::error, "Failed to amend image to QCOW2 v3"); - mp::QemuVirtualMachine machine{default_description, - &mock_qemu_platform, - stub_monitor, - key_provider, - instance_dir.path()}; + mp::QemuVirtualMachine machine{ + default_description, + &mock_qemu_platform, + stub_monitor, + key_provider, + zone, + instance_dir.path(), + }; } struct MockQemuVM : public mpt::MockVirtualMachineT @@ -987,7 +999,7 @@ struct MockQemuVM : public mpt::MockVirtualMachineT TEST_F(QemuBackend, dropsSSHSessionWhenStopping) { - NiceMock machine{"mock-qemu-vm", key_provider}; + NiceMock machine{"mock-qemu-vm", key_provider, zone}; machine.state = multipass::VirtualMachine::State::running; EXPECT_CALL(machine, drop_ssh_session()); @@ -998,28 +1010,32 @@ TEST_F(QemuBackend, dropsSSHSessionWhenStopping) TEST_F(QemuBackend, supportsSnapshots) { - MockQemuVM vm{"asdf", key_provider}; + MockQemuVM vm{"asdf", key_provider, zone}; EXPECT_NO_THROW(vm.require_snapshots_support()); } TEST_F(QemuBackend, createsQemuSnapshotsFromSpecs) { - MockQemuVM machine{"mock-qemu-vm", key_provider}; + MockQemuVM machine{"mock-qemu-vm", key_provider, zone}; auto snapshot_name = "elvis"; auto snapshot_comment = "has left the building"; auto instance_id = "vm1"; - const mp::VMSpecs specs{2, - mp::MemorySize{"3.21G"}, - mp::MemorySize{"4.32M"}, - "00:00:00:00:00:00", - {{"eth18", "18:18:18:18:18:18", true}}, - "asdf", - mp::VirtualMachine::State::stopped, - {}, - false, - {}}; + const mp::VMSpecs specs{ + 2, + mp::MemorySize{"3.21G"}, + mp::MemorySize{"4.32M"}, + "00:00:00:00:00:00", + {{"eth18", "18:18:18:18:18:18", true}}, + "asdf", + mp::VirtualMachine::State::stopped, + {}, + false, + {}, + 0, + "zone1", + }; auto snapshot = machine.make_specific_snapshot(snapshot_name, snapshot_comment, instance_id, specs, nullptr); EXPECT_EQ(snapshot->get_name(), snapshot_name); EXPECT_EQ(snapshot->get_comment(), snapshot_comment); @@ -1033,7 +1049,7 @@ TEST_F(QemuBackend, createsQemuSnapshotsFromSpecs) TEST_F(QemuBackend, createsQemuSnapshotsFromJsonFile) { - MockQemuVM machine{"mock-qemu-vm", key_provider}; + MockQemuVM machine{"mock-qemu-vm", key_provider, zone}; const auto parent = std::make_shared(); EXPECT_CALL(machine, get_snapshot(2)).WillOnce(Return(parent)); @@ -1153,7 +1169,7 @@ TEST_F(QemuBackend, addNetworkInterface) mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor, az_manager); EXPECT_NO_THROW(machine->add_network_interface(0, "", {"", "", true})); } @@ -1196,12 +1212,15 @@ TEST_F(QemuBackend, removeAllSnapshotsFromTheImage) mpt::StubVMStatusMonitor stub_monitor; mp::QemuVirtualMachineFactory backend{data_dir.path()}; - const mp::QemuVirtualMachine machine{default_description, - mock_qemu_platform.get(), - stub_monitor, - key_provider, - instance_dir.path(), - true}; + const mp::QemuVirtualMachine machine{ + default_description, + mock_qemu_platform.get(), + stub_monitor, + key_provider, + zone, + instance_dir.path(), + true, + }; const std::vector processes = process_factory->process_list(); @@ -1236,7 +1255,7 @@ TEST_F(QemuBackend, createVmAndCloneInstanceDirData) // create the file with the parent directory in place std::ofstream{src_img_file_path}; constexpr auto* dest_vm_name = "dummy_dest_name"; - EXPECT_TRUE(backend.clone_bare_vm({}, {}, src_vm_name, dest_vm_name, {}, key_provider, stub_monitor)); + EXPECT_TRUE(backend.clone_bare_vm({}, {}, src_vm_name, dest_vm_name, {}, key_provider, stub_monitor, az_manager)); const fs::path dest_img_file_path = instances_dir / dest_vm_name / "dummy.img"; EXPECT_TRUE(fs::exists(dest_img_file_path)); diff --git a/tests/qemu/test_qemu_mount_handler.cpp b/tests/qemu/test_qemu_mount_handler.cpp index cf790bde4e..d7d3213a4a 100644 --- a/tests/qemu/test_qemu_mount_handler.cpp +++ b/tests/qemu/test_qemu_mount_handler.cpp @@ -22,6 +22,7 @@ #include "tests/mock_ssh_process_exit_status.h" #include "tests/mock_ssh_test_fixture.h" #include "tests/mock_virtual_machine.h" +#include "tests/stub_availability_zone.h" #include "tests/stub_ssh_key_provider.h" #include "qemu_mount_handler.h" @@ -38,8 +39,12 @@ namespace { struct MockQemuVirtualMachine : mpt::MockVirtualMachineT { - explicit MockQemuVirtualMachine(const std::string& name) - : mpt::MockVirtualMachineT{name, mpt::StubSSHKeyProvider{}} + explicit MockQemuVirtualMachine(const std::string& name, mp::AvailabilityZone& zone) + : mpt::MockVirtualMachineT{ + name, + mpt::StubSSHKeyProvider{}, + zone, + } { } @@ -142,7 +147,8 @@ struct QemuMountHandlerTest : public ::Test mpt::MockServerReaderWriter server; mpt::MockSSHTestFixture mock_ssh_test_fixture; mpt::ExitStatusMock exit_status_mock; - NiceMock vm{"my_instance"}; + mpt::StubAvailabilityZone zone{}; + NiceMock vm{"my_instance", zone}; mp::QemuVirtualMachine::MountArgs mount_args; CommandOutputs command_outputs{ {"echo $PWD/target", {"/home/ubuntu/target"}}, diff --git a/tests/qemu/test_qemu_snapshot.cpp b/tests/qemu/test_qemu_snapshot.cpp index 48d0dc8db8..5d107e67df 100644 --- a/tests/qemu/test_qemu_snapshot.cpp +++ b/tests/qemu/test_qemu_snapshot.cpp @@ -22,6 +22,7 @@ #include "tests/mock_snapshot.h" #include "tests/mock_virtual_machine.h" #include "tests/path.h" +#include "tests/stub_availability_zone.h" #include "tests/stub_ssh_key_provider.h" #include @@ -91,7 +92,8 @@ struct TestQemuSnapshot : public Test }(); mpt::StubSSHKeyProvider key_provider{}; - NiceMock> vm{"qemu-vm", key_provider}; + mpt::StubAvailabilityZone zone{}; + NiceMock> vm{"qemu-vm", key_provider, zone}; ArgsMatcher list_args_matcher = ElementsAre("snapshot", "-l", desc.image.image_path); const mpt::MockCloudInitFileOps::GuardedMock mock_cloud_init_file_ops_injection = mpt::MockCloudInitFileOps::inject(); @@ -111,8 +113,22 @@ struct TestQemuSnapshot : public Test metadata["meta"] = "data"; return metadata; }(); - - return mp::VMSpecs{cpus, mem_size, disk_space, "mac", extra_interfaces, "", state, mounts, false, metadata}; + const auto zone = "zone1"; + + return mp::VMSpecs{ + cpus, + mem_size, + disk_space, + "mac", + extra_interfaces, + "", + state, + mounts, + false, + metadata, + 0, + zone, + }; }(); }; @@ -125,7 +141,7 @@ TEST_F(TestQemuSnapshot, initializesBaseProperties) const auto parent = std::make_shared(); auto desc = mp::VirtualMachineDescription{}; - auto vm = NiceMock>{"qemu-vm", key_provider}; + auto vm = NiceMock>{"qemu-vm", key_provider, zone}; const auto snapshot = mp::QemuSnapshot{name, comment, instance_id, parent, specs, vm, desc}; EXPECT_EQ(snapshot.get_name(), name); diff --git a/tests/qemu/test_qemu_vm_process_spec.cpp b/tests/qemu/test_qemu_vm_process_spec.cpp index 50af4fa9ea..8fec33ee09 100644 --- a/tests/qemu/test_qemu_vm_process_spec.cpp +++ b/tests/qemu/test_qemu_vm_process_spec.cpp @@ -35,6 +35,7 @@ struct TestQemuVMProcessSpec : public Test mp::MemorySize{"3G"} /*mem_size*/, mp::MemorySize{"4G"} /*disk_space*/, "vm_name", + "zone1", "00:11:22:33:44:55", {}, "ssh_username", diff --git a/tests/stub_availability_zone.h b/tests/stub_availability_zone.h new file mode 100644 index 0000000000..4aedac1bd8 --- /dev/null +++ b/tests/stub_availability_zone.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MULTIPASS_STUB_AVAILABILITY_ZONE_H +#define MULTIPASS_STUB_AVAILABILITY_ZONE_H + +#include "multipass/availability_zone.h" + +namespace multipass +{ +namespace test +{ +struct StubAvailabilityZone final : public AvailabilityZone +{ + static AvailabilityZone& instance() + { + static StubAvailabilityZone zone{}; + return zone; + } + + StubAvailabilityZone() + { + } + + const std::string& get_name() const override + { + static std::string name{"zone1"}; + return name; + } + + const std::string& get_subnet() const override + { + static std::string subnet{"192.168.123"}; + return subnet; + } + + bool is_available() const override + { + return true; + } + + void set_available(bool new_available) override + { + } + + void add_vm(VirtualMachine& vm) override + { + } + + void remove_vm(VirtualMachine& vm) override + { + } +}; +} // namespace test +} // namespace multipass +#endif // MULTIPASS_STUB_AVAILABILITY_ZONE_H diff --git a/tests/stub_availability_zone_manager.h b/tests/stub_availability_zone_manager.h new file mode 100644 index 0000000000..88d482679f --- /dev/null +++ b/tests/stub_availability_zone_manager.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MULTIPASS_STUB_AVAILABILITY_ZONE_MANAGER_H +#define MULTIPASS_STUB_AVAILABILITY_ZONE_MANAGER_H + +#include "multipass/availability_zone_manager.h" +#include "stub_availability_zone.h" + +namespace multipass +{ +namespace test +{ +struct StubAvailabilityZoneManager final : public AvailabilityZoneManager +{ + StubAvailabilityZoneManager() + { + } + + AvailabilityZone& get_zone(const std::string& name) override + { + return zone; + } + std::string get_automatic_zone_name() override + { + return "zone1"; + } + std::vector> get_zones() override + { + return {zone}; + } + + std::string get_default_zone_name() const override + { + return "zone1"; + } + +private: + StubAvailabilityZone zone{}; +}; +} // namespace test +} // namespace multipass +#endif // MULTIPASS_STUB_AVAILABILITY_ZONE_MANAGER_H diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index f74d716eab..3b9d7da3db 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -18,6 +18,7 @@ #ifndef MULTIPASS_STUB_VIRTUAL_MACHINE_H #define MULTIPASS_STUB_VIRTUAL_MACHINE_H +#include "stub_availability_zone.h" #include "stub_mount_handler.h" #include "stub_snapshot.h" #include "temp_dir.h" @@ -39,7 +40,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine } StubVirtualMachine(const std::string& name, std::unique_ptr tmp_dir) - : VirtualMachine{name, tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + : VirtualMachine{name, StubAvailabilityZone::instance(), tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} { } @@ -55,6 +56,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } + void make_available(bool) override + { + } + multipass::VirtualMachine::State current_state() override { return multipass::VirtualMachine::State::off; diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index a692fc8fbd..bb062a052a 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -41,7 +41,8 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription&, const SSHKeyProvider&, - VMStatusMonitor&) override + VMStatusMonitor&, + AvailabilityZoneManager& az_manager) override { return std::make_unique(); } diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index ac73fff4c1..b2bab2cfdc 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -34,6 +34,7 @@ #include "mock_settings.h" #include "mock_utils.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone_manager.h" #include "stub_terminal.h" #include "src/daemon/daemon.h" @@ -606,6 +607,8 @@ struct DaemonAliasTestsuite EXPECT_CALL(mock_settings, register_handler).WillRepeatedly(Return(nullptr)); EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); EXPECT_CALL(mock_settings, get(Eq(mp::winterm_key))).WillRepeatedly(Return("none")); + + config_builder.az_manager = std::make_unique(); } mpt::MockSettings::GuardedMock mock_settings_injection = mpt::MockSettings::inject(); diff --git a/tests/test_base_snapshot.cpp b/tests/test_base_snapshot.cpp index c6bead9d1c..19db52fdb7 100644 --- a/tests/test_base_snapshot.cpp +++ b/tests/test_base_snapshot.cpp @@ -22,6 +22,7 @@ #include "mock_json_utils.h" #include "mock_virtual_machine.h" #include "path.h" +#include "stub_availability_zone.h" #include #include @@ -130,7 +131,8 @@ struct TestBaseSnapshot : public Test static constexpr auto* test_json_filename = "test_snapshot.json"; mp::VMSpecs specs = stub_specs(); mp::VirtualMachineDescription desc = stub_desc(); - NiceMock vm{"a-vm"}; + mpt::StubAvailabilityZone zone{}; + NiceMock vm{"a-vm", zone}; const mpt::MockCloudInitFileOps::GuardedMock mock_cloud_init_file_ops_injection = mpt::MockCloudInitFileOps::inject(); const mpt::MockJsonUtils::GuardedMock mock_json_utils_injection = mpt::MockJsonUtils::inject(); diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 276c1d93e3..191e987798 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -24,6 +24,7 @@ #include "mock_ssh_test_fixture.h" #include "mock_utils.h" #include "mock_virtual_machine.h" +#include "stub_availability_zone.h" #include "temp_dir.h" #include @@ -119,12 +120,15 @@ struct MockBaseVirtualMachine : public mpt::MockVirtualMachineT()} + + StubBaseVirtualMachine(mp::AvailabilityZone& zone, St s = St::off) + : StubBaseVirtualMachine{s, zone, std::make_unique()} { } - StubBaseVirtualMachine(St s, std::unique_ptr tmp_dir) - : mp::BaseVirtualMachine{s, "stub", mpt::StubSSHKeyProvider{}, tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + StubBaseVirtualMachine(St s, mp::AvailabilityZone& zone, std::unique_ptr tmp_dir) + : mp::BaseVirtualMachine{s, "stub", mpt::StubSSHKeyProvider{}, zone, tmp_dir->path()}, + tmp_dir{std::move(tmp_dir)} { } @@ -250,7 +254,8 @@ struct BaseVM : public Test mpt::MockSSHTestFixture mock_ssh_test_fixture; const mpt::DummyKeyProvider key_provider{"keeper of the seven keys"}; - NiceMock vm{"mock-vm", key_provider}; + mpt::StubAvailabilityZone zone{}; + NiceMock vm{"mock-vm", key_provider, zone}; std::vector> snapshot_album; QString head_path = vm.tmp_dir->filePath(head_filename); QString count_path = vm.tmp_dir->filePath(count_filename); @@ -299,7 +304,7 @@ TEST_F(BaseVM, get_all_ipv4_works_when_instance_is_off) TEST_F(BaseVM, add_network_interface_throws) { - StubBaseVirtualMachine base_vm(St::off); + StubBaseVirtualMachine base_vm(zone, St::off); MP_EXPECT_THROW_THAT(base_vm.add_network_interface(1, "", {"eth1", "52:54:00:00:00:00", true}), mp::NotImplementedOnThisBackendException, @@ -389,7 +394,7 @@ TEST_F(BaseVM, takesSnapshots) TEST_F(BaseVM, takeSnasphotThrowsIfSpecificSnapshotNotOverridden) { - StubBaseVirtualMachine stub{}; + StubBaseVirtualMachine stub{zone}; MP_EXPECT_THROW_THAT(stub.take_snapshot({}, "stub-snap", ""), mp::NotImplementedOnThisBackendException, mpt::match_what(HasSubstr("snapshots"))); @@ -741,16 +746,20 @@ TEST_F(BaseVM, restoresSnapshots) QJsonObject metadata{}; metadata["meta"] = "data"; - const mp::VMSpecs original_specs{2, - mp::MemorySize{"3.5G"}, - mp::MemorySize{"15G"}, - "12:12:12:12:12:12", - {}, - "user", - St::off, - {{"dst", mount}}, - false, - metadata}; + const mp::VMSpecs original_specs{ + 2, + mp::MemorySize{"3.5G"}, + mp::MemorySize{"15G"}, + "12:12:12:12:12:12", + {}, + "user", + St::off, + {{"dst", mount}}, + false, + metadata, + 0, + "zone1", + }; const auto* snapshot_name = "shoot"; vm.take_snapshot(original_specs, snapshot_name, ""); @@ -838,7 +847,7 @@ TEST_F(BaseVM, usesRestoredSnapshotAsParentForNewSnapshots) TEST_F(BaseVM, loadSnasphotThrowsIfSnapshotsNotImplemented) { - StubBaseVirtualMachine stub{}; + StubBaseVirtualMachine stub{zone}; mpt::make_file_with_content(stub.tmp_dir->filePath("0001.snapshot.json"), "whatever-content"); MP_EXPECT_THROW_THAT(stub.load_snapshots(), mp::NotImplementedOnThisBackendException, @@ -1178,16 +1187,20 @@ TEST_F(BaseVM, rollsbackFailedRestore) { mock_snapshotting(); - const mp::VMSpecs original_specs{1, - mp::MemorySize{"1.5G"}, - mp::MemorySize{"4G"}, - "ab:ab:ab:ab:ab:ab", - {}, - "me", - St::off, - {}, - false, - {}}; + const mp::VMSpecs original_specs{ + 1, + mp::MemorySize{"1.5G"}, + mp::MemorySize{"4G"}, + "ab:ab:ab:ab:ab:ab", + {}, + "me", + St::off, + {}, + false, + {}, + 0, + "zone1", + }; vm.take_snapshot(original_specs, "", ""); diff --git a/tests/test_base_virtual_machine_factory.cpp b/tests/test_base_virtual_machine_factory.cpp index 02e773887b..5339ed31ae 100644 --- a/tests/test_base_virtual_machine_factory.cpp +++ b/tests/test_base_virtual_machine_factory.cpp @@ -49,7 +49,10 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory MOCK_METHOD(mp::VirtualMachine::UPtr, create_virtual_machine, - (const mp::VirtualMachineDescription&, const mp::SSHKeyProvider&, mp::VMStatusMonitor&), + (const mp::VirtualMachineDescription&, + const mp::SSHKeyProvider&, + mp::VMStatusMonitor&, + mp::AvailabilityZoneManager& az_manager), (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)); @@ -135,6 +138,7 @@ TEST_F(BaseFactory, creates_cloud_init_iso_image) mp::MemorySize{"3M"}, mp::MemorySize{}, // not used name, + "zone1", "00:16:3e:fe:f2:b9", {}, "yoda", diff --git a/tests/test_blueprint_provider.cpp b/tests/test_blueprint_provider.cpp index 17ad305b88..9bd587243f 100644 --- a/tests/test_blueprint_provider.cpp +++ b/tests/test_blueprint_provider.cpp @@ -76,7 +76,7 @@ TEST_F(VMBlueprintProvider, fetchBlueprintForUnknownBlueprintThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -96,7 +96,7 @@ TEST_F(VMBlueprintProvider, invalidImageSchemeThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -110,7 +110,7 @@ TEST_F(VMBlueprintProvider, invalidMinCoresThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -124,7 +124,7 @@ TEST_F(VMBlueprintProvider, invalidMinMemorySizeThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -138,7 +138,7 @@ TEST_F(VMBlueprintProvider, invalidMinDiskSpaceThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -152,7 +152,7 @@ TEST_F(VMBlueprintProvider, invalidAliasDefinitionThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -166,7 +166,7 @@ TEST_F(VMBlueprintProvider, fetchTestBlueprint1ReturnsExpectedInfo) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -186,7 +186,7 @@ TEST_F(VMBlueprintProvider, fetchTestBlueprint1ReturnsExpectedAliasesAndWorkspac mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData launch_data; @@ -213,7 +213,7 @@ TEST_F(VMBlueprintProvider, fetchTestBlueprint2ReturnsExpectedInfo) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData launch_data; @@ -278,7 +278,7 @@ TEST_F(VMBlueprintProvider, invalidCloudInitThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; const std::string blueprint{"invalid-cloud-init-blueprint"}; @@ -294,7 +294,7 @@ TEST_F(VMBlueprintProvider, givenCoresLessThanMinimumThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{1, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{1, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -308,7 +308,7 @@ TEST_F(VMBlueprintProvider, givenMemLessThanMinimumThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, mp::MemorySize{"1G"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, mp::MemorySize{"1G"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -322,7 +322,7 @@ TEST_F(VMBlueprintProvider, givenDiskSpaceLessThanMinimumThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, mp::MemorySize{"20G"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, mp::MemorySize{"20G"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -336,8 +336,8 @@ TEST_F(VMBlueprintProvider, higherOptionsIsNotOverridden) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{ - 4, mp::MemorySize{"4G"}, mp::MemorySize{"50G"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription + vm_desc{4, mp::MemorySize{"4G"}, mp::MemorySize{"50G"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -582,7 +582,7 @@ TEST_F(VMBlueprintProvider, noImageDefinedReturnsDefault) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -596,7 +596,7 @@ TEST_F(VMBlueprintProvider, nameMismatchThrows) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -812,7 +812,7 @@ TEST_P(VMBlueprintFileLaunchFromFile, loadsFile) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &mock_url_downloader, cache_dir.path(), default_ttl, "multivacs"}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -842,7 +842,7 @@ TEST_F(VMBlueprintFileLaunch, mergesBlueprintVendorData) vendor_data["system_info"]["default_user"]["name"] = "ubuntu"; vendor_data["growpart"]["devices"].push_back("/dev/vdb1"); - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, vendor_data, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, vendor_data, {}}; mp::ClientLaunchData dummy_data; @@ -874,7 +874,7 @@ TEST_F(VMBlueprintFileLaunch, failsMergeVmBlueprintVendorDataDifferentTypes) YAML::Node vendor_data; vendor_data["runcmd"] = "echo 123"; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, vendor_data, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, vendor_data, {}}; mp::ClientLaunchData dummy_data; @@ -896,7 +896,7 @@ TEST_F(VMBlueprintFileLaunch, failsMergeVmBlueprintVendorDataScalarValues) YAML::Node vendor_data; vendor_data["system_info"]["default_user"]["shell"] = "/bin/fish"; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, vendor_data, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, vendor_data, {}}; mp::ClientLaunchData dummy_data; @@ -913,7 +913,7 @@ TEST_F(VMBlueprintFileLaunch, failsWithNonexistentFile) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -929,7 +929,7 @@ TEST_F(VMBlueprintFileLaunch, fileLoadfailsWithInvalidHostName) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; @@ -966,7 +966,7 @@ TEST_F(VMBlueprintFileLaunch, fileLoadfailsWithNoUrl) mp::DefaultVMBlueprintProvider blueprint_provider{blueprints_zip_url, &url_downloader, cache_dir.path(), default_ttl, "microvac"}; - mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; + mp::VirtualMachineDescription vm_desc{0, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}; mp::ClientLaunchData dummy_data; diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 8ceaf74de1..1841f7a6b7 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -38,6 +38,8 @@ #include "mock_vm_blueprint_provider.h" #include "mock_vm_image_vault.h" #include "path.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" #include "stub_virtual_machine.h" #include "tracking_url_downloader.h" @@ -121,6 +123,8 @@ struct Daemon : public mpt::DaemonTestFixture EXPECT_CALL(mock_platform, remove_alias_script(_)).WillRepeatedly(Return()); EXPECT_CALL(mock_platform, setup_permission_inheritance(_)).Times(AnyNumber()).WillRepeatedly(Return()); EXPECT_CALL(mock_platform, bridge_nomenclature).Times(AnyNumber()).WillRepeatedly(Return("notabridge")); + + config_builder.az_manager = std::make_unique(); } void SetUp() override @@ -149,6 +153,8 @@ struct Daemon : public mpt::DaemonTestFixture const mpt::MockJsonUtils::GuardedMock mock_json_utils_injection = mpt::MockJsonUtils::inject(); mpt::MockJsonUtils& mock_json_utils = *mock_json_utils_injection.first; + + mpt::StubAvailabilityZone zone{}; }; TEST_F(Daemon, receives_commands_and_calls_corresponding_slot) @@ -1835,9 +1841,9 @@ TEST_F(Daemon, skips_over_instance_ghosts_in_db) // which will have been sometim auto mock_factory = use_a_mock_vm_factory(); EXPECT_CALL(*mock_factory, create_virtual_machine).Times(0); - EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, id1), _, _)) + EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, id1), _, _, _)) .Times(1); - EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, id2), _, _)) + EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, id2), _, _, _)) .Times(1); mp::Daemon daemon{config_builder.build()}; @@ -1874,11 +1880,11 @@ TEST_F(Daemon, ctor_drops_removed_instances) config_builder.vault = std::move(mock_image_vault); auto mock_factory = use_a_mock_vm_factory(); - EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, stayed), _, _)) + EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, stayed), _, _, _)) .Times(1) .WillRepeatedly( WithArg<0>([](const auto& desc) { return std::make_unique(desc.vm_name); })); - EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, gone), _, _)) + EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, gone), _, _, _)) .Times(0); EXPECT_CALL(mock_json_utils, write_json(_, Eq(filename))) @@ -1907,7 +1913,7 @@ TEST_P(ListIP, lists_with_ip) mp::Daemon daemon{config_builder.build()}; - auto instance_ptr = std::make_unique>("mock"); + auto instance_ptr = std::make_unique>("mock", zone); EXPECT_CALL(*mock_factory, create_virtual_machine).WillRepeatedly([&instance_ptr](auto&&...) { return std::move(instance_ptr); }); @@ -2035,9 +2041,9 @@ TEST_F(Daemon, releases_macs_of_purged_instances_but_keeps_the_rest) return Field(&mp::VirtualMachineDescription::extra_interfaces, Contains(Field(&mp::NetworkInterface::mac_address, mac))); }; - EXPECT_CALL(*mock_factory, create_virtual_machine(mac_matcher(mac1), _, _)).Times(1); - EXPECT_CALL(*mock_factory, create_virtual_machine(mac_matcher(mac2), _, _)).Times(1); - EXPECT_CALL(*mock_factory, create_virtual_machine(mac_matcher(mac3), _, _)).Times(2); // this one gets reused + EXPECT_CALL(*mock_factory, create_virtual_machine(mac_matcher(mac1), _, _, _)).Times(1); + EXPECT_CALL(*mock_factory, create_virtual_machine(mac_matcher(mac2), _, _, _)).Times(1); + EXPECT_CALL(*mock_factory, create_virtual_machine(mac_matcher(mac3), _, _, _)).Times(2); // this one gets reused send_command({"launch", "--network", fmt::format("name=eth0,mac={}", mac1), "--name", "vm1"}); send_command({"launch", "--network", fmt::format("name=eth0,mac={}", mac2), "--name", "vm2"}); @@ -2056,7 +2062,7 @@ TEST_P(DaemonLaunchTimeoutValueTestSuite, uses_correct_launch_timeout) { auto mock_factory = use_a_mock_vm_factory(); auto mock_blueprint_provider = std::make_unique>(); - auto instance_ptr = std::make_unique>("mock"); + auto instance_ptr = std::make_unique>("mock", zone); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce([&instance_ptr](auto&&...) { return std::move(instance_ptr); }); @@ -2392,7 +2398,7 @@ TEST_F(Daemon, add_bridged_interface_works) auto mock_factory = use_a_mock_vm_factory(); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); auto logger_scope = mpt::MockLogger::inject(); logger_scope.mock_logger->screen_logs(mpl::Level::debug); @@ -2416,7 +2422,7 @@ TEST_F(Daemon, add_bridged_interface_warns_and_noop_if_already_bridged) auto mock_factory = use_a_mock_vm_factory(); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); auto logger_scope = mpt::MockLogger::inject(); logger_scope.mock_logger->screen_logs(mpl::Level::warning); @@ -2438,7 +2444,7 @@ TEST_F(Daemon, add_bridged_interface_honors_prepared_bridge) auto mock_factory = use_a_mock_vm_factory(); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); std::vector net_info{{if_name, "Ethernet", "A regular adapter", {}, false}}; EXPECT_CALL(*mock_factory, networks).WillOnce(Return(net_info)); @@ -2455,7 +2461,7 @@ TEST_F(Daemon, add_bridged_interface_throws_if_backend_throws) auto mock_factory = use_a_mock_vm_factory(); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); auto logger_scope = mpt::MockLogger::inject(); logger_scope.mock_logger->screen_logs(mpl::Level::debug); @@ -2478,7 +2484,7 @@ TEST_F(Daemon, add_bridged_interface_throws_on_bad_bridged_network_setting) auto mock_factory = use_a_mock_vm_factory(); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); std::vector net_info{{"eth9", "Ethernet", "An invalid network adapter", {}, false}}; EXPECT_CALL(*mock_factory, networks).WillOnce(Return(net_info)); @@ -2498,7 +2504,7 @@ TEST_F(Daemon, add_bridged_interface_throws_if_needs_authorization) auto mock_factory = use_a_mock_vm_factory(); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); std::vector net_info{{"eth8", "Ethernet", "A network adapter", {}, true}}; EXPECT_CALL(*mock_factory, networks).WillOnce(Return(net_info)); @@ -2539,7 +2545,7 @@ TEST_P(DaemonIsBridged, is_bridged_works) auto mock_factory = use_a_mock_vm_factory(); EXPECT_CALL(*mock_factory, networks).WillOnce(Return(host_nets)); mpt::MockDaemon daemon{config_builder.build()}; - auto instance_ptr = std::make_shared>(instance_name); + auto instance_ptr = std::make_shared>(instance_name, zone); EXPECT_EQ(daemon.test_is_bridged(instance_name, specs), result); } diff --git a/tests/test_daemon_authenticate.cpp b/tests/test_daemon_authenticate.cpp index fbbc172fe7..8f70288d84 100644 --- a/tests/test_daemon_authenticate.cpp +++ b/tests/test_daemon_authenticate.cpp @@ -22,6 +22,7 @@ #include "mock_server_reader_writer.h" #include "mock_settings.h" #include "mock_utils.h" +#include "stub_availability_zone_manager.h" #include @@ -42,6 +43,8 @@ struct TestDaemonAuthenticate : public mpt::DaemonTestFixture { EXPECT_CALL(mock_settings, register_handler(_)).WillRepeatedly(Return(nullptr)); EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); + + config_builder.az_manager = std::make_unique(); } mpt::MockUtils::GuardedMock utils_attr{mpt::MockUtils::inject()}; diff --git a/tests/test_daemon_clone.cpp b/tests/test_daemon_clone.cpp index f77f0fd615..5ccdd37231 100644 --- a/tests/test_daemon_clone.cpp +++ b/tests/test_daemon_clone.cpp @@ -22,6 +22,8 @@ #include "mock_server_reader_writer.h" #include "mock_virtual_machine.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" #include @@ -34,11 +36,12 @@ struct TestDaemonClone : public mpt::DaemonTestFixture void SetUp() override { config_builder.vault = std::make_unique>(); + config_builder.az_manager = std::make_unique(); } auto build_daemon_with_mock_instance() { - auto instance_unique_ptr = std::make_unique>(mock_src_instance_name); + auto instance_unique_ptr = std::make_unique>(mock_src_instance_name, zone); auto* instance_raw_ptr = instance_unique_ptr.get(); EXPECT_CALL(mock_factory, create_virtual_machine).WillOnce(Return(std::move(instance_unique_ptr))); @@ -53,6 +56,7 @@ struct TestDaemonClone : public mpt::DaemonTestFixture const std::string mock_src_instance_name{"real-zebraphant"}; const std::string mac_addr{"52:54:00:73:76:28"}; std::vector extra_interfaces; + mpt::StubAvailabilityZone zone{}; const mpt::MockVirtualMachineFactory& mock_factory = *use_a_mock_vm_factory(); diff --git a/tests/test_daemon_find.cpp b/tests/test_daemon_find.cpp index 04dfbedc43..0dbe2c9596 100644 --- a/tests/test_daemon_find.cpp +++ b/tests/test_daemon_find.cpp @@ -25,6 +25,7 @@ #include "mock_utils.h" #include "mock_vm_blueprint_provider.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone_manager.h" #include @@ -52,6 +53,8 @@ struct DaemonFind : public mpt::DaemonTestFixture EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); EXPECT_CALL(mock_settings, get(Eq(mp::winterm_key))).WillRepeatedly(Return("none")); ON_CALL(mock_utils, contents_of(_)).WillByDefault(Return(mpt::root_cert)); + + config_builder.az_manager = std::make_unique(); } mpt::MockPlatform::GuardedMock attr{mpt::MockPlatform::inject()}; diff --git a/tests/test_daemon_launch.cpp b/tests/test_daemon_launch.cpp index e01bff9f80..0f500289b0 100644 --- a/tests/test_daemon_launch.cpp +++ b/tests/test_daemon_launch.cpp @@ -27,6 +27,7 @@ #include "mock_virtual_machine.h" #include "mock_vm_blueprint_provider.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone_manager.h" #include "stub_virtual_machine.h" #include "stub_vm_image_vault.h" @@ -46,6 +47,8 @@ struct TestDaemonLaunch : public mpt::DaemonTestFixture EXPECT_CALL(mock_settings, register_handler).WillRepeatedly(Return(nullptr)); EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); EXPECT_CALL(mock_settings, get(Eq(mp::mounts_key))).WillRepeatedly(Return("true")); + + config_builder.az_manager = std::make_unique(); } mpt::MockPlatform::GuardedMock attr{mpt::MockPlatform::inject()}; diff --git a/tests/test_daemon_mount.cpp b/tests/test_daemon_mount.cpp index cc392d644c..16c454c34f 100644 --- a/tests/test_daemon_mount.cpp +++ b/tests/test_daemon_mount.cpp @@ -26,6 +26,8 @@ #include "mock_settings.h" #include "mock_virtual_machine.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" #include "stub_mount_handler.h" #include "temp_dir.h" #include "temp_file.h" @@ -52,6 +54,7 @@ struct TestDaemonMount : public mpt::DaemonTestFixture config_builder.vault = std::make_unique>(); mock_factory = use_a_mock_vm_factory(); + config_builder.az_manager = std::make_unique(); } std::unique_ptr mock_mount_handler{std::make_unique()}; @@ -73,6 +76,7 @@ struct TestDaemonMount : public mpt::DaemonTestFixture const mpt::MockPermissionUtils::GuardedMock mock_permission_utils_injection = mpt::MockPermissionUtils::inject(); mpt::MockPermissionUtils& mock_permission_utils = *mock_permission_utils_injection.first; + mpt::StubAvailabilityZone zone{}; }; } // namespace @@ -115,7 +119,7 @@ TEST_F(TestDaemonMount, invalidTargetPathFails) config_builder.data_directory = temp_dir->path(); EXPECT_CALL(*mock_factory, create_virtual_machine) - .WillOnce(Return(std::make_unique>(mock_instance_name))); + .WillOnce(Return(std::make_unique>(mock_instance_name, zone))); mp::Daemon daemon{config_builder.build()}; @@ -138,7 +142,7 @@ TEST_F(TestDaemonMount, mountIgnoresTrailingSlash) const auto [temp_dir, _] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); config_builder.data_directory = temp_dir->path(); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler).WillOnce(Return(std::make_unique())); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce(Return(std::move(mock_vm))); @@ -169,7 +173,7 @@ TEST_F(TestDaemonMount, skipStartMountIfInstanceIsNotRunning) EXPECT_CALL(*mock_mount_handler, activate_impl).Times(0); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, current_state).WillRepeatedly(Return(mp::VirtualMachine::State::stopped)); EXPECT_CALL(*mock_vm, make_native_mount_handler).WillOnce(Return(std::move(mock_mount_handler))); @@ -197,7 +201,7 @@ TEST_F(TestDaemonMount, startsMountIfInstanceRunning) EXPECT_CALL(*mock_mount_handler, activate_impl).Times(1); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, current_state).WillRepeatedly(Return(mp::VirtualMachine::State::running)); EXPECT_CALL(*mock_vm, make_native_mount_handler).WillOnce(Return(std::move(mock_mount_handler))); @@ -226,7 +230,7 @@ TEST_F(TestDaemonMount, mountFailsErrorMounting) auto error = "permission denied"; EXPECT_CALL(*mock_mount_handler, activate_impl).WillOnce(Throw(std::runtime_error(error))); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, current_state).WillRepeatedly(Return(mp::VirtualMachine::State::running)); EXPECT_CALL(*mock_vm, make_native_mount_handler).WillOnce(Return(std::move(mock_mount_handler))); @@ -253,7 +257,7 @@ TEST_F(TestDaemonMount, performanceMountsNotImplementedHasErrorFails) const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); config_builder.data_directory = temp_dir->path(); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler) .WillOnce(Throw(mp::NotImplementedOnThisBackendException("native mounts"))); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce(Return(std::move(mock_vm))); @@ -287,7 +291,7 @@ TEST_F(TestDaemonMount, mount_uses_resolved_source) EXPECT_CALL(*mock_file_ops, weakly_canonical).WillOnce(Return(target_path)); // mock mount_handler to check the VMMount is using target_path as its source - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler( _, diff --git a/tests/test_daemon_snapshot_restore.cpp b/tests/test_daemon_snapshot_restore.cpp index 6b83119e08..1003acdc60 100644 --- a/tests/test_daemon_snapshot_restore.cpp +++ b/tests/test_daemon_snapshot_restore.cpp @@ -25,6 +25,8 @@ #include "mock_virtual_machine.h" #include "mock_vm_image_vault.h" #include "multipass/exceptions/snapshot_exceptions.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" #include @@ -42,13 +44,14 @@ struct TestDaemonSnapshotRestoreBase : public mpt::DaemonTestFixture EXPECT_CALL(mock_settings, register_handler).WillRepeatedly(Return(nullptr)); EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); config_builder.vault = std::make_unique>(); + config_builder.az_manager = std::make_unique(); } auto build_daemon_with_mock_instance() { const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); - auto instance_ptr = std::make_unique>(mock_instance_name); + auto instance_ptr = std::make_unique>(mock_instance_name, zone); auto* ret_instance = instance_ptr.get(); EXPECT_CALL(*instance_ptr, current_state).WillRepeatedly(Return(mp::VirtualMachine::State::restarting)); @@ -75,6 +78,7 @@ struct TestDaemonSnapshotRestoreBase : public mpt::DaemonTestFixture std::vector extra_interfaces; const std::string mac_addr{"52:54:00:73:76:28"}; const std::string mock_instance_name{"real-zebraphant"}; + mpt::StubAvailabilityZone zone{}; }; struct TestDaemonSnapshot : public TestDaemonSnapshotRestoreBase diff --git a/tests/test_daemon_start.cpp b/tests/test_daemon_start.cpp index fff214a31d..84ecc38dfe 100644 --- a/tests/test_daemon_start.cpp +++ b/tests/test_daemon_start.cpp @@ -27,6 +27,8 @@ #include "mock_settings.h" #include "mock_virtual_machine.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" #include @@ -44,6 +46,8 @@ struct TestDaemonStart : public mpt::DaemonTestFixture EXPECT_CALL(mock_settings, register_handler).WillRepeatedly(Return(nullptr)); EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); EXPECT_CALL(mock_settings, get(Eq(mp::mounts_key))).WillRepeatedly(Return("true")); + + config_builder.az_manager = std::make_unique(); } const std::string mock_instance_name{"real-zebraphant"}; @@ -59,6 +63,7 @@ struct TestDaemonStart : public mpt::DaemonTestFixture const mpt::MockPermissionUtils::GuardedMock mock_permission_utils_injection = mpt::MockPermissionUtils::inject(); mpt::MockPermissionUtils& mock_permission_utils = *mock_permission_utils_injection.first; + mpt::StubAvailabilityZone zone{}; }; TEST_F(TestDaemonStart, successfulStartOkStatus) @@ -66,7 +71,7 @@ TEST_F(TestDaemonStart, successfulStartOkStatus) auto mock_factory = use_a_mock_vm_factory(); const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); - auto instance_ptr = std::make_unique>(mock_instance_name); + auto instance_ptr = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce([&instance_ptr](auto&&...) { return std::move(instance_ptr); }); @@ -100,7 +105,7 @@ TEST_F(TestDaemonStart, exitlessSshProcessExceptionDoesNotShowMessage) auto mock_factory = use_a_mock_vm_factory(); const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); - auto instance_ptr = std::make_unique>(mock_instance_name); + auto instance_ptr = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce([&instance_ptr](auto&&...) { return std::move(instance_ptr); }); @@ -133,7 +138,7 @@ TEST_F(TestDaemonStart, unknownStateDoesNotStart) auto mock_factory = use_a_mock_vm_factory(); const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); - auto instance_ptr = std::make_unique>(mock_instance_name); + auto instance_ptr = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce([&instance_ptr](auto&&...) { return std::move(instance_ptr); }); @@ -162,7 +167,7 @@ TEST_F(TestDaemonStart, suspendingStateDoesNotStartHasError) auto mock_factory = use_a_mock_vm_factory(); const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); - auto instance_ptr = std::make_unique>(mock_instance_name); + auto instance_ptr = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce([&instance_ptr](auto&&...) { return std::move(instance_ptr); }); @@ -202,7 +207,7 @@ TEST_F(TestDaemonStart, definedMountsInitializedDuringStart) auto mock_mount_handler = std::make_unique(); EXPECT_CALL(*mock_mount_handler, activate_impl).Times(1); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, wait_until_ssh_up).WillRepeatedly(Return()); EXPECT_CALL(*mock_vm, current_state).WillRepeatedly(Return(mp::VirtualMachine::State::off)); EXPECT_CALL(*mock_vm, start).Times(1); @@ -240,7 +245,7 @@ TEST_F(TestDaemonStart, removingMountOnFailedStart) auto mock_mount_handler = std::make_unique(); EXPECT_CALL(*mock_mount_handler, activate_impl).WillOnce(Throw(std::runtime_error{error})); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, wait_until_ssh_up).WillRepeatedly(Return()); EXPECT_CALL(*mock_vm, current_state).WillRepeatedly(Return(mp::VirtualMachine::State::off)); EXPECT_CALL(*mock_vm, start).Times(1); diff --git a/tests/test_daemon_suspend.cpp b/tests/test_daemon_suspend.cpp index b0dc27672a..702fb4ee4e 100644 --- a/tests/test_daemon_suspend.cpp +++ b/tests/test_daemon_suspend.cpp @@ -24,6 +24,8 @@ #include "mock_settings.h" #include "mock_virtual_machine.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" #include @@ -42,6 +44,7 @@ struct TestDaemonSuspend : public mpt::DaemonTestFixture EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); config_builder.vault = std::make_unique>(); + config_builder.az_manager = std::make_unique(); } const std::string mock_instance_name{"real-zebraphant"}; @@ -59,6 +62,7 @@ struct TestDaemonSuspend : public mpt::DaemonTestFixture const mpt::MockPermissionUtils::GuardedMock mock_permission_utils_injection = mpt::MockPermissionUtils::inject(); mpt::MockPermissionUtils& mock_permission_utils = *mock_permission_utils_injection.first; + mpt::StubAvailabilityZone zone{}; }; } // namespace @@ -74,7 +78,7 @@ TEST_F(TestDaemonSuspend, suspendNotSupportedDoesNotStopMounts) auto mock_mount_handler = std::make_unique(); EXPECT_CALL(*mock_mount_handler, deactivate_impl).Times(0); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path, _)) .WillOnce(Return(std::move(mock_mount_handler))); @@ -109,7 +113,7 @@ TEST_F(TestDaemonSuspend, suspendStopsMounts) EXPECT_CALL(*mock_mount_handler, is_active).WillOnce(Return(true)); EXPECT_CALL(*mock_mount_handler, deactivate_impl); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path, _)) .WillOnce(Return(std::move(mock_mount_handler))); diff --git a/tests/test_daemon_umount.cpp b/tests/test_daemon_umount.cpp index dbd7cfdfeb..868df22b4b 100644 --- a/tests/test_daemon_umount.cpp +++ b/tests/test_daemon_umount.cpp @@ -25,6 +25,8 @@ #include "mock_settings.h" #include "mock_virtual_machine.h" #include "mock_vm_image_vault.h" +#include "stub_availability_zone.h" +#include "stub_availability_zone_manager.h" namespace mp = multipass; namespace mpt = multipass::test; @@ -41,6 +43,7 @@ struct TestDaemonUmount : public mpt::DaemonTestFixture EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber()); config_builder.vault = std::make_unique>(); + config_builder.az_manager = std::make_unique(); mock_factory = use_a_mock_vm_factory(); } @@ -62,6 +65,7 @@ struct TestDaemonUmount : public mpt::DaemonTestFixture const mpt::MockPermissionUtils::GuardedMock mock_permission_utils_injection = mpt::MockPermissionUtils::inject(); mpt::MockPermissionUtils& mock_permission_utils = *mock_permission_utils_injection.first; + mpt::StubAvailabilityZone zone{}; }; } // namespace @@ -97,7 +101,7 @@ TEST_F(TestDaemonUmount, noTargetsUnmountsAll) EXPECT_CALL(*mock_mount_handler, deactivate_impl(false)); EXPECT_CALL(*mock_mount_handler2, deactivate_impl(false)); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path, _)) .WillOnce(Return(std::move(mock_mount_handler))); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path + "2", _)) @@ -137,7 +141,7 @@ TEST_F(TestDaemonUmount, umountWithTargetOnlyStopsItsHandlers) EXPECT_CALL(*mock_mount_handler2, deactivate_impl(false)).Times(0); EXPECT_CALL(*mock_mount_handler3, deactivate_impl(false)); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path, _)) .WillOnce(Return(std::move(mock_mount_handler))); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path + "2", _)) @@ -169,7 +173,7 @@ TEST_F(TestDaemonUmount, mountNotFound) config_builder.data_directory = temp_dir->path(); EXPECT_CALL(*mock_factory, create_virtual_machine) - .WillOnce(Return(std::make_unique>(mock_instance_name))); + .WillOnce(Return(std::make_unique>(mock_instance_name, zone))); mp::Daemon daemon{config_builder.build()}; @@ -199,7 +203,7 @@ TEST_F(TestDaemonUmount, stoppingMountFails) EXPECT_CALL(*mock_mount_handler, is_active).WillOnce(Return(true)); EXPECT_CALL(*mock_mount_handler, deactivate_impl(false)).WillOnce(Throw(std::runtime_error{error})); - auto mock_vm = std::make_unique>(mock_instance_name); + auto mock_vm = std::make_unique>(mock_instance_name, zone); EXPECT_CALL(*mock_vm, make_native_mount_handler(fake_target_path, _)) .WillOnce(Return(std::move(mock_mount_handler))); diff --git a/tests/test_delayed_shutdown.cpp b/tests/test_delayed_shutdown.cpp index ebf594ae16..019ba7b089 100644 --- a/tests/test_delayed_shutdown.cpp +++ b/tests/test_delayed_shutdown.cpp @@ -18,6 +18,7 @@ #include "common.h" #include "mock_ssh_test_fixture.h" #include "mock_virtual_machine.h" +#include "stub_availability_zone.h" #include "stub_virtual_machine.h" #include @@ -45,6 +46,7 @@ struct DelayedShutdown : public Test mp::VirtualMachine::UPtr vm; QEventLoop loop; ssh_channel_callbacks callbacks{nullptr}; + mpt::StubAvailabilityZone zone{}; }; TEST_F(DelayedShutdown, emits_finished_after_timer_expires) @@ -71,7 +73,7 @@ TEST_F(DelayedShutdown, wallsImpendingShutdown) static const auto upcoming_cmd_matcher = AllOf(HasSubstr("wall"), HasSubstr(msg_upcoming)); static const auto now_cmd_matcher = AllOf(HasSubstr("wall"), HasSubstr(msg_now)); - mpt::MockVirtualMachine vm{mp::VirtualMachine::State::running, "mock"}; + mpt::MockVirtualMachine vm{mp::VirtualMachine::State::running, "mock", zone}; mp::DelayedShutdownTimer delayed_shutdown_timer{&vm, [](const std::string&) {}}; EXPECT_CALL(vm, ssh_exec(upcoming_cmd_matcher, _)).Times(1); // as we start @@ -85,7 +87,7 @@ TEST_F(DelayedShutdown, wallsImpendingShutdown) TEST_F(DelayedShutdown, handlesExceptionWhenAttemptingToWall) { - mpt::MockVirtualMachine vm{mp::VirtualMachine::State::running, "mock"}; + mpt::MockVirtualMachine vm{mp::VirtualMachine::State::running, "mock", zone}; mp::DelayedShutdownTimer delayed_shutdown_timer{&vm, [](const std::string&) {}}; EXPECT_CALL(vm, ssh_exec(HasSubstr("wall"), _)).Times(2).WillRepeatedly(Throw(mp::SSHException("nope"))); diff --git a/tests/test_instance_settings_handler.cpp b/tests/test_instance_settings_handler.cpp index c03a1c0dc5..67cad39eb2 100644 --- a/tests/test_instance_settings_handler.cpp +++ b/tests/test_instance_settings_handler.cpp @@ -17,6 +17,7 @@ #include "common.h" #include "mock_virtual_machine.h" +#include "stub_availability_zone.h" #include #include @@ -123,7 +124,8 @@ struct TestInstanceSettingsHandler : public Test template