Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix get local.<instance>.bridged command #3357

Merged
merged 8 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/multipass/virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class VirtualMachineFactory : private DisabledCopyMove
// List all the network interfaces seen by the backend.
virtual std::vector<NetworkInterfaceInfo> networks() const = 0;
virtual void require_snapshots_support() const = 0;
virtual std::string bridge_name_for(const std::string& iface_name) const = 0;

protected:
VirtualMachineFactory() = default;
Expand Down
5 changes: 5 additions & 0 deletions src/daemon/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,7 @@
const std::unordered_set<std::string>& preparing_instances,
std::function<void()> instance_persister,
std::function<std::string()> bridged_interface,
std::function<std::string()> bridge_name,
std::function<std::vector<mp::NetworkInterfaceInfo>()> host_networks)
{
return MP_SETTINGS.register_handler(std::make_unique<mp::InstanceSettingsHandler>(vm_instance_specs,
Expand All @@ -1247,6 +1248,7 @@
preparing_instances,
std::move(instance_persister),
std::move(bridged_interface),
std::move(bridge_name),
host_networks));
}

Expand Down Expand Up @@ -1405,6 +1407,9 @@
preparing_instances,
[this] { persist_instances(); },
get_bridged_interface_name,
[this]() {
return config->factory->bridge_name_for(MP_SETTINGS.get(mp::bridged_interface_key).toStdString());

Check warning on line 1411 in src/daemon/daemon.cpp

View check run for this annotation

Codecov / codecov/patch

src/daemon/daemon.cpp#L1410-L1411

Added lines #L1410 - L1411 were not covered by tests
},
[this]() { return config->factory->networks(); })},
snapshot_mod_handler{
register_snapshot_mod(operative_instances, deleted_instances, preparing_instances, *config->factory)}
Expand Down
22 changes: 15 additions & 7 deletions src/daemon/instance_settings_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,25 @@ void update_disk(const QString& key, const QString& val, mp::VirtualMachine& ins
}
}

bool is_bridged(const mp::VMSpecs& spec, const std::string& br_interface)
bool is_bridged(const mp::VMSpecs& spec, const std::string& br_interface, const std::string& br_name)
{
return std::any_of(spec.extra_interfaces.cbegin(),
spec.extra_interfaces.cend(),
[&br_interface](const auto& network) -> bool { return network.id == br_interface; });
[&br_interface, &br_name](const auto& network) -> bool {
return network.id == br_interface || network.id == br_name;
});
}

void checked_update_bridged(const QString& key,
const QString& val,
mp::VirtualMachine& instance,
mp::VMSpecs& spec,
const std::string& br_interface)
const std::string& br_interface,
const std::string& br_name)
{
auto bridged = mp::BoolSettingSpec{key, "false"}.interpret(val) == "true";

if (!bridged && is_bridged(spec, br_interface))
if (!bridged && is_bridged(spec, br_interface, br_name))
{
throw mp::InvalidSettingException{key, val, "Bridged interface cannot be removed"};
}
Expand All @@ -189,6 +192,7 @@ void update_bridged(const QString& key,
mp::VirtualMachine& instance,
mp::VMSpecs& spec,
std::function<std::string()> bridged_interface,
std::function<std::string()> bridge_name,
std::function<std::vector<mp::NetworkInterfaceInfo>()> host_networks)
{
const auto& host_nets = host_networks(); // This will throw if not implemented on this backend.
Expand All @@ -204,7 +208,7 @@ void update_bridged(const QString& key,
mp::bridged_interface_key));
}

checked_update_bridged(key, val, instance, spec, br);
checked_update_bridged(key, val, instance, spec, br, bridge_name());
}

} // namespace
Expand All @@ -222,13 +226,15 @@ mp::InstanceSettingsHandler::InstanceSettingsHandler(
const std::unordered_set<std::string>& preparing_instances,
std::function<void()> instance_persister,
std::function<std::string()> bridged_interface,
std::function<std::string()> bridge_name,
std::function<std::vector<NetworkInterfaceInfo>()> host_networks)
: vm_instance_specs{vm_instance_specs},
operative_instances{operative_instances},
deleted_instances{deleted_instances},
preparing_instances{preparing_instances},
instance_persister{std::move(instance_persister)},
bridged_interface{std::move(bridged_interface)},
bridge_name{std::move(bridge_name)},
host_networks{std::move(host_networks)}
{
}
Expand All @@ -251,7 +257,9 @@ QString mp::InstanceSettingsHandler::get(const QString& key) const
const auto& spec = find_spec(instance_name);

if (property == bridged_suffix)
return is_bridged(spec, bridged_interface()) ? "true" : "false";
{
return is_bridged(spec, bridged_interface(), bridge_name()) ? "true" : "false";
}
if (property == cpus_suffix)
return QString::number(spec.num_cores);
if (property == mem_suffix)
Expand All @@ -277,7 +285,7 @@ void mp::InstanceSettingsHandler::set(const QString& key, const QString& val)
update_cpus(key, val, instance, spec);
else if (property == bridged_suffix)
{
update_bridged(key, val, instance, spec, bridged_interface, host_networks);
update_bridged(key, val, instance, spec, bridged_interface, bridge_name, host_networks);
}
else
{
Expand Down
2 changes: 2 additions & 0 deletions src/daemon/instance_settings_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class InstanceSettingsHandler : public SettingsHandler
const std::unordered_set<std::string>& preparing_instances,
std::function<void()> instance_persister,
std::function<std::string()> bridged_interface,
std::function<std::string()> bridge_name,
std::function<std::vector<NetworkInterfaceInfo>()> host_networks);

std::set<QString> keys() const override;
Expand All @@ -63,6 +64,7 @@ class InstanceSettingsHandler : public SettingsHandler
const std::unordered_set<std::string>& preparing_instances;
std::function<void()> instance_persister;
std::function<std::string()> bridged_interface;
std::function<std::string()> bridge_name;
std::function<std::vector<NetworkInterfaceInfo>()> host_networks;
};

Expand Down
5 changes: 5 additions & 0 deletions src/platform/backends/lxd/lxd_virtual_machine_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ auto mp::LXDVirtualMachineFactory::networks() const -> std::vector<NetworkInterf
return ret;
}

std::string mp::LXDVirtualMachineFactory::bridge_name_for(const std::string& iface_name) const
{
return MP_BACKEND.bridge_name(iface_name);
};

void mp::LXDVirtualMachineFactory::prepare_networking(std::vector<NetworkInterface>& extra_interfaces)
{
prepare_networking_guts(extra_interfaces, "bridge");
Expand Down
2 changes: 2 additions & 0 deletions src/platform/backends/lxd/lxd_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory

std::vector<NetworkInterfaceInfo> networks() const override;

std::string bridge_name_for(const std::string& iface_name) const override;

protected:
void remove_resources_for_impl(const std::string& name) override;
std::string create_bridge_with(const NetworkInterfaceInfo& interface) override;
Expand Down
5 changes: 5 additions & 0 deletions src/platform/backends/shared/base_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory

void require_snapshots_support() const override;

std::string bridge_name_for(const std::string& iface_name) const override
{
return "";
};

protected:
static const Path instances_subdir;

Expand Down
12 changes: 10 additions & 2 deletions src/platform/backends/shared/linux/backend_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,22 @@ std::string mp::backend::generate_random_subnet()
throw std::runtime_error("Could not determine a subnet for networking.");
}

std::string mp::Backend::bridge_name(const std::string& interface) const
{
static const std::string base_name{"br-"};

static const auto max_iface_length{max_bridge_name_len - base_name.size()};

return fmt::format("{}{:.{}}", base_name, interface, max_iface_length);
}

// @precondition no bridge exists for this interface
// @precondition interface identifies an ethernet device
std::string mp::Backend::create_bridge_with(const std::string& interface)
{
static constexpr auto log_category_create = "create bridge";
static constexpr auto log_category_rollback = "rollback bridge";
static const auto root_path = QDBusObjectPath{"/"};
static const auto base_name = QStringLiteral("br-");

static std::once_flag once;
std::call_once(once, [] { qDBusRegisterMetaType<VariantMapMap>(); });
Expand All @@ -212,7 +220,7 @@ std::string mp::Backend::create_bridge_with(const std::string& interface)
auto nm_root = get_checked_interface(system_bus, nm_bus_name, nm_root_obj, nm_root_ifc);
auto nm_settings = get_checked_interface(system_bus, nm_bus_name, nm_settings_obj, nm_settings_ifc);

auto parent_name = (base_name + interface.c_str()).left(max_bridge_name_len);
auto parent_name = QString::fromStdString(bridge_name(interface));
auto child_name = parent_name + "-child";
mpl::log(mpl::Level::debug, log_category_create, fmt::format("Creating bridge: {}", parent_name));

Expand Down
1 change: 1 addition & 0 deletions src/platform/backends/shared/linux/backend_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class Backend : public Singleton<Backend>
public:
using Singleton<Backend>::Singleton;

virtual std::string bridge_name(const std::string& interface) const;
virtual std::string create_bridge_with(const std::string& interface);
virtual std::string get_subnet(const Path& network_dir, const QString& bridge_name) const;

Expand Down
18 changes: 18 additions & 0 deletions tests/lxd/test_lxd_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2355,3 +2355,21 @@ TEST_F(LXDBackend, addsNetworkInterface)

EXPECT_EQ(times_called, 1u);
}

struct LXDNetworkNameTestSuite : LXDBackend, WithParamInterface<std::pair<std::string, std::string>>
{
};

TEST_P(LXDNetworkNameTestSuite, backendReturnsCorrectBridgeName)
{
const auto [name, ret] = GetParam();

CustomLXDFactory factory{std::move(mock_network_access_manager), data_dir.path(), base_url};

EXPECT_EQ(factory.bridge_name_for(name), ret);
}

INSTANTIATE_TEST_SUITE_P(LXDBackend,
LXDNetworkNameTestSuite,
Values(std::make_pair("enp4s0", "br-enp4s0"),
std::make_pair("enx586d8fd35b6c", "br-enx586d8fd35")));
1 change: 1 addition & 0 deletions tests/mock_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory
MOCK_METHOD(void, configure, (VirtualMachineDescription&), (override));
MOCK_METHOD(std::vector<NetworkInterfaceInfo>, networks, (), (const, override));
MOCK_METHOD(void, require_snapshots_support, (), (const, override));
MOCK_METHOD(std::string, bridge_name_for, (const std::string&), (const, override));

// originally protected:
MOCK_METHOD(std::string, create_bridge_with, (const NetworkInterfaceInfo&), (override));
Expand Down
13 changes: 13 additions & 0 deletions tests/test_base_virtual_machine_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,19 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory
MOCK_METHOD(QString, get_backend_version_string, (), (const, override));
MOCK_METHOD(void, prepare_networking, (std::vector<mp::NetworkInterface>&), (override));
MOCK_METHOD(std::vector<mp::NetworkInterfaceInfo>, networks, (), (const, override));
MOCK_METHOD(std::string, bridge_name_for, (const std::string&), (const, override));
MOCK_METHOD(std::string, create_bridge_with, (const mp::NetworkInterfaceInfo&), (override));
MOCK_METHOD(void, prepare_interface,
(mp::NetworkInterface & net, std::vector<mp::NetworkInterfaceInfo>& host_nets,
const std::string& bridge_type),
(override));
MOCK_METHOD(void, remove_resources_for_impl, (const std::string&), (override));

std::string base_bridge_name_for(const std::string& iface_name) const
{
return mp::BaseVirtualMachineFactory::bridge_name_for(iface_name);
}

std::string base_create_bridge_with(const mp::NetworkInterfaceInfo& interface)
{
return mp::BaseVirtualMachineFactory::create_bridge_with(interface); // protected
Expand Down Expand Up @@ -155,6 +161,13 @@ TEST_F(BaseFactory, creates_cloud_init_iso_image)
EXPECT_TRUE(QFile::exists(vm_desc.cloud_init_iso));
}

TEST_F(BaseFactory, baseBridgeNameForReturnsEmpty)
{
StrictMock<MockBaseFactory> factory;

ASSERT_EQ(factory.base_bridge_name_for("any_interface"), "");
}

TEST_F(BaseFactory, create_bridge_not_implemented)
{
StrictMock<MockBaseFactory> factory;
Expand Down
11 changes: 10 additions & 1 deletion tests/test_instance_settings_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct TestInstanceSettingsHandler : public Test
preparing_vms,
make_fake_persister(),
make_fake_bridged_interface(),
make_fake_bridge_name(),
make_fake_host_networks()};
}

Expand All @@ -76,6 +77,11 @@ struct TestInstanceSettingsHandler : public Test
return [] { return "eth8"; };
}

std::function<std::string()> make_fake_bridge_name()
{
return [] { return "br-eth8"; };
}

std::function<std::vector<mp::NetworkInterfaceInfo>()> make_fake_host_networks()
{
std::vector<mp::NetworkInterfaceInfo> ret;
Expand Down Expand Up @@ -235,7 +241,10 @@ TEST_P(TestBridgedInstanceSettings, getFetchesBridged)

INSTANTIATE_TEST_SUITE_P(getFetchesBridged,
TestBridgedInstanceSettings,
Values(std::make_pair("eth8", true), std::make_pair("eth9", false)));
Values(std::make_pair("eth8", true),
std::make_pair("br-eth8", true),
std::make_pair("eth9", false),
std::make_pair("br-eth9", false)));

TEST_F(TestInstanceSettingsHandler, getFetchesPropertiesOfInstanceInSpecialState)
{
Expand Down