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

Add network bridges #3195

Merged
merged 54 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
8b60596
[tests] Check bridge prompter
luis4a0 Jul 25, 2023
ef069a0
[daemon] Add local.instance.bridged setting.
luis4a0 Nov 9, 2023
9e9803d
[cli] Refactor bridging prompt into a new function
luis4a0 Nov 7, 2023
29aa95d
[tests] Check set local.instance.bridged.
luis4a0 Sep 13, 2023
9640605
[client] Move bridge prompt to client_common
luis4a0 Nov 7, 2023
3c374f6
[settings] Fix `get local.<instance>.bridged`.
luis4a0 Nov 9, 2023
f97c3fd
[vm] Function to add a new interface to a VM.
luis4a0 Nov 9, 2023
fa1a315
[tests] Add new function to mock and stub VM.
luis4a0 Nov 9, 2023
c51aaa9
[lxd] Implement function to add interface.
luis4a0 Sep 18, 2023
5a96c90
[daemon] Configure new bridged interfaces at boot.
luis4a0 Sep 27, 2023
6e89610
[settings handler] Configure new interface bridge.
luis4a0 Sep 18, 2023
6818ca6
[tests] Add SSH premocking to daemon test fixture.
luis4a0 Sep 28, 2023
805dfc0
[daemon] Configure new bridged interfaces.
luis4a0 Sep 18, 2023
0c5ed15
[tests] Mock SSH execution when adding interfaces.
luis4a0 Sep 28, 2023
971b7a7
[tests] Check interface creation at VM start.
luis4a0 Sep 18, 2023
2d9d368
[daemon] Catch exception when SSH execution fails.
luis4a0 Sep 29, 2023
a2db4de
[tests] Check BaseVM::add_network_interface().
luis4a0 Sep 18, 2023
c05abe2
[tests] Check failures configuring interfaces.
luis4a0 Sep 29, 2023
ea6d09d
[tests] Check settings handler adds interface.
luis4a0 Sep 19, 2023
e7aa698
[daemon] Show warnings to the user.
luis4a0 Oct 3, 2023
92eaa6a
[tests] Check LXD correctly adds interface.
luis4a0 Sep 20, 2023
2088aa9
[tests] Check `get local.<instance>.bridged`.
luis4a0 Sep 22, 2023
2a95c33
[tests] Check configuration warnings.
luis4a0 Oct 3, 2023
298197d
[tests] Fix test of launch with bridges.
luis4a0 Sep 22, 2023
96dd3bb
Merge pull request #3193 from canonical/add-bridged-setting
luis4a0 Nov 9, 2023
e6ced1f
[daemon] Refactor running commands at boot.
luis4a0 Oct 5, 2023
f45ef96
Merge branch 'add-lxd-interfaces' into add-network-bridges
luis4a0 Nov 10, 2023
e78aa67
Merge pull request #3238 from canonical/configure-new-interfaces-at-boot
luis4a0 Nov 10, 2023
c316e65
Fix the formatting of the branch
luis4a0 Nov 7, 2023
bdaae9f
[tests] Update signature of VM function.
luis4a0 Oct 28, 2023
fb24b47
[ssh] Add exception for execution failure.
luis4a0 Oct 28, 2023
3937738
[utils] Use SSHExecFailure exception.
luis4a0 Oct 28, 2023
1f961d8
[daemon] Warn about bridge failure only if needed.
luis4a0 Oct 28, 2023
9c95d24
[qemu][linux] Throw when bridging.
luis4a0 Oct 19, 2023
9c2df22
[cli] Show warning only once.
luis4a0 Oct 29, 2023
ad4a958
[tests] Adapt QEMU platform mock to new interface.
luis4a0 Nov 7, 2023
0065cee
[daemon] Improve error reporting.
luis4a0 Oct 30, 2023
9f9287a
[daemon] Run commands at boot only if needed.
luis4a0 Oct 19, 2023
7eeaaa8
[tests] Check throwing when opening a SSH session.
luis4a0 Oct 30, 2023
507a4cc
[tests][qemu][linux] Adding interface must throw.
luis4a0 Oct 20, 2023
74fcbf7
Merge pull request #3276 from canonical/message-only-on-ssh-exec-error
luis4a0 Nov 10, 2023
b613e4c
Merge pull request #3266 from canonical/qemu-macos-add-bridged-interf…
luis4a0 Nov 10, 2023
035499b
Address reviews.
luis4a0 Nov 17, 2023
c3a6d48
[factory] Add can_add_bridges().
luis4a0 Nov 23, 2023
9d13219
[lxd] Add can_add_bridges().
luis4a0 Nov 23, 2023
6e3bfbf
[daemon] Refuse adding bridge when calling `set`.
luis4a0 Nov 23, 2023
4d8540a
[daemon] Move run_at_boot to VMSpec.
luis4a0 Nov 23, 2023
ae49b97
[daemon] Configure bridge as early as possible.
luis4a0 Nov 27, 2023
5f253e3
[daemon] Properly clear run_at_boot after config.
luis4a0 Nov 27, 2023
1312697
Address reviews #2.
luis4a0 Nov 30, 2023
2d7427c
[tests][factory] Remove can_add_bridges().
luis4a0 Nov 30, 2023
f4eeb9f
[cli] Use mp::client:: namespace for *_answer.
luis4a0 Dec 4, 2023
f36824a
[daemon] Make vm_specs a reference.
luis4a0 Dec 5, 2023
89f1b78
[daemon] Filter out empty MAC addresses.
luis4a0 Dec 7, 2023
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
3 changes: 3 additions & 0 deletions include/multipass/cli/client_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <multipass/ssl_cert_provider.h>

#include <memory>
#include <regex>
#include <string>

namespace multipass
Expand Down Expand Up @@ -74,6 +75,8 @@ void set_logger();
void set_logger(multipass::logging::Level verbosity); // full param qualification makes sure msvc is happy
void pre_setup();
void post_setup();
const std::regex yes_answer{"y|yes", std::regex::icase | std::regex::optimize};
const std::regex no_answer{"n|no", std::regex::icase | std::regex::optimize};
}
} // namespace multipass
#endif // MULTIPASS_CLIENT_COMMON_H
16 changes: 16 additions & 0 deletions include/multipass/cli/prompters.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <multipass/terminal.h>

#include <string>
#include <vector>

#ifndef MULTIPASS_CLI_PROMPTERS_H
#define MULTIPASS_CLI_PROMPTERS_H
Expand Down Expand Up @@ -88,6 +89,21 @@ class NewPassphrasePrompter : public PassphrasePrompter

std::string prompt(const std::string& text = "Please re-enter passphrase") const override;
};

class BridgePrompter : private DisabledCopyMove
{
public:
explicit BridgePrompter(Terminal* term) : term(term){};

~BridgePrompter() = default;

bool bridge_prompt(const std::vector<std::string>& nets_need_bridging) const;

private:
BridgePrompter() = default;

Terminal* term;
};
} // namespace multipass

#endif // MULTIPASS_CLI_PROMPTERS_H
8 changes: 8 additions & 0 deletions include/multipass/exceptions/ssh_exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,13 @@ class SSHException : public std::runtime_error
{
}
};

class SSHExecFailure : public SSHException
{
public:
SSHExecFailure(const std::string& what_arg) : SSHException(what_arg)
{
}
};
} // namespace multipass
#endif // MULTIPASS_SSH_EXCEPTION_H
2 changes: 2 additions & 0 deletions include/multipass/virtual_machine.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "disabled_copy_move.h"
#include "ip_address.h"
#include "network_interface.h"
#include "path.h"

#include <QDir>
Expand Down Expand Up @@ -83,6 +84,7 @@ class VirtualMachine : private DisabledCopyMove
virtual void update_cpus(int num_cores) = 0;
virtual void resize_memory(const MemorySize& new_size) = 0;
virtual void resize_disk(const MemorySize& new_size) = 0;
virtual void add_network_interface(int index, const NetworkInterface& net) = 0;
virtual std::unique_ptr<MountHandler> make_native_mount_handler(const SSHKeyProvider* ssh_key_provider,
const std::string& target,
const VMMount& mount) = 0;
Expand Down
23 changes: 13 additions & 10 deletions include/multipass/vm_specs.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct VMSpecs
std::unordered_map<std::string, VMMount> mounts;
bool deleted;
QJsonObject metadata;
std::vector<std::string> run_at_boot;
};

inline bool operator==(const VMSpecs& a, const VMSpecs& b)
Expand All @@ -57,16 +58,18 @@ inline bool operator==(const VMSpecs& a, const VMSpecs& b)
a.state,
a.mounts,
a.deleted,
a.metadata) == 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.metadata,
a.run_at_boot) == 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,
b.run_at_boot);
}

inline bool operator!=(const VMSpecs& a, const VMSpecs& b) // TODO drop in C++20
Expand Down
4 changes: 0 additions & 4 deletions src/client/cli/cmd/common_cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@

#include <QString>

#include <regex>

using RpcMethod = multipass::Rpc::StubInterface;

namespace multipass
Expand All @@ -41,8 +39,6 @@ namespace cmd
{
const QString all_option_name{"all"};
const QString format_option_name{"format"};
const std::regex yes_answer{"y|yes", std::regex::icase | std::regex::optimize};
const std::regex no_answer{"n|no", std::regex::icase | std::regex::optimize};
ricab marked this conversation as resolved.
Show resolved Hide resolved

ParseCode check_for_name_and_all_option_conflict(const ArgParser* parser, std::ostream& cerr, bool allow_empty = false);
InstanceNames add_instance_names(const ArgParser* parser);
Expand Down
5 changes: 3 additions & 2 deletions src/client/cli/cmd/delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,11 @@
mp::PlainPrompter prompter{term};

auto answer = prompter.prompt(fmt::format(prompt_text, snapshot_purge_notice_msg));
while (!answer.empty() && !std::regex_match(answer, yes_answer) && !std::regex_match(answer, no_answer))
while (!answer.empty() && !std::regex_match(answer, mp::client::yes_answer) &&
!std::regex_match(answer, mp::client::no_answer))

Check warning on line 163 in src/client/cli/cmd/delete.cpp

View check run for this annotation

Codecov / codecov/patch

src/client/cli/cmd/delete.cpp#L162-L163

Added lines #L162 - L163 were not covered by tests
answer = prompter.prompt(invalid_input);

return std::regex_match(answer, yes_answer);
return std::regex_match(answer, mp::client::yes_answer);

Check warning on line 166 in src/client/cli/cmd/delete.cpp

View check run for this annotation

Codecov / codecov/patch

src/client/cli/cmd/delete.cpp#L166

Added line #L166 was not covered by tests
}

std::string multipass::cmd::Delete::generate_snapshot_purge_msg() const
Expand Down
42 changes: 5 additions & 37 deletions src/client/cli/cmd/launch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <multipass/cli/argparser.h>
#include <multipass/cli/client_platform.h>
#include <multipass/cli/prompters.h>
#include <multipass/constants.h>
#include <multipass/exceptions/cmd_exceptions.h>
#include <multipass/exceptions/snap_environment_exception.h>
Expand Down Expand Up @@ -52,16 +53,6 @@

namespace
{
constexpr bool on_windows()
{ // TODO when we have remote client-daemon communication, we need to get the daemon's platform
return
#ifdef MULTIPASS_PLATFORM_WINDOWS
true;
#else
false;
#endif
}

auto checked_mode(const std::string& mode)
{
if (mode == "auto")
Expand Down Expand Up @@ -618,33 +609,10 @@

bool cmd::Launch::ask_bridge_permission(multipass::LaunchReply& reply)
{
static constexpr auto plural = "Multipass needs to create {} to connect to {}.\nThis will temporarily disrupt "
"connectivity on those interfaces.\n\nDo you want to continue (yes/no)? ";
static constexpr auto singular = "Multipass needs to create a {} to connect to {}.\nThis will temporarily disrupt "
"connectivity on that interface.\n\nDo you want to continue (yes/no)? ";
static constexpr auto nodes = on_windows() ? "switches" : "bridges";
static constexpr auto node = on_windows() ? "switch" : "bridge";

if (term->is_live())
{
assert(reply.nets_need_bridging_size()); // precondition
if (reply.nets_need_bridging_size() != 1)
fmt::print(cout, plural, nodes, fmt::join(reply.nets_need_bridging(), ", "));
else
fmt::print(cout, singular, node, reply.nets_need_bridging(0));
std::vector<std::string> nets;

Check warning on line 612 in src/client/cli/cmd/launch.cpp

View check run for this annotation

Codecov / codecov/patch

src/client/cli/cmd/launch.cpp#L612

Added line #L612 was not covered by tests

while (true)
{
std::string answer;
std::getline(term->cin(), answer);
if (std::regex_match(answer, yes_answer))
return true;
else if (std::regex_match(answer, no_answer))
return false;
else
cout << "Please answer yes/no: ";
}
}
std::copy(reply.nets_need_bridging().cbegin(), reply.nets_need_bridging().cend(), std::back_inserter(nets));

Check warning on line 614 in src/client/cli/cmd/launch.cpp

View check run for this annotation

Codecov / codecov/patch

src/client/cli/cmd/launch.cpp#L614

Added line #L614 was not covered by tests

return false;
mp::BridgePrompter prompter(term);
return prompter.bridge_prompt(nets);

Check warning on line 617 in src/client/cli/cmd/launch.cpp

View check run for this annotation

Codecov / codecov/patch

src/client/cli/cmd/launch.cpp#L616-L617

Added lines #L616 - L617 were not covered by tests
}
5 changes: 3 additions & 2 deletions src/client/cli/cmd/restore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ bool cmd::Restore::confirm_destruction(const std::string& instance_name)
mp::PlainPrompter prompter(term);

auto answer = prompter.prompt(fmt::format(prompt_text, instance_name));
while (!answer.empty() && !std::regex_match(answer, yes_answer) && !std::regex_match(answer, no_answer))
while (!answer.empty() && !std::regex_match(answer, mp::client::yes_answer) &&
!std::regex_match(answer, mp::client::no_answer))
answer = prompter.prompt(invalid_input);

return std::regex_match(answer, no_answer);
return std::regex_match(answer, mp::client::no_answer);
}
47 changes: 47 additions & 0 deletions src/client/common/prompters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*
*/

#include <multipass/cli/client_common.h>
#include <multipass/cli/prompters.h>
#include <multipass/exceptions/cli_exceptions.h>
#include <multipass/format.h>

#include <iostream>

Expand All @@ -34,6 +36,17 @@ auto get_input(std::istream& cin)

return value;
}

// TODO when we have remote client-daemon communication, we need to get the daemon's platform
constexpr bool on_windows()
{
return
#ifdef MULTIPASS_PLATFORM_WINDOWS
true;
#else
false;
#endif
}
} // namespace

std::string mp::PlainPrompter::prompt(const std::string& text) const
Expand Down Expand Up @@ -66,3 +79,37 @@ std::string mp::NewPassphrasePrompter::prompt(const std::string& text) const

return passphrase;
}

bool mp::BridgePrompter::bridge_prompt(const std::vector<std::string>& nets_need_bridging) const
{
assert(nets_need_bridging.size()); // precondition

static constexpr auto plural = "Multipass needs to create {} to connect to {}.\nThis will temporarily disrupt "
"connectivity on those interfaces.\n\nDo you want to continue (yes/no)? ";
static constexpr auto singular = "Multipass needs to create a {} to connect to {}.\nThis will temporarily disrupt "
"connectivity on that interface.\n\nDo you want to continue (yes/no)? ";
static constexpr auto nodes = on_windows() ? "switches" : "bridges";
static constexpr auto node = on_windows() ? "switch" : "bridge";

if (term->is_live())
{
if (nets_need_bridging.size() != 1)
fmt::print(term->cout(), plural, nodes, fmt::join(nets_need_bridging, ", "));
else
fmt::print(term->cout(), singular, node, nets_need_bridging[0]);

while (true)
{
std::string answer;
std::getline(term->cin(), answer);
if (std::regex_match(answer, mp::client::yes_answer))
return true;
else if (std::regex_match(answer, mp::client::no_answer))
return false;
else
term->cout() << "Please answer yes/no: ";
}
}

return false;
}
Loading