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

Create and use bridge #2078

Merged
merged 22 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6d86dc4
[backend] Add an empty `prepare_networking` step
ricab Apr 28, 2021
a8055ed
[daemon] Call new networking preparation
ricab Apr 30, 2021
217e659
[lxd] Add abstract network preparing logic
ricab Apr 29, 2021
c00ba93
[lxd] Use bridge with target net if there is one
ricab Apr 28, 2021
6e00ee7
[linux] Return name of newly created bridge
ricab Apr 29, 2021
a7547ec
[test] Check returned name of created bridges
ricab Apr 29, 2021
999aae2
[test] Replace "wlan" in fake interface name
ricab Apr 29, 2021
409ab9f
[lxd] Create bridges with ethernet devices
ricab Apr 29, 2021
722d212
[linux] Record bridge members
ricab Apr 29, 2021
cfaac36
[lxd] Move link finding to net info struct
ricab Apr 29, 2021
50d543a
[backend] Comment out unused parameter
ricab May 5, 2021
2306e37
[linux] Stop returning empty network structs
ricab May 5, 2021
294b76c
[linux] Filter unrecognized nets from bridge links
ricab May 5, 2021
233189f
[linux] Describe bridges after filtering links
ricab May 5, 2021
425a394
[test] Account for filtering in bridge member test
ricab May 5, 2021
2492aea
[test] Improve var naming in test
ricab May 5, 2021
77658bc
[test] Fix buggy param in test instantiation
ricab May 5, 2021
cece802
[test] Cover link filtering in bridge member tests
ricab May 5, 2021
d430924
[linux] Outline lambda, for readability
ricab May 5, 2021
8446c21
[linux] Produce bridge descriptions in a single go
ricab May 6, 2021
6b5f691
[linux] Stop describing bridges as empty
ricab May 6, 2021
a21957d
[backends] Explain empty base factory impl
ricab May 6, 2021
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
13 changes: 12 additions & 1 deletion include/multipass/network_interface_info.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 Canonical, Ltd.
* Copyright (C) 2020-2021 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
Expand All @@ -21,13 +21,24 @@
#include <multipass/ip_address.h>
#include <multipass/optional.h>

#include <algorithm>
#include <string>
#include <vector>

namespace multipass
{
struct NetworkInterfaceInfo
{
bool has_link(const std::string& net) const
{
auto end = links.cend();
return std::find(links.cbegin(), end, net) != end;
}

std::string id;
std::string type;
std::string description;
std::vector<std::string> links = {}; // default initializer allows aggregate init of the other 3
};
} // namespace multipass
#endif // MULTIPASS_NETWORK_INTERFACE_INFO_H
2 changes: 2 additions & 0 deletions include/multipass/virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class URLDownloader;
class VirtualMachineDescription;
class VMImageHost;
class VMStatusMonitor;
struct NetworkInterface;
struct NetworkInterfaceInfo;

class VirtualMachineFactory
Expand All @@ -53,6 +54,7 @@ class VirtualMachineFactory
virtual void remove_resources_for(const std::string& name) = 0;

virtual FetchType fetch_type() = 0;
virtual void prepare_networking(std::vector<NetworkInterface>& extra_interfaces) = 0; // note the arg may be updated
virtual VMImage prepare_source_image(const VMImage& source_image) = 0;
virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0;
virtual void hypervisor_health_check() = 0;
Expand Down
2 changes: 2 additions & 0 deletions src/daemon/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2141,6 +2141,8 @@ void mp::Daemon::create_vm(const CreateRequest* request, grpc::ServerWriter<Crea
reply.set_create_message("Configuring " + name);
server->Write(reply);

config->factory->prepare_networking(checked_args.extra_interfaces);

// This set stores the MAC's which need to be in the allocated_mac_addrs if everything goes well.
auto new_macs = allocated_mac_addrs;

Expand Down
24 changes: 23 additions & 1 deletion src/platform/backends/lxd/lxd_virtual_machine_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
#include "lxd_virtual_machine.h"
#include "lxd_vm_image_vault.h"

#include <shared/linux/backend_utils.h>

#include <multipass/exceptions/local_socket_connection_exception.h>
#include <multipass/format.h>
#include <multipass/logging/log.h>
#include <multipass/network_interface.h>
#include <multipass/network_interface_info.h>
#include <multipass/platform.h>
#include <multipass/utils.h>
Expand All @@ -36,6 +39,7 @@ namespace
{
constexpr auto category = "lxd factory";
const QString multipass_bridge_name = "mpbr0";

} // namespace

mp::LXDVirtualMachineFactory::LXDVirtualMachineFactory(NetworkAccessManager::UPtr manager, const mp::Path& data_dir,
Expand Down Expand Up @@ -173,7 +177,8 @@ auto mp::LXDVirtualMachineFactory::networks() const -> std::vector<NetworkInterf
auto description = lxd_description.isEmpty() ? std::move(platform_it->second.description)
: lxd_description.toStdString();

ret.push_back({std::move(id), std::move(type), std::move(description)});
ret.push_back({std::move(id), std::move(type), std::move(description),
std::move(platform_it->second.links)});

platform_networks.erase(platform_it); // prevent matching with this network again
}
Expand All @@ -184,3 +189,20 @@ auto mp::LXDVirtualMachineFactory::networks() const -> std::vector<NetworkInterf

return ret;
}

void mp::LXDVirtualMachineFactory::prepare_networking(std::vector<NetworkInterface>& extra_interfaces)
{
auto host_nets = networks();
for (auto& net : extra_interfaces)
{
auto it = std::find_if(host_nets.cbegin(), host_nets.cend(),
[&net](const mp::NetworkInterfaceInfo& info) { return info.id == net.id; });
if (it != host_nets.end() && it->type == "ethernet")
{
it = std::find_if(host_nets.cbegin(), host_nets.cend(), [&net](const mp::NetworkInterfaceInfo& info) {
return info.type == "bridge" && info.has_link(net.id);
});
net.id = it != host_nets.cend() ? it->id : mp::backend::create_bridge_with(net.id);
}
}
}
1 change: 1 addition & 0 deletions src/platform/backends/lxd/lxd_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class LXDVirtualMachineFactory final : public BaseVirtualMachineFactory
explicit LXDVirtualMachineFactory(NetworkAccessManager::UPtr manager, const Path& data_dir,
const QUrl& base_url = lxd_socket_url);

void prepare_networking(std::vector<NetworkInterface>& extra_interfaces) override;
VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc,
VMStatusMonitor& monitor) override;
void remove_resources_for(const std::string& name) 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 @@ -44,6 +44,11 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory
return {};
};

void prepare_networking(std::vector<NetworkInterface>& /*extra_interfaces*/) override
{
// only certain backends need to do anything to prepare networking
}

VMImageVault::UPtr create_image_vault(std::vector<VMImageHost*> image_hosts, URLDownloader* downloader,
const Path& cache_dir_path, const Path& data_dir_path,
const days& days_to_expire) override
Expand Down
8 changes: 6 additions & 2 deletions src/platform/backends/shared/linux/backend_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ void mp::backend::check_if_kvm_is_in_use()

// @precondition no bridge exists for this interface
// @precondition interface identifies an ethernet device
void mp::backend::create_bridge_with(const std::string& interface)
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";
Expand Down Expand Up @@ -359,7 +359,11 @@ void mp::backend::create_bridge_with(const std::string& interface)
for '/' to signal null `device` and `specific-object` derived from nmcli and libnm. See https://bit.ly/3dMA3QB */

rollback_guard.dismiss(); // we succeeded!
mpl::log(mpl::Level::info, log_category_create, fmt::format("Created bridge: {}", parent_name));

auto ret = parent_name.toStdString();
mpl::log(mpl::Level::info, log_category_create, fmt::format("Created bridge: {}", ret));

return ret;
}

mp::backend::CreateBridgeException::CreateBridgeException(const std::string& detail, const QDBusError& dbus_error,
Expand Down
2 changes: 1 addition & 1 deletion src/platform/backends/shared/linux/backend_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Path convert_to_qcow_if_necessary(const Path& image_path);
QString cpu_arch();
void check_for_kvm_support();
void check_if_kvm_is_in_use();
void create_bridge_with(const std::string& interface);
std::string create_bridge_with(const std::string& interface);

class CreateBridgeException : public std::runtime_error
{
Expand Down
50 changes: 38 additions & 12 deletions src/platform/platform_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,25 +110,47 @@ bool is_ethernet(const QDir& net_dir)
get_net_devtype(net_dir).isEmpty();
}

mp::NetworkInterfaceInfo get_network(const QDir& net_dir)
mp::optional<mp::NetworkInterfaceInfo> get_network(const QDir& net_dir)
{
static const auto bridge_fname = QStringLiteral("brif");
auto id = net_dir.dirName().toStdString();

std::string type, description;
if (auto bridge = "bridge"; net_dir.exists(bridge))
{
type = bridge;
std::vector<std::string> links;
QStringList bridge_members = QDir{net_dir.filePath(bridge_fname)}.entryList(QDir::NoDotAndDotDot | QDir::Dirs);
description = bridge_members.isEmpty() ? "Empty network bridge"
: fmt::format("Network bridge with {}", bridge_members.join(", "));

links.reserve(bridge_members.size());
std::transform(bridge_members.cbegin(), bridge_members.cend(), std::back_inserter(links),
[](const QString& interface) { return interface.toStdString(); });
Saviq marked this conversation as resolved.
Show resolved Hide resolved

return {{std::move(id), bridge, /*description=*/"", std::move(links)}}; // description needs updating with links
}
else if (is_ethernet(net_dir))
return {{std::move(id), "ethernet", "Ethernet device"}};

return mp::nullopt;
}

void update_bridges(std::map<std::string, mp::NetworkInterfaceInfo>& networks)
{
for (auto& item : networks)
{
type = "ethernet";
description = "Ethernet device";
if (auto& net = item.second; net.type == "bridge")
{ // bridge descriptions and links depend on what other networks we recognized
auto& links = net.links;
auto is_unknown = [&networks](const std::string& id) {
auto same_as = [&id](const auto& other) { return other.first == id; };
return std::find_if(networks.cbegin(), networks.cend(), same_as) == networks.cend();
};
links.erase(std::remove_if(links.begin(), links.end(), is_unknown),
links.end()); // filter links to networks we don't recognize

net.description =
links.empty() ? "Network bridge"
: fmt::format("Network bridge with {}", fmt::join(links.cbegin(), links.cend(), ", "));
}
}

return mp::NetworkInterfaceInfo{net_dir.dirName().toStdString(), std::move(type), std::move(description)};
}
} // namespace

Expand Down Expand Up @@ -160,11 +182,15 @@ auto mp::platform::detail::get_network_interfaces_from(const QDir& sys_dir)
auto ifaces_info = std::map<std::string, mp::NetworkInterfaceInfo>();
for (const auto& entry : sys_dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs))
{
auto iface = get_network(QDir{sys_dir.filePath(entry)});
auto name = iface.id; // (can't rely on param evaluation order)
ifaces_info.emplace(std::move(name), std::move(iface));
if (auto iface = get_network(QDir{sys_dir.filePath(entry)}); iface)
{
auto name = iface->id; // (can't rely on param evaluation order)
ifaces_info.emplace(std::move(name), std::move(*iface));
}
}

update_bridges(ifaces_info);

return ifaces_info;
}

Expand Down
19 changes: 9 additions & 10 deletions tests/linux/test_backend_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ struct CreateBridgeTest : public Test
QMap<QString, QVariantMap> outer_map;
QMap<QString, QVariantMap>::const_iterator outer_it;
QVariantMap::const_iterator inner_it;
QString parent_name = get_bridge_name(child).left(15);
QString parent_name = get_bridge_name(child);

return (outer_map = arg.value<QMap<QString, QVariantMap>>()).size() == 2 &&
(outer_it = outer_map.find("connection")) != outer_map.end() && outer_it->size() == 3 &&
Expand All @@ -330,7 +330,7 @@ struct CreateBridgeTest : public Test
QMap<QString, QVariantMap> outer_map;
QMap<QString, QVariantMap>::const_iterator outer_it;
QVariantMap::const_iterator inner_it;
QString parent_name = get_bridge_name(child).left(15);
QString parent_name = get_bridge_name(child);
QString child_name = parent_name + "-child";

return (outer_map = arg.value<QMap<QString, QVariantMap>>()).size() == 1 &&
Expand All @@ -350,6 +350,11 @@ struct CreateBridgeTest : public Test
Property(&QDBusObjectPath::path, Property(&QString::toStdString, Eq(path))));
}

static QString get_bridge_name(const char* child)
{
return (QString{"br-"} + child).left(15);
}

MockDBusProvider::GuardedMock mock_dbus_injection = MockDBusProvider::inject();
MockDBusProvider* mock_dbus_provider = mock_dbus_injection.first;
MockDBusConnection mock_bus{};
Expand All @@ -358,17 +363,11 @@ struct CreateBridgeTest : public Test
mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject();

const QVariant empty{};

private:
static QString get_bridge_name(const char* child)
{
return QString{"br-"} + child;
}
};

TEST_F(CreateBridgeTest, creates_and_activates_connections) // success case
{
static constexpr auto network = "wlan1234567890a";
static constexpr auto network = "eth1234567890a";
static constexpr auto child_obj_path = "/an/obj/path/for/child";
static constexpr auto null_obj_path = "/";

Expand All @@ -390,7 +389,7 @@ TEST_F(CreateBridgeTest, creates_and_activates_connections) // success case
}

inject_dbus_interfaces();
EXPECT_NO_THROW(mp::backend::create_bridge_with(network));
EXPECT_EQ(mp::backend::create_bridge_with(network), get_bridge_name(network).toStdString());
}

TEST_F(CreateBridgeTest, throws_if_bus_disconnected)
Expand Down
Loading