diff --git a/include/multipass/base_availability_zone.h b/include/multipass/base_availability_zone.h
new file mode 100644
index 0000000000..43343251ac
--- /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 : 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{};
+ bool available{};
+ std::vector> vms{};
+ // we don't have designated initializers, so mutex remains last so it doesn't need to be manually initialized
+ mutable std::recursive_mutex mutex{};
+ } m;
+
+ static data read_from_file(const std::string& name, const std::filesystem::path& file_path);
+};
+} // namespace multipass
+
+#endif // MULTIPASS_BASE_AVAILABILITY_ZONE_H
diff --git a/include/multipass/base_availability_zone_manager.h b/include/multipass/base_availability_zone_manager.h
new file mode 100644
index 0000000000..1857dabb55
--- /dev/null
+++ b/include/multipass/base_availability_zone_manager.h
@@ -0,0 +1,78 @@
+/*
+ * 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
+#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;
+
+ class ZoneCollection
+ {
+ public:
+ static constexpr size_t size = default_zone_names.size();
+ using ZoneArray = std::array;
+ static_assert(size > 0);
+
+ const ZoneArray zones{};
+
+ ZoneCollection(ZoneArray&& zones, std::string last_used);
+ [[nodiscard]] std::string next_available();
+ [[nodiscard]] std::string last_used() const;
+
+ private:
+ ZoneArray::const_iterator automatic_zone;
+ mutable std::shared_mutex mutex{};
+ };
+
+ // 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{};
+ ZoneCollection zone_collection;
+ // we don't have designated initializers, so mutex remains last so it doesn't need to be manually initialized
+ mutable std::recursive_mutex mutex{};
+ } m;
+
+ [[nodiscard]] const ZoneCollection::ZoneArray& zones() const;
+
+ static data read_from_file(const std::filesystem::path& file_path, const std::filesystem::path& zones_directory);
+};
+} // namespace multipass
+
+#endif // MULTIPASS_BASE_AVAILABILITY_ZONE_MANAGER_H
diff --git a/include/multipass/constants.h b/include/multipass/constants.h
index 00ad8bfe3b..95ed243a06 100644
--- a/include/multipass/constants.h
+++ b/include/multipass/constants.h
@@ -72,6 +72,8 @@ constexpr auto petenv_default = "primary";
constexpr auto timeout_exit_code = 5;
constexpr auto authenticated_certs_dir = "authenticated-certs";
+
+constexpr auto default_zone_names = {"zone1", "zone2", "zone3"};
} // namespace multipass
#endif // MULTIPASS_CONSTANTS_H
diff --git a/include/multipass/json_utils.h b/include/multipass/json_utils.h
index 0cf43cea92..53fbdefdcb 100644
--- a/include/multipass/json_utils.h
+++ b/include/multipass/json_utils.h
@@ -26,6 +26,7 @@
#include
#include
+#include
#include
#include
#include
@@ -41,6 +42,7 @@ class JsonUtils : public Singleton
explicit JsonUtils(const Singleton::PrivatePass&) noexcept;
virtual void write_json(const QJsonObject& root, QString file_name) const; // transactional; creates parent dirs
+ virtual QJsonObject read_object_from_file(const std::filesystem::path& file_path) const;
virtual std::string json_to_string(const QJsonObject& root) const;
virtual QJsonValue update_cloud_init_instance_id(const QJsonValue& id,
const std::string& src_vm_name,
diff --git a/include/multipass/platform.h b/include/multipass/platform.h
index 56dff1665a..93610ca055 100644
--- a/include/multipass/platform.h
+++ b/include/multipass/platform.h
@@ -19,6 +19,7 @@
#define MULTIPASS_PLATFORM_H
#include
+#include
#include
#include
#include
@@ -86,7 +87,7 @@ void sync_winterm_profiles();
std::string default_server_address();
-VirtualMachineFactory::UPtr vm_backend(const Path& data_dir);
+VirtualMachineFactory::UPtr vm_backend(const Path& data_dir, AvailabilityZoneManager& az_manager);
logging::Logger::UPtr make_logger(logging::Level level);
UpdatePrompt::UPtr make_update_prompt();
std::unique_ptr make_sshfs_server_process(const SSHFSServerConfig& config);
diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h
index 57e5b302b1..1fd11a13bf 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 set_available(bool available) = 0;
virtual State current_state() = 0;
virtual int ssh_port() = 0;
virtual std::string ssh_hostname()
@@ -122,6 +125,7 @@ class VirtualMachine : private DisabledCopyMove
virtual int get_snapshot_count() const = 0;
QDir instance_directory() const;
+ virtual const AvailabilityZone& get_zone() const = 0;
VirtualMachine::State state;
const std::string vm_name;
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/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/daemon/daemon.cpp b/src/daemon/daemon.cpp
index eaaccdffdc..24ce292764 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,
@@ -2863,6 +2893,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,8 +2915,17 @@ 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 AvailabilityZoneNotFound& e)
+{
+ status_promise->set_value(grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, e.what(), ""));
+}
catch (const std::exception& e)
{
status_promise->set_value(grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, e.what(), ""));
@@ -3018,6 +3064,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,16 +3103,20 @@ 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()};
+ 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);
preparing_instances.erase(name);
@@ -3136,7 +3188,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 +3204,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 +3212,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..9263eedfae 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
@@ -152,8 +153,10 @@ std::unique_ptr mp::DaemonConfigBuilder::build()
if (url_downloader == nullptr)
url_downloader = std::make_unique(cache_directory, std::chrono::seconds{10});
+ if (az_manager == nullptr)
+ az_manager = std::make_unique(data_directory.toStdString());
if (factory == nullptr)
- factory = platform::vm_backend(data_directory);
+ factory = platform::vm_backend(data_directory, *az_manager);
if (update_prompt == nullptr)
update_prompt = platform::make_update_prompt();
if (image_hosts.empty())
@@ -238,8 +241,23 @@ std::unique_ptr mp::DaemonConfigBuilder::build()
server_name_from(server_address));
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;
diff --git a/src/platform/backends/hyperv/hyperv_virtual_machine.cpp b/src/platform/backends/hyperv/hyperv_virtual_machine.cpp
index 0ab44d659c..2216101596 100644
--- a/src/platform/backends/hyperv/hyperv_virtual_machine.cpp
+++ b/src/platform/backends/hyperv/hyperv_virtual_machine.cpp
@@ -151,8 +151,9 @@ fs::path locate_vmcx_file(const fs::path& exported_vm_dir_path)
mp::HyperVVirtualMachine::HyperVVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const mp::Path& instance_dir)
- : HyperVVirtualMachine{desc, monitor, key_provider, instance_dir, true}
+ : HyperVVirtualMachine{desc, monitor, key_provider, zone, instance_dir, true}
{
if (!power_shell->run({"Get-VM", "-Name", name}))
{
@@ -204,8 +205,9 @@ mp::HyperVVirtualMachine::HyperVVirtualMachine(const std::string& source_vm_name
const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& dest_instance_dir)
- : HyperVVirtualMachine{desc, monitor, key_provider, dest_instance_dir, true}
+ : HyperVVirtualMachine{desc, monitor, key_provider, zone, dest_instance_dir, true}
{
// 1. Export-VM -Name vm1 -Path C:\ProgramData\Multipass\data\vault\instances\vm1-clone1
power_shell->easy_run(
@@ -257,9 +259,10 @@ mp::HyperVVirtualMachine::HyperVVirtualMachine(const std::string& source_vm_name
mp::HyperVVirtualMachine::HyperVVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& instance_dir,
bool /*is_internal*/)
- : BaseVirtualMachine{desc.vm_name, key_provider, instance_dir},
+ : BaseVirtualMachine{desc.vm_name, key_provider, zone, instance_dir},
desc{desc},
name{QString::fromStdString(desc.vm_name)},
power_shell{std::make_unique(vm_name)},
diff --git a/src/platform/backends/hyperv/hyperv_virtual_machine.h b/src/platform/backends/hyperv/hyperv_virtual_machine.h
index 2012983773..a5b81e517e 100644
--- a/src/platform/backends/hyperv/hyperv_virtual_machine.h
+++ b/src/platform/backends/hyperv/hyperv_virtual_machine.h
@@ -44,6 +44,7 @@ class HyperVVirtualMachine final : public BaseVirtualMachine
HyperVVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& instance_dir);
// Contruct the vm based on the source virtual machine
HyperVVirtualMachine(const std::string& source_vm_name,
@@ -51,6 +52,7 @@ class HyperVVirtualMachine final : public BaseVirtualMachine
const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& dest_instance_dir);
~HyperVVirtualMachine();
void start() override;
@@ -85,6 +87,7 @@ class HyperVVirtualMachine final : public BaseVirtualMachine
HyperVVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& instance_dir,
bool is_internal); // is_internal is a dummy parameter to differentiate with other constructors
diff --git a/src/platform/backends/hyperv/hyperv_virtual_machine_factory.cpp b/src/platform/backends/hyperv/hyperv_virtual_machine_factory.cpp
index 29a2b9fdfb..155bffa1fb 100644
--- a/src/platform/backends/hyperv/hyperv_virtual_machine_factory.cpp
+++ b/src/platform/backends/hyperv/hyperv_virtual_machine_factory.cpp
@@ -247,8 +247,10 @@ std::string error_msg_helper(const std::string& msg_core, const QString& ps_outp
}
} // namespace
-mp::HyperVVirtualMachineFactory::HyperVVirtualMachineFactory(const mp::Path& data_dir)
- : BaseVirtualMachineFactory(MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir))
+mp::HyperVVirtualMachineFactory::HyperVVirtualMachineFactory(const mp::Path& data_dir,
+ AvailabilityZoneManager& az_manager)
+ : BaseVirtualMachineFactory(MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir),
+ az_manager)
{
}
@@ -259,6 +261,7 @@ mp::VirtualMachine::UPtr mp::HyperVVirtualMachineFactory::create_virtual_machine
return std::make_unique(desc,
monitor,
key_provider,
+ az_manager.get_zone(desc.zone),
get_instance_directory(desc.vm_name));
}
@@ -433,5 +436,6 @@ mp::VirtualMachine::UPtr mp::HyperVVirtualMachineFactory::clone_vm_impl(const st
dest_vm_desc,
monitor,
key_provider,
+ az_manager.get_zone(dest_vm_desc.zone),
get_instance_directory(dest_vm_desc.vm_name));
}
diff --git a/src/platform/backends/hyperv/hyperv_virtual_machine_factory.h b/src/platform/backends/hyperv/hyperv_virtual_machine_factory.h
index a23f8ddcba..b1a5a1dfd6 100644
--- a/src/platform/backends/hyperv/hyperv_virtual_machine_factory.h
+++ b/src/platform/backends/hyperv/hyperv_virtual_machine_factory.h
@@ -30,7 +30,7 @@ struct HyperVNetworkAccessor; // fwd declaration, to befriend below
class HyperVVirtualMachineFactory final : public BaseVirtualMachineFactory
{
public:
- explicit HyperVVirtualMachineFactory(const Path& data_dir);
+ explicit HyperVVirtualMachineFactory(const Path& data_dir, AvailabilityZoneManager& az_manager);
VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc,
const SSHKeyProvider& key_provider,
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..82f289eeac 100644
--- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp
+++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp
@@ -107,9 +107,10 @@ auto make_libvirt_wrapper(const std::string& libvirt_object_path)
} // namespace
mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir,
- const std::string& libvirt_object_path)
- : BaseVirtualMachineFactory(
- MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir)),
+ const std::string& libvirt_object_path,
+ AvailabilityZoneManager& az_manager)
+ : BaseVirtualMachineFactory(MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir),
+ az_manager),
libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)},
data_dir{data_dir},
bridge_name{enable_libvirt_network(data_dir, libvirt_wrapper)},
@@ -117,8 +118,9 @@ mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& d
{
}
-mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir)
- : LibVirtVirtualMachineFactory(data_dir, "libvirt.so.0")
+mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir,
+ AvailabilityZoneManager& az_manager)
+ : LibVirtVirtualMachineFactory(data_dir, "libvirt.so.0", az_manager)
{
}
@@ -134,6 +136,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..c57d1295ab 100644
--- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h
+++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h
@@ -31,8 +31,10 @@ class ProcessFactory;
class LibVirtVirtualMachineFactory final : public BaseVirtualMachineFactory
{
public:
- explicit LibVirtVirtualMachineFactory(const Path& data_dir, const std::string& libvirt_object_path); // For testing
- explicit LibVirtVirtualMachineFactory(const Path& data_dir);
+ explicit LibVirtVirtualMachineFactory(const Path& data_dir,
+ const std::string& libvirt_object_path,
+ AvailabilityZoneManager& az_manager); // For testing
+ explicit LibVirtVirtualMachineFactory(const Path& data_dir, AvailabilityZoneManager& az_manager);
~LibVirtVirtualMachineFactory();
VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc,
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..2cc207c17a 100644
--- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp
+++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp
@@ -77,16 +77,19 @@ mp::NetworkInterfaceInfo munch_network(std::map(), data_dir, base_url)
+mp::LXDVirtualMachineFactory::LXDVirtualMachineFactory(const mp::Path& data_dir,
+ AvailabilityZoneManager& az_manager,
+ const QUrl& base_url)
+ : LXDVirtualMachineFactory(std::make_unique(), data_dir, az_manager, base_url)
{
}
@@ -101,6 +104,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..a6c8f2d0cb 100644
--- a/src/platform/backends/lxd/lxd_virtual_machine_factory.h
+++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.h
@@ -30,8 +30,12 @@ namespace multipass
class LXDVirtualMachineFactory : public BaseVirtualMachineFactory
{
public:
- explicit LXDVirtualMachineFactory(const Path& data_dir, const QUrl& base_url = lxd_socket_url);
- explicit LXDVirtualMachineFactory(NetworkAccessManager::UPtr manager, const Path& data_dir,
+ explicit LXDVirtualMachineFactory(const Path& data_dir,
+ AvailabilityZoneManager& az_manager,
+ const QUrl& base_url = lxd_socket_url);
+ explicit LXDVirtualMachineFactory(NetworkAccessManager::UPtr manager,
+ const Path& data_dir,
+ AvailabilityZoneManager& az_manager,
const QUrl& base_url = lxd_socket_url);
VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc,
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..e3a211a8e8 100644
--- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp
+++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp
@@ -36,14 +36,17 @@ namespace
constexpr auto category = "qemu factory";
} // namespace
-mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_dir)
- : QemuVirtualMachineFactory{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir), data_dir}
+mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_dir, AvailabilityZoneManager& az_manager)
+ : QemuVirtualMachineFactory{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir), data_dir, az_manager}
{
}
-mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const mp::Path& data_dir)
+mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform,
+ const mp::Path& data_dir,
+ AvailabilityZoneManager& az_manager)
: BaseVirtualMachineFactory(
- MP_UTILS.derive_instances_dir(data_dir, qemu_platform->get_directory_name(), instances_subdir)),
+ MP_UTILS.derive_instances_dir(data_dir, qemu_platform->get_directory_name(), instances_subdir),
+ az_manager),
qemu_platform{std::move(qemu_platform)}
{
}
@@ -56,6 +59,7 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(c
qemu_platform.get(),
monitor,
key_provider,
+ az_manager.get_zone(desc.zone),
get_instance_directory(desc.vm_name));
}
@@ -167,6 +171,7 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::clone_vm_impl(const std:
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..9d07df6feb 100644
--- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h
+++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h
@@ -31,7 +31,7 @@ namespace multipass
class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory
{
public:
- explicit QemuVirtualMachineFactory(const Path& data_dir);
+ explicit QemuVirtualMachineFactory(const Path& data_dir, AvailabilityZoneManager& az_manager);
VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc,
const SSHKeyProvider& key_provider,
@@ -51,7 +51,9 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory
std::string create_bridge_with(const NetworkInterfaceInfo& interface) override;
private:
- QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir);
+ QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform,
+ const Path& data_dir,
+ AvailabilityZoneManager& az_manager);
VirtualMachine::UPtr clone_vm_impl(const std::string& source_vm_name,
const multipass::VMSpecs& src_vm_specs,
const VirtualMachineDescription& desc,
diff --git a/src/platform/backends/shared/CMakeLists.txt b/src/platform/backends/shared/CMakeLists.txt
index 8981b6c04f..e65d52cbe4 100644
--- a/src/platform/backends/shared/CMakeLists.txt
+++ b/src/platform/backends/shared/CMakeLists.txt
@@ -13,6 +13,8 @@
# along with this program. If not, see .
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.cpp b/src/platform/backends/shared/base_availability_zone.cpp
new file mode 100644
index 0000000000..b7b5816bf2
--- /dev/null
+++ b/src/platform/backends/shared/base_availability_zone.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+namespace mpl = multipass::logging;
+
+namespace
+{
+constexpr auto subnet_key = "subnet";
+constexpr auto available_key = "available";
+
+[[nodiscard]] QJsonObject read_json(const multipass::fs::path& file_path, const std::string& name)
+try
+{
+ return MP_JSONUTILS.read_object_from_file(file_path);
+}
+catch (const std::ios_base::failure& e)
+{
+ mpl::warn(name, "failed to read AZ file: {}", e.what());
+ return QJsonObject{};
+}
+
+[[nodiscard]] std::string deserialize_subnet(const QJsonObject& json,
+ const multipass::fs::path& file_path,
+ const std::string& name)
+{
+ if (const auto json_subnet = json[subnet_key].toString().toStdString(); !json_subnet.empty())
+ return json_subnet;
+
+ mpl::debug(name, "subnet missing from AZ file '{}', using default", file_path);
+ // TODO GET ACTUAL SUBNET
+ return {};
+};
+
+[[nodiscard]] bool deserialize_available(const QJsonObject& json,
+ const multipass::fs::path& file_path,
+ const std::string& name)
+{
+ if (const auto json_available = json[available_key]; json_available.isBool())
+ return json_available.toBool();
+
+ mpl::debug(name, "availability missing from AZ file '{}', using default", file_path);
+ return true;
+}
+} // namespace
+
+namespace multipass
+{
+
+BaseAvailabilityZone::data BaseAvailabilityZone::read_from_file(const std::string& name, const fs::path& file_path)
+{
+ mpl::trace(name, "reading AZ from file '{}'", file_path);
+
+ const auto json = read_json(file_path, name);
+ return {
+ // TODO remove these comments in C++20
+ /*.name = */ name,
+ /*.file_path = */ file_path,
+ /*.subnet = */ deserialize_subnet(json, file_path, name),
+ /*.available = */ deserialize_available(json, file_path, name),
+ };
+}
+
+BaseAvailabilityZone::BaseAvailabilityZone(const std::string& name, const fs::path& az_directory)
+ : m{read_from_file(name, az_directory / (name + ".json"))}
+{
+ 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)
+{
+
+ mpl::debug(m.name, "making AZ {}available", new_available ? "" : "un");
+ const std::unique_lock lock{m.mutex};
+ if (m.available == new_available)
+ return;
+
+ m.available = new_available;
+ serialize();
+
+ for (auto& vm : m.vms)
+ vm.get().set_available(m.available);
+}
+
+void BaseAvailabilityZone::add_vm(VirtualMachine& vm)
+{
+ mpl::debug(m.name, "adding vm '{}' to AZ", vm.vm_name);
+ const std::unique_lock lock{m.mutex};
+ m.vms.emplace_back(vm);
+}
+
+void BaseAvailabilityZone::remove_vm(VirtualMachine& vm)
+{
+ mpl::debug(m.name, "removing vm '{}' from AZ", vm.vm_name);
+ const std::unique_lock lock{m.mutex};
+ // 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
+{
+ mpl::trace(m.name, "writing AZ to file '{}'", m.file_path);
+ const std::unique_lock lock{m.mutex};
+
+ const QJsonObject json{
+ {subnet_key, QString::fromStdString(m.subnet)},
+ {available_key, m.available},
+ };
+
+ MP_JSONUTILS.write_json(json, QString::fromStdString(m.file_path.u8string()));
+}
+} // namespace multipass
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..9cd0df68c7
--- /dev/null
+++ b/src/platform/backends/shared/base_availability_zone_manager.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+namespace mpl = multipass::logging;
+
+namespace
+{
+constexpr auto category = "az-manager";
+constexpr auto az_file = "az-manager.json";
+constexpr auto zones_directory_name = "zones";
+constexpr auto automatic_zone_key = "automatic_zone";
+
+[[nodiscard]] auto create_default_zones(const multipass::fs::path& zones_directory)
+{
+ using namespace multipass;
+
+ std::array zones{};
+ std::transform(default_zone_names.begin(), default_zone_names.end(), zones.begin(), [&](const auto& zone_name) {
+ return std::make_unique(zone_name, zones_directory);
+ });
+ return zones;
+};
+
+[[nodiscard]] QJsonObject read_json(const std::filesystem::path& file_path)
+try
+{
+ return MP_JSONUTILS.read_object_from_file(file_path);
+}
+catch (const std::ios_base::failure& e)
+{
+ mpl::warn(category, "failed to read AZ manager file': {}", e.what());
+ return QJsonObject{};
+}
+
+[[nodiscard]] std::string deserialize_automatic_zone(const QJsonObject& json)
+{
+ using namespace multipass;
+
+ auto json_automatic_zone = json[automatic_zone_key].toString().toStdString();
+ if (std::find(default_zone_names.begin(), default_zone_names.end(), json_automatic_zone) !=
+ default_zone_names.end())
+ return json_automatic_zone;
+
+ mpl::debug(category, "automatic zone '{}' not known, using default", json_automatic_zone);
+ return *default_zone_names.begin();
+}
+} // namespace
+
+namespace multipass
+{
+
+BaseAvailabilityZoneManager::data BaseAvailabilityZoneManager::read_from_file(
+ const std::filesystem::path& file_path,
+ const std::filesystem::path& zones_directory)
+{
+ mpl::debug(category, "reading AZ manager from file '{}'", file_path);
+ return {
+ // TODO remove these comments in C++20
+ /*.file_path = */ file_path,
+ /*.zone_collection = */
+ {create_default_zones(zones_directory), deserialize_automatic_zone(read_json(file_path))},
+ };
+}
+
+BaseAvailabilityZoneManager::BaseAvailabilityZoneManager(const fs::path& data_dir)
+ : m{read_from_file(data_dir / az_file, data_dir / zones_directory_name)}
+{
+ serialize();
+}
+
+AvailabilityZone& BaseAvailabilityZoneManager::get_zone(const std::string& name)
+{
+ for (const auto& zone : zones())
+ {
+ if (zone->get_name() == name)
+ return *zone;
+ }
+ throw AvailabilityZoneNotFound{name};
+}
+
+std::string BaseAvailabilityZoneManager::get_automatic_zone_name()
+{
+ const auto zone_name = m.zone_collection.next_available();
+ serialize();
+ return zone_name;
+}
+
+std::vector> BaseAvailabilityZoneManager::get_zones()
+{
+ std::vector> zone_list;
+ zone_list.reserve(zones().size());
+ for (auto& zone : zones())
+ zone_list.emplace_back(*zone);
+ return zone_list;
+}
+
+std::string BaseAvailabilityZoneManager::get_default_zone_name() const
+{
+ return (*zones().begin())->get_name();
+}
+
+void BaseAvailabilityZoneManager::serialize() const
+{
+ mpl::debug(category, "writing AZ manager to file '{}'", m.file_path);
+ const std::unique_lock lock{m.mutex};
+
+ const QJsonObject json{
+ {automatic_zone_key, QString::fromStdString(m.zone_collection.last_used())},
+ };
+
+ MP_JSONUTILS.write_json(json, QString::fromStdString(m.file_path.u8string()));
+}
+
+const BaseAvailabilityZoneManager::ZoneCollection::ZoneArray& BaseAvailabilityZoneManager::zones() const
+{
+ return m.zone_collection.zones;
+}
+
+BaseAvailabilityZoneManager::ZoneCollection::ZoneCollection(
+ std::array&& _zones,
+ std::string last_used)
+ : zones{std::move(_zones)}, automatic_zone{std::find_if(zones.begin(), zones.end(), [&last_used](const auto& zone) {
+ return zone->get_name() == last_used;
+ })}
+{
+}
+
+std::string BaseAvailabilityZoneManager::ZoneCollection::next_available()
+{
+ std::unique_lock lock{mutex};
+ const auto start = automatic_zone;
+ auto current = start;
+ do
+ {
+ const auto& az = *current->get();
+ if (++current == zones.end())
+ current = zones.begin();
+
+ if (az.is_available())
+ {
+ automatic_zone = current;
+ return az.get_name();
+ }
+ } while (current != start);
+
+ throw NoAvailabilityZoneAvailable{};
+}
+
+std::string BaseAvailabilityZoneManager::ZoneCollection::last_used() const
+{
+ std::shared_lock lock{mutex};
+ return automatic_zone->get()->get_name();
+}
+
+} // namespace multipass
diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp
index db5c3f3832..c034bfb786 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, instance_dir}, key_provider{key_provider}, zone{zone}
{
+ 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, instance_dir}, key_provider{key_provider}, zone{zone}
{
+ zone.add_vm(*this);
+}
+
+mp::BaseVirtualMachine::~BaseVirtualMachine()
+{
+ mp::top_catch_all(vm_name, [this] { 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..38c86fa03e 100644
--- a/src/platform/backends/shared/base_virtual_machine.h
+++ b/src/platform/backends/shared/base_virtual_machine.h
@@ -45,11 +45,22 @@ 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 set_available(bool available) override
+ {
+ // TODO make vm unavailable by force stopping if running or available by starting again if it was running
+ throw NotImplementedOnThisBackendException("unavailability");
+ }
+
void wait_until_ssh_up(std::chrono::milliseconds timeout) override;
void wait_for_cloud_init(std::chrono::milliseconds timeout) override;
@@ -85,6 +96,11 @@ class BaseVirtualMachine : public VirtualMachine
std::vector get_childrens_names(const Snapshot* parent) const override;
int get_snapshot_count() const override;
+ const AvailabilityZone& get_zone() const override
+ {
+ return zone;
+ }
+
protected:
virtual void require_snapshots_support() const;
virtual std::shared_ptr make_specific_snapshot(const QString& filename);
@@ -151,6 +167,7 @@ class BaseVirtualMachine : public VirtualMachine
protected:
const SSHKeyProvider& key_provider;
+ AvailabilityZone& zone;
private:
std::optional ssh_session = std::nullopt;
diff --git a/src/platform/backends/shared/base_virtual_machine_factory.cpp b/src/platform/backends/shared/base_virtual_machine_factory.cpp
index 480fcce202..42a9b09e86 100644
--- a/src/platform/backends/shared/base_virtual_machine_factory.cpp
+++ b/src/platform/backends/shared/base_virtual_machine_factory.cpp
@@ -31,7 +31,8 @@ namespace mpu = multipass::utils;
const mp::Path mp::BaseVirtualMachineFactory::instances_subdir = "vault/instances";
-mp::BaseVirtualMachineFactory::BaseVirtualMachineFactory(const Path& instances_dir) : instances_dir{instances_dir} {};
+mp::BaseVirtualMachineFactory::BaseVirtualMachineFactory(const Path& instances_dir, AvailabilityZoneManager& az_manager)
+ : az_manager{az_manager}, instances_dir{instances_dir} {};
void mp::BaseVirtualMachineFactory::configure(VirtualMachineDescription& vm_desc)
{
@@ -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);
}
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..9b16034291 100644
--- a/src/platform/backends/shared/base_virtual_machine_factory.h
+++ b/src/platform/backends/shared/base_virtual_machine_factory.h
@@ -18,6 +18,7 @@
#ifndef MULTIPASS_BASE_VIRTUAL_MACHINE_FACTORY_H
#define MULTIPASS_BASE_VIRTUAL_MACHINE_FACTORY_H
+#include
#include
#include
#include
@@ -34,7 +35,7 @@ constexpr auto log_category = "base factory";
class BaseVirtualMachineFactory : public VirtualMachineFactory
{
public:
- explicit BaseVirtualMachineFactory(const Path& instances_dir);
+ explicit BaseVirtualMachineFactory(const Path& instances_dir, AvailabilityZoneManager& az_manager);
VirtualMachine::UPtr clone_bare_vm(const VMSpecs& src_spec,
const VMSpecs& dest_spec,
const std::string& src_name,
@@ -83,6 +84,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory
protected:
static const Path instances_subdir;
+ AvailabilityZoneManager& az_manager;
protected:
std::string create_bridge_with(const NetworkInterfaceInfo& interface) override
diff --git a/src/platform/backends/virtualbox/virtualbox_virtual_machine.cpp b/src/platform/backends/virtualbox/virtualbox_virtual_machine.cpp
index 52d6d192a3..7b63cf382a 100644
--- a/src/platform/backends/virtualbox/virtualbox_virtual_machine.cpp
+++ b/src/platform/backends/virtualbox/virtualbox_virtual_machine.cpp
@@ -179,8 +179,9 @@ void update_mac_addresses_of_network_adapters(const mp::VirtualMachineDescriptio
mp::VirtualBoxVirtualMachine::VirtualBoxVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const mp::Path& instance_dir_qstr)
- : VirtualBoxVirtualMachine(desc, monitor, key_provider, instance_dir_qstr, true)
+ : VirtualBoxVirtualMachine(desc, monitor, key_provider, zone, instance_dir_qstr, true)
{
if (desc.extra_interfaces.size() > 7)
{
@@ -256,8 +257,9 @@ mp::VirtualBoxVirtualMachine::VirtualBoxVirtualMachine(const std::string& source
const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& dest_instance_dir)
- : VirtualBoxVirtualMachine(desc, monitor, key_provider, dest_instance_dir, true)
+ : VirtualBoxVirtualMachine(desc, monitor, key_provider, zone, dest_instance_dir, true)
{
const fs::path instances_dir = fs::path{dest_instance_dir.toStdString()}.parent_path();
@@ -321,9 +323,10 @@ mp::VirtualBoxVirtualMachine::VirtualBoxVirtualMachine(const std::string& source
mp::VirtualBoxVirtualMachine::VirtualBoxVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const mp::Path& instance_dir_qstr,
bool /*is_internal*/)
- : BaseVirtualMachine{desc.vm_name, key_provider, instance_dir_qstr},
+ : BaseVirtualMachine{desc.vm_name, key_provider, zone, instance_dir_qstr},
desc{desc},
name{QString::fromStdString(desc.vm_name)},
monitor{&monitor}
diff --git a/src/platform/backends/virtualbox/virtualbox_virtual_machine.h b/src/platform/backends/virtualbox/virtualbox_virtual_machine.h
index b8fc12cfff..4eb0aee692 100644
--- a/src/platform/backends/virtualbox/virtualbox_virtual_machine.h
+++ b/src/platform/backends/virtualbox/virtualbox_virtual_machine.h
@@ -39,12 +39,14 @@ class VirtualBoxVirtualMachine final : public BaseVirtualMachine
VirtualBoxVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& instance_dir);
// Contruct the vm based on the source virtual machine
VirtualBoxVirtualMachine(const std::string& source_vm_name,
const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& dest_instance_dir);
~VirtualBoxVirtualMachine() override;
@@ -80,6 +82,7 @@ class VirtualBoxVirtualMachine final : public BaseVirtualMachine
VirtualBoxVirtualMachine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor,
const SSHKeyProvider& key_provider,
+ AvailabilityZone& zone,
const Path& instance_dir_qstr,
bool is_internal);
void remove_snapshots_from_backend() const;
diff --git a/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.cpp b/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.cpp
index f868819a9b..d7aeed16e2 100644
--- a/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.cpp
+++ b/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.cpp
@@ -108,8 +108,10 @@ mp::NetworkInterfaceInfo list_vbox_network(const QString& vbox_iface_info,
}
} // namespace
-mp::VirtualBoxVirtualMachineFactory::VirtualBoxVirtualMachineFactory(const mp::Path& data_dir)
- : BaseVirtualMachineFactory(MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir))
+mp::VirtualBoxVirtualMachineFactory::VirtualBoxVirtualMachineFactory(const mp::Path& data_dir,
+ AvailabilityZoneManager& az_manager)
+ : BaseVirtualMachineFactory(MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir),
+ az_manager)
{
}
@@ -120,6 +122,7 @@ auto mp::VirtualBoxVirtualMachineFactory::create_virtual_machine(const VirtualMa
return std::make_unique(desc,
monitor,
key_provider,
+ az_manager.get_zone(desc.zone),
get_instance_directory(desc.vm_name));
}
@@ -257,5 +260,6 @@ mp::VirtualMachine::UPtr mp::VirtualBoxVirtualMachineFactory::clone_vm_impl(
dest_vm_desc,
monitor,
key_provider,
+ az_manager.get_zone(dest_vm_desc.zone),
get_instance_directory(dest_vm_desc.vm_name));
}
diff --git a/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.h b/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.h
index e54cd87d6d..115001d9e6 100644
--- a/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.h
+++ b/src/platform/backends/virtualbox/virtualbox_virtual_machine_factory.h
@@ -25,7 +25,7 @@ namespace multipass
class VirtualBoxVirtualMachineFactory final : public BaseVirtualMachineFactory
{
public:
- explicit VirtualBoxVirtualMachineFactory(const Path& data_dir);
+ explicit VirtualBoxVirtualMachineFactory(const Path& data_dir, AvailabilityZoneManager& az_manager);
VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc,
const SSHKeyProvider& key_provider,
diff --git a/src/platform/platform_linux.cpp b/src/platform/platform_linux.cpp
index 7ecd648d18..f6f8cc4614 100644
--- a/src/platform/platform_linux.cpp
+++ b/src/platform/platform_linux.cpp
@@ -408,19 +408,19 @@ std::string mp::platform::default_server_address()
return "unix:" + base_dir + "/multipass_socket";
}
-mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_dir)
+mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_dir, AvailabilityZoneManager& az_manager)
{
const auto& driver = MP_SETTINGS.get(mp::driver_key);
#ifdef QEMU_ENABLED
if (driver == QStringLiteral("qemu"))
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
#endif
if (driver == QStringLiteral("libvirt"))
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
if (driver == QStringLiteral("lxd"))
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
#if VIRTUALBOX_ENABLED
if (driver == QStringLiteral("virtualbox"))
diff --git a/src/platform/platform_osx.cpp b/src/platform/platform_osx.cpp
index fc2a90ceda..e0ba97c42d 100644
--- a/src/platform/platform_osx.cpp
+++ b/src/platform/platform_osx.cpp
@@ -267,7 +267,7 @@ QString mp::platform::Platform::daemon_config_home() const // temporary
return ret;
}
-mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_dir)
+mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_dir, AvailabilityZoneManager& az_manager)
{
auto driver = MP_SETTINGS.get(mp::driver_key);
@@ -280,13 +280,13 @@ mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_di
there.
*/
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
#endif
}
else if (driver == QStringLiteral("qemu"))
{
#if QEMU_ENABLED
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
#endif
}
diff --git a/src/platform/platform_win.cpp b/src/platform/platform_win.cpp
index de6d732e3f..3311e9377b 100644
--- a/src/platform/platform_win.cpp
+++ b/src/platform/platform_win.cpp
@@ -599,12 +599,12 @@ QString mp::platform::Platform::daemon_config_home() const // temporary
}
}
-mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_dir)
+mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_dir, AvailabilityZoneManager& az_manager)
{
const auto driver = MP_SETTINGS.get(mp::driver_key);
if (driver == QStringLiteral("hyperv"))
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
else if (driver == QStringLiteral("virtualbox"))
{
qputenv("Path", qgetenv("Path") + ";C:\\Program Files\\Oracle\\VirtualBox"); /*
@@ -613,7 +613,7 @@ mp::VirtualMachineFactory::UPtr mp::platform::vm_backend(const mp::Path& data_di
there.
*/
- return std::make_unique(data_dir);
+ return std::make_unique(data_dir, az_manager);
}
throw std::runtime_error("Invalid virtualization driver set in the environment");
diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp
index 0603c22eb8..90bbba8529 100644
--- a/src/utils/json_utils.cpp
+++ b/src/utils/json_utils.cpp
@@ -17,6 +17,7 @@
*
*/
+#include
#include
#include
#include
@@ -55,6 +56,14 @@ void mp::JsonUtils::write_json(const QJsonObject& root, QString file_name) const
throw std::runtime_error{fmt::format("Could not commit transactional file; filename: {}", file_name)};
}
+QJsonObject mp::JsonUtils::read_object_from_file(const std::filesystem::path& file_path) const
+{
+ const auto file = MP_FILEOPS.open_read(file_path);
+ file->exceptions(std::ifstream::failbit | std::ifstream::badbit);
+ const auto data = QString::fromStdString(std::string{std::istreambuf_iterator{*file}, {}}).toUtf8();
+ return QJsonDocument::fromJson(data).object();
+}
+
std::string mp::JsonUtils::json_to_string(const QJsonObject& root) const
{
// The function name toJson() is shockingly wrong, for it converts an actual JsonDocument to a QByteArray.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 7d047e1d1c..0acb6aed6d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -54,6 +54,8 @@ add_executable(multipass_tests
temp_file.cpp
test_alias_dict.cpp
test_argparser.cpp
+ test_base_availability_zone.cpp
+ test_base_availability_zone_manager.cpp
test_base_snapshot.cpp
test_base_virtual_machine.cpp
test_base_virtual_machine_factory.cpp
diff --git a/tests/daemon_test_fixture.cpp b/tests/daemon_test_fixture.cpp
index bf4193d805..445de6805e 100644
--- a/tests/daemon_test_fixture.cpp
+++ b/tests/daemon_test_fixture.cpp
@@ -23,6 +23,7 @@
#include "mock_cert_provider.h"
#include "mock_server_reader_writer.h"
#include "mock_standard_paths.h"
+#include "stub_availability_zone_manager.h"
#include "stub_cert_store.h"
#include "stub_image_host.h"
#include "stub_logger.h"
@@ -309,11 +310,14 @@ class TestClient : public multipass::Client
mpt::DaemonTestFixture::DaemonTestFixture()
{
+ auto az_manager = std::make_unique();
+
config_builder.server_address = server_address;
config_builder.cache_directory = cache_dir.path();
config_builder.data_directory = data_dir.path();
config_builder.vault = std::make_unique();
- config_builder.factory = std::make_unique();
+ config_builder.factory = std::make_unique(*az_manager);
+ config_builder.az_manager = std::move(az_manager);
config_builder.image_hosts.push_back(std::make_unique());
config_builder.ssh_key_provider = std::make_unique();
config_builder.cert_provider = std::make_unique>();
diff --git a/tests/hyperv/test_hyperv_backend.cpp b/tests/hyperv/test_hyperv_backend.cpp
index baf42dd618..e194f077da 100644
--- a/tests/hyperv/test_hyperv_backend.cpp
+++ b/tests/hyperv/test_hyperv_backend.cpp
@@ -19,6 +19,8 @@
#include "tests/mock_logger.h"
#include "tests/mock_platform.h"
#include "tests/mock_process_factory.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/temp_dir.h"
@@ -124,6 +126,7 @@ struct HyperVBackend : public Test
mp::MemorySize{"3M"},
mp::MemorySize{}, // not used,
"pied-piper-valley",
+ "zone1",
"ba:ba:ca:ca:ca:ba",
{},
"",
@@ -131,7 +134,9 @@ struct HyperVBackend : public Test
dummy_cloud_init_iso.name()};
mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject();
mpt::PowerShellTestHelper ps_helper;
- mp::HyperVVirtualMachineFactory backend{data_dir.path()};
+ mpt::StubAvailabilityZone zone{};
+ mpt::StubAvailabilityZoneManager az_manager{};
+ mp::HyperVVirtualMachineFactory backend{data_dir.path(), az_manager};
mpt::StubVMStatusMonitor stub_monitor;
mpt::StubSSHKeyProvider stub_key_provider;
};
@@ -382,7 +387,9 @@ struct HyperVNetworks : public Test
mpt::MockPlatform::GuardedMock attr = mpt::MockPlatform::inject();
mpt::MockPlatform* mock_platform = attr.first;
mpt::TempDir data_dir;
- mp::HyperVVirtualMachineFactory backend{data_dir.path()};
+ mpt::StubAvailabilityZone zone{};
+ mpt::StubAvailabilityZoneManager az_manager{};
+ mp::HyperVVirtualMachineFactory backend{data_dir.path(), az_manager};
};
struct HyperVNetworksPS : public HyperVNetworks
diff --git a/tests/libvirt/test_libvirt_backend.cpp b/tests/libvirt/test_libvirt_backend.cpp
index 54df51bcef..e1c2b224ea 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{""};
@@ -81,7 +84,7 @@ TEST_F(LibVirtBackend, health_check_good_does_not_throw)
EXPECT_CALL(*mock_backend, check_for_kvm_support()).WillOnce(Return());
EXPECT_CALL(*mock_backend, check_if_kvm_is_in_use()).WillOnce(Return());
- mp::LibVirtVirtualMachineFactory backend(data_dir.path(), fake_libvirt_path);
+ mp::LibVirtVirtualMachineFactory backend(data_dir.path(), fake_libvirt_path, az_manager);
EXPECT_NO_THROW(backend.hypervisor_health_check());
}
@@ -96,7 +99,7 @@ TEST_F(LibVirtBackend, health_check_failed_connection_throws)
auto virGetLastErrorMessage = [&error_msg] { return error_msg.c_str(); };
static auto static_virGetLastErrorMessage = virGetLastErrorMessage;
- mp::LibVirtVirtualMachineFactory backend(data_dir.path(), fake_libvirt_path);
+ mp::LibVirtVirtualMachineFactory backend(data_dir.path(), fake_libvirt_path, az_manager);
backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; };
backend.libvirt_wrapper->virGetLastErrorMessage = [] { return static_virGetLastErrorMessage(); };
@@ -108,7 +111,7 @@ TEST_F(LibVirtBackend, health_check_failed_connection_throws)
TEST_F(LibVirtBackend, creates_in_off_state)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
mpt::StubVMStatusMonitor stub_monitor;
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -118,7 +121,7 @@ TEST_F(LibVirtBackend, creates_in_off_state)
TEST_F(LibVirtBackend, creates_in_suspended_state_with_managed_save)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virDomainHasManagedSaveImage = [](auto...) { return 1; };
mpt::StubVMStatusMonitor stub_monitor;
@@ -132,7 +135,7 @@ TEST_F(LibVirtBackend, machine_sends_monitoring_events)
REPLACE(ssh_connect, [](auto...) { return SSH_OK; });
REPLACE(ssh_userauth_publickey, [](auto...) { return SSH_AUTH_SUCCESS; });
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virNetworkGetDHCPLeases = [](auto, auto, auto leases, auto) {
virNetworkDHCPLeasePtr* leases_ret;
leases_ret = (virNetworkDHCPLeasePtr*)calloc(1, sizeof(virNetworkDHCPLeasePtr));
@@ -165,7 +168,7 @@ TEST_F(LibVirtBackend, machine_sends_monitoring_events)
TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_start)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
NiceMock mock_monitor;
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
@@ -182,7 +185,7 @@ TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_start)
TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_shutdown)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virDomainGetState = [](auto, auto state, auto, auto) {
*state = VIR_DOMAIN_RUNNING;
return 0;
@@ -203,7 +206,7 @@ TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_shutdown)
TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_suspend)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virDomainGetState = [](auto, auto state, auto, auto) {
*state = VIR_DOMAIN_RUNNING;
return 0;
@@ -226,7 +229,7 @@ TEST_F(LibVirtBackend, machine_persists_and_sets_state_on_suspend)
TEST_F(LibVirtBackend, start_with_broken_libvirt_connection_throws)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; };
NiceMock mock_monitor;
@@ -239,7 +242,7 @@ TEST_F(LibVirtBackend, start_with_broken_libvirt_connection_throws)
TEST_F(LibVirtBackend, shutdown_with_broken_libvirt_connection_throws)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; };
NiceMock mock_monitor;
@@ -252,7 +255,7 @@ TEST_F(LibVirtBackend, shutdown_with_broken_libvirt_connection_throws)
TEST_F(LibVirtBackend, suspend_with_broken_libvirt_connection_throws)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; };
NiceMock mock_monitor;
@@ -265,7 +268,7 @@ TEST_F(LibVirtBackend, suspend_with_broken_libvirt_connection_throws)
TEST_F(LibVirtBackend, current_state_with_broken_libvirt_unknown)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; };
NiceMock mock_monitor;
@@ -276,7 +279,7 @@ TEST_F(LibVirtBackend, current_state_with_broken_libvirt_unknown)
TEST_F(LibVirtBackend, current_state_delayed_shutdown_domain_running)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virDomainGetState = [](auto, auto state, auto, auto) {
*state = VIR_DOMAIN_RUNNING;
return 0;
@@ -291,7 +294,7 @@ TEST_F(LibVirtBackend, current_state_delayed_shutdown_domain_running)
TEST_F(LibVirtBackend, current_state_delayed_shutdown_domain_off)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
NiceMock mock_monitor;
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
machine->state = mp::VirtualMachine::State::delayed_shutdown;
@@ -301,7 +304,7 @@ TEST_F(LibVirtBackend, current_state_delayed_shutdown_domain_off)
TEST_F(LibVirtBackend, current_state_off_domain_starts_running)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
NiceMock mock_monitor;
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
@@ -317,7 +320,7 @@ TEST_F(LibVirtBackend, current_state_off_domain_starts_running)
TEST_F(LibVirtBackend, returns_version_string)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectGetVersion = [](virConnectPtr, unsigned long* hvVer) {
*hvVer = 1002003;
return 0;
@@ -328,7 +331,7 @@ TEST_F(LibVirtBackend, returns_version_string)
TEST_F(LibVirtBackend, returns_version_string_when_error)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectGetVersion = [](auto...) { return -1; };
EXPECT_EQ(backend.get_backend_version_string(), "libvirt-unknown");
@@ -336,7 +339,7 @@ TEST_F(LibVirtBackend, returns_version_string_when_error)
TEST_F(LibVirtBackend, returns_version_string_when_lacking_capabilities)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
EXPECT_EQ(backend.get_backend_version_string(), "libvirt-unknown");
}
@@ -353,7 +356,7 @@ TEST_F(LibVirtBackend, returns_version_string_when_failed_connecting)
// to a pointer to a function.
static auto static_virConnectGetVersion = virConnectGetVersion;
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virConnectOpen = [](auto...) -> virConnectPtr { return nullptr; };
backend.libvirt_wrapper->virConnectGetVersion = [](virConnectPtr conn, long unsigned int* hwVer) {
return static_virConnectGetVersion(conn, hwVer);
@@ -366,7 +369,7 @@ TEST_F(LibVirtBackend, returns_version_string_when_failed_connecting)
TEST_F(LibVirtBackend, ssh_hostname_returns_expected_value)
{
mpt::StubVMStatusMonitor stub_monitor;
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
const std::string expected_ip{"10.10.0.34"};
auto virNetworkGetDHCPLeases = [&expected_ip](auto, auto, auto leases, auto) {
@@ -400,7 +403,7 @@ TEST_F(LibVirtBackend, ssh_hostname_returns_expected_value)
TEST_F(LibVirtBackend, ssh_hostname_timeout_throws_and_sets_unknown_state)
{
mpt::StubVMStatusMonitor stub_monitor;
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
backend.libvirt_wrapper->virNetworkGetDHCPLeases = [](auto...) { return 0; };
@@ -419,7 +422,7 @@ TEST_F(LibVirtBackend, ssh_hostname_timeout_throws_and_sets_unknown_state)
TEST_F(LibVirtBackend, shutdown_while_starting_throws_and_sets_correct_state)
{
mpt::StubVMStatusMonitor stub_monitor;
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
auto destroy_called{false};
auto virDomainDestroy = [&backend, &destroy_called](auto...) {
@@ -462,7 +465,7 @@ TEST_F(LibVirtBackend, shutdown_while_starting_throws_and_sets_correct_state)
TEST_F(LibVirtBackend, machineInOffStateLogsAndIgnoresShutdown)
{
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
NiceMock mock_monitor;
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
@@ -485,7 +488,7 @@ TEST_F(LibVirtBackend, machineNoForceCannotShutdownLogsAndThrows)
{
const std::string error_msg{"Not working"};
- mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path};
+ mp::LibVirtVirtualMachineFactory backend{data_dir.path(), fake_libvirt_path, az_manager};
NiceMock mock_monitor;
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
@@ -510,7 +513,7 @@ TEST_F(LibVirtBackend, machineNoForceCannotShutdownLogsAndThrows)
TEST_F(LibVirtBackend, lists_no_networks)
{
- mp::LibVirtVirtualMachineFactory backend(data_dir.path(), fake_libvirt_path);
+ mp::LibVirtVirtualMachineFactory backend(data_dir.path(), fake_libvirt_path, az_manager);
EXPECT_THROW(backend.networks(), mp::NotImplementedOnThisBackendException);
}
diff --git a/tests/linux/test_platform_linux.cpp b/tests/linux/test_platform_linux.cpp
index 6c51db0b7f..c33ddb5865 100644
--- a/tests/linux/test_platform_linux.cpp
+++ b/tests/linux/test_platform_linux.cpp
@@ -57,6 +57,7 @@
#include
#include
+#include
namespace mp = multipass;
namespace mpt = multipass::test;
@@ -79,9 +80,10 @@ struct PlatformLinux : public mpt::TestWithMockedBinPath
auto factory = mpt::MockProcessFactory::Inject();
setup_driver_settings(driver);
+ mpt::StubAvailabilityZoneManager az_manager{};
- decltype(mp::platform::vm_backend("")) factory_ptr;
- EXPECT_NO_THROW(factory_ptr = mp::platform::vm_backend(backend_path););
+ decltype(mp::platform::vm_backend("", az_manager)) factory_ptr;
+ EXPECT_NO_THROW(factory_ptr = mp::platform::vm_backend(backend_path, az_manager););
EXPECT_TRUE(dynamic_cast(factory_ptr.get()));
}
@@ -206,7 +208,8 @@ TEST_P(TestUnsupportedDrivers, test_unsupported_driver)
ASSERT_FALSE(MP_PLATFORM.is_backend_supported(GetParam()));
setup_driver_settings(GetParam());
- EXPECT_THROW(mp::platform::vm_backend(backend_path), std::runtime_error);
+ mpt::StubAvailabilityZoneManager az_manager{};
+ EXPECT_THROW(mp::platform::vm_backend(backend_path, az_manager), std::runtime_error);
}
INSTANTIATE_TEST_SUITE_P(PlatformLinux, TestUnsupportedDrivers,
diff --git a/tests/lxd/test_lxd_backend.cpp b/tests/lxd/test_lxd_backend.cpp
index 60e6a69957..23a2b0fc0e 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",
@@ -84,6 +87,8 @@ struct LXDBackend : public Test
mpt::TempDir data_dir;
mpt::TempDir instance_dir;
mpt::StubSSHKeyProvider key_provider;
+ mpt::StubAvailabilityZoneManager az_manager{};
+ mpt::StubAvailabilityZone zone{};
std::unique_ptr> mock_network_access_manager;
QUrl base_url{"unix:///foo@1.0"};
const QString default_storage_pool{"default"};
@@ -168,7 +173,7 @@ TEST_F(LXDBackend, createsProjectStoragePoolAndNetworkOnHealthcheck)
return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
backend.hypervisor_health_check();
@@ -227,7 +232,7 @@ TEST_F(LXDBackend, usesDefaultStoragePoolWhenItExistsAndNoMultipassPool)
logger_scope.mock_logger->expect_log(mpl::Level::debug, "Using the \'default\' storage pool.");
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
backend.hypervisor_health_check();
@@ -284,7 +289,7 @@ TEST_F(LXDBackend, usesMultipassStoragePoolWhenItExists)
logger_scope.mock_logger->expect_log(mpl::Level::debug, "Using the \'multipass\' storage pool.");
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
backend.hypervisor_health_check();
@@ -352,7 +357,7 @@ TEST_F(LXDBackend, usesMultipassPoolWhenDefaultPoolExists)
logger_scope.mock_logger->expect_log(mpl::Level::debug, "Using the \'multipass\' storage pool.");
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
backend.hypervisor_health_check();
}
@@ -381,7 +386,7 @@ TEST_F(LXDBackend, factory_creates_valid_virtual_machine_ptr)
return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -396,7 +401,7 @@ TEST_F(LXDBackend, factory_creates_expected_image_vault)
mpt::TempDir data_dir;
std::vector hosts;
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
auto vault = backend.create_image_vault(hosts, &stub_downloader, cache_dir.path(), data_dir.path(), mp::days{0});
@@ -408,7 +413,7 @@ TEST_F(LXDBackend, factory_does_nothing_on_configure)
mpt::TempDir data_dir;
mp::VirtualMachineDescription vm_desc{default_description};
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
backend.configure(vm_desc);
@@ -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,12 +949,13 @@ TEST_F(LXDBackend, posts_expected_data_when_creating_instance)
bridge_name,
default_storage_pool,
key_provider,
+ zone,
instance_dir.path()};
}
TEST_F(LXDBackend, prepare_source_image_does_not_modify)
{
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
const mp::VMImage original_image{"/path/to/image", "deadbeef", "bin", "baz", "the past",
{"fee", "fi", "fo", "fum"}};
@@ -992,7 +1006,7 @@ TEST_F(LXDBackend, returns_expected_backend_string)
return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
const QString backend_string{"lxd-4.3"};
@@ -1001,7 +1015,7 @@ TEST_F(LXDBackend, returns_expected_backend_string)
TEST_F(LXDBackend, unimplemented_functions_logs_trace_message)
{
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
const std::string name{"foo"};
@@ -1022,7 +1036,7 @@ TEST_F(LXDBackend, unimplemented_functions_logs_trace_message)
TEST_F(LXDBackend, factoryDoesNotSupportSuspend)
{
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
MP_EXPECT_THROW_THAT(backend.require_suspend_support(),
mp::NotImplementedOnThisBackendException,
mpt::match_what(HasSubstr("suspend")));
@@ -1030,7 +1044,7 @@ TEST_F(LXDBackend, factoryDoesNotSupportSuspend)
TEST_F(LXDBackend, image_fetch_type_returns_expected_type)
{
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_EQ(backend.fetch_type(), mp::FetchType::ImageOnly);
}
@@ -1069,7 +1083,7 @@ TEST_F(LXDBackend, healthcheck_throws_when_untrusted)
return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
MP_EXPECT_THROW_THAT(backend.hypervisor_health_check(), std::runtime_error,
mpt::match_what(StrEq("Failed to authenticate to LXD.")));
@@ -1084,7 +1098,7 @@ TEST_F(LXDBackend, healthcheck_connection_refused_error_throws_with_expected_mes
throw mp::LocalSocketConnectionException(exception_message);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
MP_EXPECT_THROW_THAT(backend.hypervisor_health_check(), std::runtime_error,
mpt::match_what(StrEq(fmt::format("{}\n\nPlease ensure the LXD snap is installed and enabled.",
@@ -1100,7 +1114,7 @@ TEST_F(LXDBackend, healthcheck_unknown_server_error_throws_with_expected_message
throw mp::LocalSocketConnectionException(exception_message);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
MP_EXPECT_THROW_THAT(backend.hypervisor_health_check(), std::runtime_error,
mpt::match_what(StrEq(fmt::format("{}\n\nPlease ensure the LXD snap is installed and enabled.",
@@ -1111,7 +1125,7 @@ TEST_F(LXDBackend, healthcheck_error_advises_snap_connections_when_in_snap)
{
EXPECT_CALL(*mock_network_access_manager, createRequest).WillOnce(Throw(mp::LocalSocketConnectionException("")));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
mpt::SetEnvScope env{"SNAP_NAME", "multipass"};
MP_EXPECT_THROW_THAT(backend.hypervisor_health_check(), std::runtime_error,
@@ -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);
@@ -2054,7 +2081,7 @@ TEST_F(LXDBackend, requests_networks)
createRequest(QNetworkAccessManager::CustomOperation, network_request_matcher, _))
.WillOnce(Return(new mpt::MockLocalSocketReply{mpt::networks_empty_data}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_THAT(backend.networks(), IsEmpty());
}
@@ -2070,7 +2097,7 @@ TEST_P(LXDNetworksBadJson, handles_gibberish_networks_reply)
createRequest(QNetworkAccessManager::CustomOperation, network_request_matcher, _))
.WillOnce(Return(new mpt::MockLocalSocketReply{GetParam()}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_THROW(backend.networks(), std::runtime_error);
}
@@ -2088,7 +2115,7 @@ TEST_P(LXDNetworksBadFields, ignores_network_without_expected_fields)
createRequest(QNetworkAccessManager::CustomOperation, network_request_matcher, _))
.WillOnce(Return(new mpt::MockLocalSocketReply{GetParam()}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_THAT(backend.networks(), IsEmpty());
}
@@ -2111,7 +2138,7 @@ TEST_P(LXDNetworksOnlySupportedTypes, reports_only_bridge_and_ethernet_networks)
createRequest(QNetworkAccessManager::CustomOperation, network_request_matcher, _))
.WillOnce(Return(new mpt::MockLocalSocketReply{GetParam()}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
auto [mock_platform, guard] = mpt::MockPlatform::inject();
EXPECT_CALL(*mock_platform, get_network_interfaces_info)
@@ -2145,7 +2172,7 @@ TEST_F(LXDBackend, honors_bridge_description_from_lxd_when_available)
EXPECT_CALL(*mock_platform, get_network_interfaces_info)
.WillOnce(Return(std::map{{"br0", {"br0", "bridge", "gibberish"}}}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_THAT(backend.networks(), ElementsAre(Field(&mp::NetworkInterfaceInfo::description, Eq(description))));
}
@@ -2163,7 +2190,7 @@ TEST_F(LXDBackend, falls_back_to_bridge_description_from_platform)
createRequest(QNetworkAccessManager::CustomOperation, network_request_matcher, _))
.WillOnce(Return(new mpt::MockLocalSocketReply{{data}}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_THAT(backend.networks(), ElementsAre(Field(&mp::NetworkInterfaceInfo::description, Eq(fallback_desc))));
}
@@ -2179,7 +2206,7 @@ TEST_F(LXDBackend, skips_platform_network_inspection_when_lxd_reports_no_network
createRequest(QNetworkAccessManager::CustomOperation, network_request_matcher, _))
.WillOnce(Return(new mpt::MockLocalSocketReply{{data}}));
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_THAT(backend.networks(), IsEmpty());
}
@@ -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()};
}
@@ -2304,7 +2333,7 @@ class CustomLXDFactory : public mp::LXDVirtualMachineFactory
TEST_F(LXDBackend, preparesNetworkingViaBaseFactory)
{
- CustomLXDFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ CustomLXDFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
std::vector extra_networks{{"netid", "mac", false}};
auto address = [](const auto& v) { return &v; }; // replace with Address matcher once available
@@ -2318,7 +2347,7 @@ TEST_F(LXDBackend, createsBridgesViaBackendUtils)
const auto bridge = "bli";
auto [mock_backend, guard] = mpt::MockBackend::inject();
- CustomLXDFactory factory{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ CustomLXDFactory factory{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
EXPECT_CALL(*mock_backend, create_bridge_with(net.id)).WillOnce(Return(bridge));
EXPECT_EQ(factory.create_bridge_with(net), bridge);
@@ -2372,7 +2401,7 @@ TEST_F(LXDBackend, addsNetworkInterface)
return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), az_manager, base_url};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -2392,6 +2421,9 @@ TEST_F(LXDBackend, converts_http_to_https)
return new mpt::MockLocalSocketReply(mpt::stop_vm_data);
});
- mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), QUrl{"http://bar"}};
+ mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager),
+ data_dir.path(),
+ az_manager,
+ QUrl{"http://bar"}};
backend.create_virtual_machine(default_description, key_provider, stub_monitor);
}
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_json_utils.h b/tests/mock_json_utils.h
index 7dd2a83869..4f91609b1b 100644
--- a/tests/mock_json_utils.h
+++ b/tests/mock_json_utils.h
@@ -31,6 +31,7 @@ class MockJsonUtils : public JsonUtils
using JsonUtils::JsonUtils;
MOCK_METHOD(void, write_json, (const QJsonObject&, QString), (const, override));
+ MOCK_METHOD(QJsonObject, read_object_from_file, (const std::filesystem::path& file_path), (const, override));
MOCK_METHOD(std::string, json_to_string, (const QJsonObject& root), (const, override));
MP_MOCK_SINGLETON_BOILERPLATE(MockJsonUtils, JsonUtils);
diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h
index c9e5ccad64..05600a0085 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, set_available, (bool), (override));
MOCK_METHOD(multipass::VirtualMachine::State, current_state, (), (override));
MOCK_METHOD(int, ssh_port, (), (override));
MOCK_METHOD(std::string, ssh_hostname, (), (override));
@@ -101,6 +102,7 @@ struct MockVirtualMachineT : public T
MOCK_METHOD(void, load_snapshots, (), (override));
MOCK_METHOD(std::vector, get_childrens_names, (const Snapshot*), (const, override));
MOCK_METHOD(int, get_snapshot_count, (), (const, override));
+ MOCK_METHOD(AvailabilityZone&, get_zone, (), (const, override));
std::unique_ptr tmp_dir;
};
diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp
index df0f8075fa..19fbcfe859 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::StubAvailabilityZone zone{};
+ mpt::StubAvailabilityZoneManager az_manager{};
mpt::MockProcessFactory::Callback handle_external_process_calls = [](mpt::MockProcess* process) {
// Have "qemu-img snapshot" return a string with the suspend tag in it
@@ -193,7 +198,7 @@ TEST_F(QemuBackend, creates_in_off_state)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off));
@@ -205,7 +210,7 @@ TEST_F(QemuBackend, machine_in_off_state_handles_shutdown)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
EXPECT_THAT(machine->current_state(), Eq(mp::VirtualMachine::State::off));
@@ -221,7 +226,7 @@ TEST_F(QemuBackend, machine_start_shutdown_sends_monitoring_events)
});
NiceMock mock_monitor;
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
process_factory->register_callback(handle_qemu_system);
@@ -245,7 +250,7 @@ TEST_F(QemuBackend, machine_start_suspend_sends_monitoring_event)
});
NiceMock mock_monitor;
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
process_factory->register_callback(handle_qemu_system);
@@ -277,7 +282,7 @@ TEST_F(QemuBackend, throws_when_shutdown_while_starting)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -317,7 +322,7 @@ TEST_F(QemuBackend, throws_on_shutdown_timeout)
});
mpt::StubVMStatusMonitor stub_monitor;
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -353,7 +358,7 @@ TEST_F(QemuBackend, includes_error_when_shutdown_while_starting)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -389,7 +394,7 @@ TEST_F(QemuBackend, machine_unknown_state_properly_shuts_down)
});
NiceMock mock_monitor;
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
process_factory->register_callback(handle_qemu_system);
@@ -416,7 +421,7 @@ TEST_F(QemuBackend, suspendedStateNoForceShutdownThrows)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -436,7 +441,7 @@ TEST_F(QemuBackend, suspendingStateNoForceShutdownThrows)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -458,7 +463,7 @@ TEST_F(QemuBackend, startingStateNoForceShutdownThrows)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -502,7 +507,7 @@ TEST_F(QemuBackend, forceShutdownKillsProcessAndLogs)
logger_scope.mock_logger->expect_log(mpl::Level::info, "Killed");
logger_scope.mock_logger->expect_log(mpl::Level::info, "Force stopped");
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -527,7 +532,7 @@ TEST_F(QemuBackend, forceShutdownNoProcessLogs)
logger_scope.mock_logger->expect_log(mpl::Level::info, "Forcing shutdown");
logger_scope.mock_logger->expect_log(mpl::Level::debug, "No process to kill");
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
@@ -558,7 +563,7 @@ TEST_F(QemuBackend, forceShutdownSuspendDeletesSuspendImageAndOffState)
logger_scope.mock_logger->expect_log(mpl::Level::debug, "No process to kill");
logger_scope.mock_logger->expect_log(mpl::Level::info, "Deleting suspend image");
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
machine->state = mp::VirtualMachine::State::suspended;
@@ -583,7 +588,7 @@ TEST_F(QemuBackend, forceShutdownSuspendedStateButNoSuspensionSnapshotInImage)
logger_scope.mock_logger->expect_log(mpl::Level::debug, "No process to kill");
logger_scope.mock_logger->expect_log(mpl::Level::warning, "Image has no suspension snapshot, but the state is 7");
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
machine->state = mp::VirtualMachine::State::suspended;
@@ -613,7 +618,7 @@ TEST_F(QemuBackend, forceShutdownRunningStateButWithSuspensionSnapshotInImage)
logger_scope.mock_logger->expect_log(mpl::Level::info, "Deleting suspend image");
logger_scope.mock_logger->expect_log(mpl::Level::warning, "Image has a suspension snapshot, but the state is 4");
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
machine->state = mp::VirtualMachine::State::running;
@@ -634,7 +639,7 @@ TEST_F(QemuBackend, verify_dnsmasq_qemuimg_and_qemu_processes_created)
});
auto factory = mpt::StubProcessFactory::Inject();
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
machine->start();
@@ -667,7 +672,7 @@ TEST_F(QemuBackend, verify_some_common_qemu_arguments)
}
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
machine->start();
@@ -692,7 +697,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image)
process_factory->register_callback(handle_external_process_calls);
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
machine->start();
@@ -723,7 +728,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image_uses_metad
EXPECT_CALL(mock_monitor, retrieve_metadata_for(_))
.WillRepeatedly(Return(QJsonObject({{"machine_type", machine_type}})));
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
machine->start();
@@ -763,7 +768,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_from_metadata_are_used)
EXPECT_CALL(mock_monitor, retrieve_metadata_for(_))
.WillRepeatedly(Return(QJsonObject({{"arguments", QJsonArray{"-hi_there", "-hows_it_going"}}})));
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, mock_monitor);
machine->start();
@@ -801,7 +806,7 @@ TEST_F(QemuBackend, returns_version_string)
process_factory->register_callback(callback);
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
EXPECT_EQ(backend.get_backend_version_string(), "qemu-2.11.1");
}
@@ -826,7 +831,7 @@ TEST_F(QemuBackend, returns_version_string_when_failed_parsing)
process_factory->register_callback(callback);
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
EXPECT_EQ(backend.get_backend_version_string(), "qemu-unknown");
}
@@ -850,7 +855,7 @@ TEST_F(QemuBackend, returns_version_string_when_errored)
process_factory->register_callback(callback);
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
EXPECT_EQ(backend.get_backend_version_string(), "qemu-unknown");
}
@@ -873,7 +878,7 @@ TEST_F(QemuBackend, returns_version_string_when_exec_failed)
process_factory->register_callback(callback);
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
EXPECT_EQ(backend.get_backend_version_string(), "qemu-unknown");
}
@@ -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));
@@ -1059,7 +1075,7 @@ TEST_F(QemuBackend, networks_returns_supported_networks)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
const std::map networks{
{"lxdbr0", {"lxdbr0", "bridge", "gobbledygook"}},
@@ -1091,7 +1107,7 @@ TEST_F(QemuBackend, remove_resources_for_calls_qemu_platform)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
backend.remove_resources_for(test_name);
@@ -1110,7 +1126,7 @@ TEST_F(QemuBackend, hypervisor_health_check_calls_qemu_platform)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
backend.hypervisor_health_check();
@@ -1134,7 +1150,7 @@ TEST_F(QemuBackend, get_backend_directory_name_calls_qemu_platform)
return std::move(mock_qemu_platform);
});
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
const auto dir_name = backend.get_backend_directory_name();
@@ -1151,7 +1167,7 @@ TEST_F(QemuBackend, addNetworkInterface)
const auto [mock_cloud_init_file_ops, _] = mpt::MockCloudInitFileOps::inject();
EXPECT_CALL(*mock_cloud_init_file_ops, add_extra_interface_to_cloud_init).Times(1);
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor);
EXPECT_NO_THROW(machine->add_network_interface(0, "", {"", "", true}));
@@ -1164,7 +1180,7 @@ TEST_F(QemuBackend, createBridgeWithChecksWithQemuPlatform)
});
EXPECT_CALL(*mock_qemu_platform, needs_network_prep()).Times(1).WillRepeatedly(Return(true));
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
std::vector extra_interfaces{{"eth1", "52:54:00:00:00:00", true}};
EXPECT_NO_THROW(backend.prepare_networking(extra_interfaces));
@@ -1194,14 +1210,17 @@ TEST_F(QemuBackend, removeAllSnapshotsFromTheImage)
process_factory->register_callback(snapshot_list_callback);
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};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
+
+ 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();
@@ -1222,7 +1241,7 @@ TEST_F(QemuBackend, createVmAndCloneInstanceDirData)
});
mpt::StubVMStatusMonitor stub_monitor;
- mp::QemuVirtualMachineFactory backend{data_dir.path()};
+ mp::QemuVirtualMachineFactory backend{data_dir.path(), az_manager};
const mpt::MockCloudInitFileOps::GuardedMock mock_cloud_init_file_ops_injection =
mpt::MockCloudInitFileOps::inject();
EXPECT_CALL(*mock_cloud_init_file_ops_injection.first, update_identifiers(_, _, _, _)).Times(1);
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..d779d1c8f3
--- /dev/null
+++ b/tests/stub_availability_zone.h
@@ -0,0 +1,64 @@
+/*
+ * 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
+{
+ 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..abb2c41cd2 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"
@@ -55,6 +56,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine
{
}
+ void set_available(bool) override
+ {
+ }
+
multipass::VirtualMachine::State current_state() override
{
return multipass::VirtualMachine::State::off;
@@ -194,6 +199,12 @@ struct StubVirtualMachine final : public multipass::VirtualMachine
return 0;
}
+ const AvailabilityZone& get_zone() const override
+ {
+ return zone;
+ }
+
+ StubAvailabilityZone zone{};
StubSnapshot snapshot;
std::unique_ptr tmp_dir;
};
diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h
index a692fc8fbd..4255905cbb 100644
--- a/tests/stub_virtual_machine_factory.h
+++ b/tests/stub_virtual_machine_factory.h
@@ -30,12 +30,13 @@ namespace test
{
struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory
{
- StubVirtualMachineFactory() : StubVirtualMachineFactory{std::make_unique()}
+ StubVirtualMachineFactory(AvailabilityZoneManager& az_manager)
+ : StubVirtualMachineFactory{std::make_unique(), az_manager}
{
}
- StubVirtualMachineFactory(std::unique_ptr tmp_dir)
- : multipass::BaseVirtualMachineFactory{tmp_dir->path()}, tmp_dir{std::move(tmp_dir)}
+ StubVirtualMachineFactory(std::unique_ptr tmp_dir, AvailabilityZoneManager& az_manager)
+ : multipass::BaseVirtualMachineFactory{tmp_dir->path(), az_manager}, tmp_dir{std::move(tmp_dir)}
{
}
diff --git a/tests/test_base_availability_zone.cpp b/tests/test_base_availability_zone.cpp
new file mode 100644
index 0000000000..e31c29328f
--- /dev/null
+++ b/tests/test_base_availability_zone.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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 "common.h"
+#include "mock_file_ops.h"
+#include "mock_json_utils.h"
+#include "mock_logger.h"
+#include "mock_virtual_machine.h"
+
+#include
+#include
+
+#include
+
+namespace mp = multipass;
+namespace mpl = multipass::logging;
+namespace mpt = multipass::test;
+using namespace testing;
+
+struct BaseAvailabilityZoneTest : public Test
+{
+ BaseAvailabilityZoneTest()
+ {
+ mock_logger.mock_logger->screen_logs(mpl::Level::error);
+ }
+
+ const std::string az_name{"zone1"};
+ const mp::fs::path az_dir{"/path/to/zones"};
+ const mp::fs::path az_file = az_dir / (az_name + ".json");
+ const QString az_file_qstr{QString::fromStdString(az_file.u8string())};
+
+ mpt::MockFileOps::GuardedMock mock_file_ops_guard{mpt::MockFileOps::inject()};
+ mpt::MockJsonUtils::GuardedMock mock_json_utils_guard{mpt::MockJsonUtils::inject()};
+ mpt::MockLogger::Scope mock_logger{mpt::MockLogger::inject()};
+};
+
+TEST_F(BaseAvailabilityZoneTest, CreatesDefaultAvailableZone)
+{
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(az_file)).WillOnce(Return(QJsonObject{}));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(az_file.u8string())));
+
+ mp::BaseAvailabilityZone zone{az_name, az_dir};
+
+ EXPECT_EQ(zone.get_name(), az_name);
+ EXPECT_TRUE(zone.is_available());
+ // TODO: Subnet generation is not yet implemented
+ // EXPECT_TRUE(zone.get_subnet().empty());
+}
+
+// TODO: Re-implement this test when subnet generation is implemented
+// TEST_F(TestBaseAvailabilityZone, loads_existing_zone_file)
+// {
+// const std::string test_subnet = "10.0.0.0/24";
+// const bool test_available = false;
+//
+// QJsonObject json{{"subnet", QString::fromStdString(test_subnet)}, {"available", test_available}};
+//
+// EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(az_file)).WillOnce(Return(json));
+//
+// EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+//
+// EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(az_file.u8string())));
+//
+// mp::BaseAvailabilityZone zone{az_name, az_dir};
+//
+// EXPECT_EQ(zone.get_name(), az_name);
+// EXPECT_EQ(zone.get_subnet(), test_subnet);
+// EXPECT_FALSE(zone.is_available());
+// }
+
+TEST_F(BaseAvailabilityZoneTest, AddsVmAndUpdatesOnAvailabilityChange)
+{
+ QJsonObject json{{"available", true}};
+
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(az_file)).WillOnce(Return(json));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(az_file.u8string())))
+ .Times(2); // Once in constructor, once in set_available
+
+ mp::BaseAvailabilityZone zone{az_name, az_dir};
+
+ const std::string test_vm_name = "test-vm";
+ NiceMock mock_vm{test_vm_name};
+ EXPECT_CALL(mock_vm, set_available(false));
+
+ zone.add_vm(mock_vm);
+ zone.set_available(false);
+}
+
+TEST_F(BaseAvailabilityZoneTest, RemovesVmCorrectly)
+{
+ QJsonObject json{{"available", true}};
+
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(az_file)).WillOnce(Return(json));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(az_file.u8string())));
+
+ mp::BaseAvailabilityZone zone{az_name, az_dir};
+
+ const std::string test_vm_name = "test-vm";
+ NiceMock mock_vm{test_vm_name};
+
+ zone.add_vm(mock_vm);
+ zone.remove_vm(mock_vm);
+}
+
+TEST_F(BaseAvailabilityZoneTest, AvailabilityStateManagement)
+{
+ QJsonObject json{{"available", true}};
+
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(az_file)).WillOnce(Return(json));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(az_file.u8string())))
+ .Times(2); // Once in constructor, once in set_available
+
+ mp::BaseAvailabilityZone zone{az_name, az_dir};
+
+ const std::string vm1_name = "test-vm1";
+ const std::string vm2_name = "test-vm2";
+ NiceMock mock_vm1{vm1_name};
+ NiceMock mock_vm2{vm2_name};
+
+ // Both VMs should be notified when state changes
+ EXPECT_CALL(mock_vm1, set_available(false));
+ EXPECT_CALL(mock_vm2, set_available(false));
+
+ zone.add_vm(mock_vm1);
+ zone.add_vm(mock_vm2);
+
+ // Setting to current state (true) shouldn't trigger VM updates
+ zone.set_available(true);
+
+ // Setting to new state should notify all VMs
+ zone.set_available(false);
+}
diff --git a/tests/test_base_availability_zone_manager.cpp b/tests/test_base_availability_zone_manager.cpp
new file mode 100644
index 0000000000..f897da53ba
--- /dev/null
+++ b/tests/test_base_availability_zone_manager.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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 "common.h"
+#include "mock_file_ops.h"
+#include "mock_json_utils.h"
+#include "mock_logger.h"
+
+#include
+#include
+#include
+
+#include
+
+namespace mp = multipass;
+namespace mpl = multipass::logging;
+namespace mpt = multipass::test;
+using namespace testing;
+
+struct BaseAvailabilityZoneManagerTest : public Test
+{
+ BaseAvailabilityZoneManagerTest()
+ {
+ mock_logger.mock_logger->screen_logs(mpl::Level::error);
+ }
+
+ const mp::fs::path data_dir{"/path/to/data"};
+ const mp::fs::path manager_file = data_dir / "az-manager.json";
+ const mp::fs::path zones_dir = data_dir / "zones";
+ const QString manager_file_qstr{QString::fromStdString(manager_file.u8string())};
+
+ mpt::MockFileOps::GuardedMock mock_file_ops_guard{mpt::MockFileOps::inject()};
+ mpt::MockJsonUtils::GuardedMock mock_json_utils_guard{mpt::MockJsonUtils::inject()};
+ mpt::MockLogger::Scope mock_logger{mpt::MockLogger::inject()};
+};
+
+TEST_F(BaseAvailabilityZoneManagerTest, CreatesDefaultZones)
+{
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(manager_file)).WillOnce(Return(QJsonObject{}));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(Eq(mpl::Level::trace), _, _)).Times(AnyNumber());
+ EXPECT_CALL(*mock_logger.mock_logger, log(Eq(mpl::Level::debug), _, _)).Times(AnyNumber());
+
+ // Default zones will be created
+ const int expected_zone_count = static_cast(mp::default_zone_names.size());
+ for (const auto& zone_name : mp::default_zone_names)
+ {
+ const auto zone_file = zones_dir / (std::string{zone_name} + ".json");
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(zone_file)).WillOnce(Return(QJsonObject{}));
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(zone_file.u8string())));
+ }
+
+ // Manager file gets written with default zone (once in constructor and once in get_automatic_zone_name)
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, manager_file_qstr)).Times(2);
+
+ mp::BaseAvailabilityZoneManager manager{data_dir};
+
+ const auto zones = manager.get_zones();
+ EXPECT_EQ(zones.size(), expected_zone_count);
+
+ // First zone in default_zone_names should be our default
+ EXPECT_EQ(manager.get_default_zone_name(), *mp::default_zone_names.begin());
+ // And also our automatic zone initially
+ EXPECT_EQ(manager.get_automatic_zone_name(), *mp::default_zone_names.begin());
+}
+
+TEST_F(BaseAvailabilityZoneManagerTest, AutomaticZoneRotation)
+{
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(manager_file)).WillOnce(Return(QJsonObject{}));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(Eq(mpl::Level::trace), _, _)).Times(AnyNumber());
+ EXPECT_CALL(*mock_logger.mock_logger, log(Eq(mpl::Level::debug), _, _)).Times(AnyNumber());
+
+ // Set up all zones to be created
+ for (const auto& zone_name : mp::default_zone_names)
+ {
+ const auto zone_file = zones_dir / (std::string{zone_name} + ".json");
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(zone_file)).WillOnce(Return(QJsonObject{}));
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(zone_file.u8string())))
+ .Times(AnyNumber());
+ }
+
+ // Manager file will be written multiple times during rotation
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, manager_file_qstr)).Times(AnyNumber());
+
+ mp::BaseAvailabilityZoneManager manager{data_dir};
+
+ // First automatic zone should be the first zone
+ const auto first_zone = manager.get_automatic_zone_name();
+ EXPECT_EQ(first_zone, *mp::default_zone_names.begin());
+
+ // Mark first zone as unavailable
+ manager.get_zone(first_zone).set_available(false);
+
+ // Next automatic zone should be second zone
+ const auto second_zone = manager.get_automatic_zone_name();
+ EXPECT_NE(second_zone, first_zone);
+
+ // Mark all zones unavailable
+ for (const auto& zone : manager.get_zones())
+ const_cast(zone.get()).set_available(false);
+
+ // Expect exception when no zones available
+ EXPECT_THROW(manager.get_automatic_zone_name(), mp::NoAvailabilityZoneAvailable);
+}
+
+TEST_F(BaseAvailabilityZoneManagerTest, ThrowsWhenZoneNotFound)
+{
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(manager_file)).WillOnce(Return(QJsonObject{}));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+
+ // Set up default zones to be created
+ for (const auto& zone_name : mp::default_zone_names)
+ {
+ const auto zone_file = zones_dir / (std::string{zone_name} + ".json");
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(zone_file)).WillOnce(Return(QJsonObject{}));
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(zone_file.u8string())))
+ .Times(AnyNumber());
+ }
+
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, manager_file_qstr)).Times(AnyNumber());
+
+ mp::BaseAvailabilityZoneManager manager{data_dir};
+
+ EXPECT_THROW(manager.get_zone("nonexistent-zone"), mp::AvailabilityZoneNotFound);
+}
+
+TEST_F(BaseAvailabilityZoneManagerTest, CyclesThroughAvailableZones)
+{
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(manager_file)).WillOnce(Return(QJsonObject{}));
+
+ EXPECT_CALL(*mock_logger.mock_logger, log(_, _, _)).Times(AnyNumber());
+
+ // Set up default zones to be created - all initially available
+ for (const auto& zone_name : mp::default_zone_names)
+ {
+ const auto zone_file = zones_dir / (std::string{zone_name} + ".json");
+ EXPECT_CALL(*mock_json_utils_guard.first, read_object_from_file(zone_file)).WillOnce(Return(QJsonObject{}));
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, QString::fromStdString(zone_file.u8string())))
+ .Times(AnyNumber());
+ }
+
+ EXPECT_CALL(*mock_json_utils_guard.first, write_json(_, manager_file_qstr)).Times(AnyNumber());
+
+ mp::BaseAvailabilityZoneManager manager{data_dir};
+
+ // First call returns initial zone (zone1)
+ auto zone = manager.get_automatic_zone_name();
+ EXPECT_EQ(zone, "zone1");
+
+ // Make zone2 unavailable
+ manager.get_zone("zone2").set_available(false);
+
+ // Second call follows pointer to zone2 (unavailable), so moves to and returns zone3
+ zone = manager.get_automatic_zone_name();
+ EXPECT_EQ(zone, "zone3");
+
+ // Third call follows pointer to zone1 and returns it
+ zone = manager.get_automatic_zone_name();
+ EXPECT_EQ(zone, "zone1");
+
+ // Fourth call follows pointer to zone2 (unavailable), so moves to and returns zone3
+ zone = manager.get_automatic_zone_name();
+ EXPECT_EQ(zone, "zone3");
+}
diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp
index 276c1d93e3..c5c76e86b6 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)}
{
}
@@ -248,9 +252,10 @@ struct BaseVM : public Test
return MatchesRegex(fmt::format("{0}*{1}{0}*", space_char_class, idx));
}
+ mpt::StubAvailabilityZone zone{};
mpt::MockSSHTestFixture mock_ssh_test_fixture;
const mpt::DummyKeyProvider key_provider{"keeper of the seven keys"};
- NiceMock vm{"mock-vm", key_provider};
+ 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..9e9a80d104 100644
--- a/tests/test_base_virtual_machine_factory.cpp
+++ b/tests/test_base_virtual_machine_factory.cpp
@@ -18,6 +18,7 @@
#include "common.h"
#include "mock_logger.h"
#include "mock_platform.h"
+#include "stub_availability_zone_manager.h"
#include "stub_ssh_key_provider.h"
#include "stub_url_downloader.h"
#include "temp_dir.h"
@@ -38,12 +39,13 @@ namespace
{
struct MockBaseFactory : mp::BaseVirtualMachineFactory
{
- MockBaseFactory() : MockBaseFactory{std::make_unique()}
+ MockBaseFactory(mp::AvailabilityZoneManager& az_manager)
+ : MockBaseFactory{std::make_unique(), az_manager}
{
}
- MockBaseFactory(std::unique_ptr&& tmp_dir)
- : mp::BaseVirtualMachineFactory{tmp_dir->path()}, tmp_dir{std::move(tmp_dir)}
+ MockBaseFactory(std::unique_ptr&& tmp_dir, mp::AvailabilityZoneManager& az_manager)
+ : mp::BaseVirtualMachineFactory{tmp_dir->path(), az_manager}, tmp_dir{std::move(tmp_dir)}
{
}
@@ -80,18 +82,19 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory
struct BaseFactory : public Test
{
mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject();
+ mpt::StubAvailabilityZoneManager az_manager{};
};
TEST_F(BaseFactory, returns_image_only_fetch_type)
{
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
EXPECT_EQ(factory.fetch_type(), mp::FetchType::ImageOnly);
}
TEST_F(BaseFactory, dir_name_returns_empty_string)
{
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
const auto dir_name = factory.get_backend_directory_name();
@@ -104,7 +107,7 @@ TEST_F(BaseFactory, create_image_vault_returns_default_vault)
mpt::TempDir cache_dir;
mpt::TempDir data_dir;
std::vector hosts;
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
auto vault = factory.create_image_vault(hosts, &stub_downloader, cache_dir.path(), data_dir.path(), mp::days{0});
@@ -113,7 +116,7 @@ TEST_F(BaseFactory, create_image_vault_returns_default_vault)
TEST_F(BaseFactory, networks_throws)
{
- StrictMock factory;
+ StrictMock factory{az_manager};
ASSERT_THROW(factory.mp::BaseVirtualMachineFactory::networks(), mp::NotImplementedOnThisBackendException);
}
@@ -123,7 +126,7 @@ TEST_F(BaseFactory, networks_throws)
// at this time. Instead, just make sure an ISO image is created and has the expected path.
TEST_F(BaseFactory, creates_cloud_init_iso_image)
{
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
const std::string name{"foo"};
const YAML::Node metadata{YAML::Load({fmt::format("name: {}", name)})}, vendor_data{metadata}, user_data{metadata},
network_data{metadata};
@@ -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",
@@ -153,7 +157,7 @@ TEST_F(BaseFactory, creates_cloud_init_iso_image)
TEST_F(BaseFactory, create_bridge_not_implemented)
{
- StrictMock factory;
+ StrictMock factory{az_manager};
MP_EXPECT_THROW_THAT(factory.base_create_bridge_with({}), mp::NotImplementedOnThisBackendException,
mpt::match_what(HasSubstr("bridge creation")));
@@ -161,7 +165,7 @@ TEST_F(BaseFactory, create_bridge_not_implemented)
TEST_F(BaseFactory, prepareNetworkingHasNoObviousEffectByDefault)
{
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
EXPECT_CALL(factory, prepare_networking).WillOnce(Invoke([&factory](auto& nets) {
factory.mp::BaseVirtualMachineFactory::prepare_networking(nets);
@@ -176,7 +180,7 @@ TEST_F(BaseFactory, prepareNetworkingHasNoObviousEffectByDefault)
TEST_F(BaseFactory, prepareInterfaceLeavesUnrecognizedNetworkAlone)
{
- StrictMock factory;
+ StrictMock factory{az_manager};
auto host_nets = std::vector{{"eth0", "ethernet", "asd"}, {"wlan0", "wifi", "asd"}};
auto extra_net = mp::NetworkInterface{"eth1", "fa:se:ma:c0:12:23", false};
@@ -190,7 +194,7 @@ TEST_F(BaseFactory, prepareInterfaceLeavesUnrecognizedNetworkAlone)
TEST_F(BaseFactory, prepareInterfaceLeavesExistingBridgeAlone)
{
- StrictMock factory;
+ StrictMock factory{az_manager};
constexpr auto bridge_type = "arbitrary";
auto [mock_platform, platform_guard] = mpt::MockPlatform::inject();
@@ -208,7 +212,7 @@ TEST_F(BaseFactory, prepareInterfaceLeavesExistingBridgeAlone)
TEST_F(BaseFactory, prepareInterfaceReplacesBridgedNetworkWithCorrespondingBridge)
{
- StrictMock factory;
+ StrictMock factory{az_manager};
constexpr auto bridge_type = "tunnel";
constexpr auto bridge = "br";
@@ -232,7 +236,7 @@ TEST_F(BaseFactory, prepareInterfaceReplacesBridgedNetworkWithCorrespondingBridg
TEST_F(BaseFactory, prepareInterfaceCreatesBridgeForUnbridgedNetwork)
{
- StrictMock factory;
+ StrictMock factory{az_manager};
constexpr auto bridge_type = "gagah";
constexpr auto bridge = "newbr";
@@ -268,7 +272,7 @@ TEST_F(BaseFactory, prepareInterfaceCreatesBridgeForUnbridgedNetwork)
TEST_F(BaseFactory, prepareNetworkingWithNoExtraNetsHasNoObviousEffect)
{
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
MP_DELEGATE_MOCK_CALLS_ON_BASE(factory, prepare_networking, mp::BaseVirtualMachineFactory);
std::vector empty;
@@ -289,7 +293,7 @@ TEST_F(BaseFactory, prepareNetworkingPreparesEachRequestedNetwork)
{"aaa", "alpha", true}, {"bbb", "beta", false}, {"br", "bridge", true}, {"brr", "bridge", false}};
const auto num_nets = extra_nets.size();
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
EXPECT_CALL(factory, networks).WillOnce(Return(host_nets));
MP_DELEGATE_MOCK_CALLS_ON_BASE(factory, prepare_networking, mp::BaseVirtualMachineFactory);
@@ -303,7 +307,7 @@ TEST_F(BaseFactory, prepareNetworkingPreparesEachRequestedNetwork)
TEST_F(BaseFactory, factoryHasDefaultSuspendSupport)
{
- MockBaseFactory factory;
+ MockBaseFactory factory{az_manager};
EXPECT_NO_THROW(factory.mp::BaseVirtualMachineFactory::require_suspend_support());
}
} // namespace
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;