Skip to content

Commit

Permalink
Merge pull request #3195 from canonical/add-network-bridges
Browse files Browse the repository at this point in the history
Add network bridges

a=luis4a0 r=georgeliao,ricab
  • Loading branch information
ricab authored Jan 5, 2024
2 parents 4b38e63 + 89f1b78 commit d84f8f7
Show file tree
Hide file tree
Showing 35 changed files with 826 additions and 119 deletions.
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};

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 @@ bool multipass::cmd::Delete::confirm_snapshot_purge() const
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))
answer = prompter.prompt(invalid_input);

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

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 fs = std::filesystem;

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 @@ auto cmd::Launch::mount(const mp::ArgParser* parser, const QString& mount_source

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;

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));

return false;
mp::BridgePrompter prompter(term);
return prompter.bridge_prompt(nets);
}
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

0 comments on commit d84f8f7

Please sign in to comment.