From 215e7dae28aa1aadb707937b18c654f3302cf2c8 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 14 Mar 2023 10:21:38 -0700 Subject: [PATCH 001/627] [rpc] add num snaphots --- src/rpc/multipass.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 20cecf12ff2..40a23ab812f 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -217,6 +217,7 @@ message InfoReply { repeated string ipv6 = 12; MountInfo mount_info = 13; string cpu_count = 14; + int32 num_snapshots = 15; } repeated Info info = 1; string log_line = 2; From e8e3946cbb1f14cf0ede212230f4ff7be8270be5 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 14 Mar 2023 12:33:55 -0700 Subject: [PATCH 002/627] [cli] add num snapshots to info cmd --- src/client/cli/formatter/csv_formatter.cpp | 5 +++-- src/client/cli/formatter/json_formatter.cpp | 1 + src/client/cli/formatter/table_formatter.cpp | 2 ++ src/client/cli/formatter/yaml_formatter.cpp | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index a79e1916591..996083ffe33 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -53,7 +53,7 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const fmt::format_to( std::back_inserter(buf), "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory " - "total,Mounts,AllIPv4,CPU(s)\n"); + "total,Mounts,AllIPv4,CPU(s),Snapshots\n"); for (const auto& info : format::sorted(reply.info())) { @@ -68,7 +68,8 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); } - fmt::format_to(std::back_inserter(buf), ",\"{}\";,{}\n", fmt::join(info.ipv4(), ","), info.cpu_count()); + fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(info.ipv4(), ","), info.cpu_count(), + info.num_snapshots()); } return fmt::to_string(buf); } diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 4ddd2f44699..7e70b9b1849 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -72,6 +72,7 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const instance_info.insert("image_release", QString::fromStdString(info.image_release())); instance_info.insert("release", QString::fromStdString(info.current_release())); instance_info.insert("cpu_count", QString::fromStdString(info.cpu_count())); + instance_info.insert("snapshots", QString::number(info.num_snapshots())); QJsonArray load; if (!info.load().empty()) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 9a3d72480ab..60a2d3cd0f0 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -135,6 +135,8 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const } } + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", info.num_snapshots()); + fmt::format_to(std::back_inserter(buf), "\n"); } diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 4b92b41160d..13910a02f60 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -147,6 +147,7 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const mounts[mount.target_path()] = mount_node; } instance_node["mounts"] = mounts; + instance_node["snapshots"] = info.num_snapshots(); info_node[info.name()].push_back(instance_node); } From c469e0eeead7468494658bb4b50d73b1ad3bba28 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 14 Mar 2023 12:34:25 -0700 Subject: [PATCH 003/627] [tests] edit tests to include num snapshots --- tests/test_output_formatter.cpp | 35 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 1d672cf24cc..070b1716adf 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -196,6 +196,7 @@ auto construct_single_instance_info_reply() info_entry->add_ipv4("200.3.123.29"); info_entry->add_ipv6("2001:67c:1562:8007::aac:423a"); info_entry->add_ipv6("fd52:2ccf:f758:0:a342:79b5:e2ba:e05e"); + info_entry->set_num_snapshots(0); return info_reply; } @@ -233,12 +234,14 @@ auto construct_multiple_instances_info_reply() info_entry->set_disk_total("6764573492"); info_entry->set_current_release("Ubuntu 16.04.3 LTS"); info_entry->add_ipv4("10.21.124.56"); + info_entry->set_num_snapshots(1); info_entry = info_reply.add_info(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); info_entry->set_image_release("18.04 LTS"); info_entry->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); + info_entry->set_num_snapshots(3); return info_reply; } @@ -525,7 +528,8 @@ const std::vector orderable_list_info_formatter_outputs{ " GID map: 1000:1000\n" " /home/user/test_dir => test_dir\n" " UID map: 1000:1000\n" - " GID map: 1000:1000\n", + " GID map: 1000:1000\n" + "Snapshots: 0\n", "table_info_single"}, {&table_formatter, &multiple_instances_info_reply, "Name: bogus-instance\n" @@ -539,7 +543,8 @@ const std::vector orderable_list_info_formatter_outputs{ "Memory usage: 37.0MiB out of 1.5GiB\n" "Mounts: /home/user/source => source\n" " UID map: 1000:501\n" - " GID map: 1000:501\n\n" + " GID map: 1000:501\n" + "Snapshots: 1\n\n" "Name: bombastic\n" "State: Stopped\n" "IPv4: --\n" @@ -549,7 +554,8 @@ const std::vector orderable_list_info_formatter_outputs{ "Load: --\n" "Disk usage: --\n" "Memory usage: --\n" - "Mounts: --\n", + "Mounts: --\n" + "Snapshots: 3\n", "table_info_multiple"}, {&csv_formatter, &empty_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n", "csv_list_empty"}, @@ -572,22 +578,23 @@ const std::vector orderable_list_info_formatter_outputs{ {&csv_formatter, &empty_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " - "usage,Memory total,Mounts,AllIPv4,CPU(s)\n", + "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\n", "csv_info_empty"}, {&csv_formatter, &single_instance_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " - "usage,Memory total,Mounts,AllIPv4,CPU(s)\nfoo,Running,10.168.32.2,2001:67c:1562:8007::aac:423a,Ubuntu 16.04.3 " + "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nfoo,Running,10.168.32.2,2001:67c:1562:8007::aac:423a,Ubuntu " + "16.04.3 " "LTS,1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac,16.04 LTS,0.45 0.51 " "0.15,1288490188,5153960756,60817408,1503238554,/home/user/foo => foo;/home/user/test_dir " - "=> test_dir;,\"10.168.32.2,200.3.123.29\";,1\n", + "=> test_dir;,\"10.168.32.2,200.3.123.29\";,1,0\n", "csv_info_single"}, {&csv_formatter, &multiple_instances_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " - "usage,Memory total,Mounts,AllIPv4,CPU(s)\nbogus-instance,Running,10.21.124.56,,Ubuntu 16.04.3 " + "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nbogus-instance,Running,10.21.124.56,,Ubuntu 16.04.3 " "LTS,1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac,16.04 LTS,0.03 0.10 " "0.15,1932735284,6764573492,38797312,1610612736,/home/user/source => " - "source;,\"10.21.124.56\";,4\nbombastic,Stopped,,,," - "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,\"\";,\n", + "source;,\"10.21.124.56\";,4,1\nbombastic,Stopped,,,," + "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,\"\";,,3\n", "csv_info_multiple"}, {&yaml_formatter, &empty_list_reply, "\n", "yaml_list_empty"}, @@ -670,7 +677,8 @@ const std::vector orderable_list_info_formatter_outputs{ " - \"1000:1000\"\n" " gid_mappings:\n" " - \"1000:1000\"\n" - " source_path: /home/user/test_dir\n", + " source_path: /home/user/test_dir\n" + " snapshots: 0\n", "yaml_info_single"}, {&yaml_formatter, &multiple_instances_info_reply, "errors:\n" @@ -701,6 +709,7 @@ const std::vector orderable_list_info_formatter_outputs{ " gid_mappings:\n" " - \"1000:501\"\n" " source_path: /home/user/source\n" + " snapshots: 1\n" "bombastic:\n" " - state: Stopped\n" " image_hash: ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\n" @@ -716,7 +725,8 @@ const std::vector orderable_list_info_formatter_outputs{ " total: ~\n" " ipv4:\n" " []\n" - " mounts: ~\n", + " mounts: ~\n" + " snapshots: 3\n", "yaml_info_multiple"}}; const std::vector non_orderable_list_info_formatter_outputs{ @@ -819,6 +829,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " },\n" " \"release\": \"Ubuntu 16.04.3 LTS\",\n" + " \"snapshots\": \"0\",\n" " \"state\": \"Running\"\n" " }\n" " }\n" @@ -863,6 +874,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " },\n" " \"release\": \"Ubuntu 16.04.3 LTS\",\n" + " \"snapshots\": \"1\",\n" " \"state\": \"Running\"\n" " },\n" " \"bombastic\": {\n" @@ -882,6 +894,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " \"mounts\": {\n" " },\n" " \"release\": \"\",\n" + " \"snapshots\": \"3\",\n" " \"state\": \"Stopped\"\n" " }\n" " }\n" From 15f4c55cd5c69da354786f9ae83670b609133713 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 28 Feb 2023 19:14:07 +0000 Subject: [PATCH 004/627] [rpc] Add a snapshot RPC --- src/rpc/multipass.proto | 12 ++++++++++++ tests/mock_client_rpc.h | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 40a23ab812f..af910db2570 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -39,6 +39,7 @@ service Rpc { rpc set (stream SetRequest) returns (stream SetReply); rpc keys (stream KeysRequest) returns (stream KeysReply); rpc authenticate (stream AuthenticateRequest) returns (stream AuthenticateReply); + rpc snapshot (stream SnapshotRequest) returns (stream SnapshotReply); } message LaunchRequest { @@ -441,3 +442,14 @@ message AuthenticateRequest { message AuthenticateReply { string log_line = 1; } + +message SnapshotRequest { + string instance = 1; + string snapshot = 2; + string comment = 3; +} + +message SnapshotReply { + string snapshot = 1; // automatically generated unless specifically requested + string log_line = 2; +} diff --git a/tests/mock_client_rpc.h b/tests/mock_client_rpc.h index 97e41906230..fc9d18898dc 100644 --- a/tests/mock_client_rpc.h +++ b/tests/mock_client_rpc.h @@ -188,6 +188,12 @@ class MockRpcStub : public multipass::Rpc::StubInterface (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), PrepareAsyncauthenticateRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); + MOCK_METHOD((grpc::ClientReaderWriterInterface*), snapshotRaw, + (grpc::ClientContext * context), (override)); + MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), + AsyncsnapshotRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), (override)); + MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), + PrepareAsyncsnapshotRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); }; } // namespace multipass::test From d102d6389b089ffa95372342fcf70ef03d02f3a2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 1 Mar 2023 19:58:21 +0000 Subject: [PATCH 005/627] [cli] Add a snapshot command --- src/client/cli/client.cpp | 2 ++ src/client/cli/cmd/CMakeLists.txt | 1 + src/client/cli/cmd/snapshot.cpp | 49 +++++++++++++++++++++++++++++++ src/client/cli/cmd/snapshot.h | 40 +++++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 src/client/cli/cmd/snapshot.cpp create mode 100644 src/client/cli/cmd/snapshot.h diff --git a/src/client/cli/client.cpp b/src/client/cli/client.cpp index 59b52a2df88..7b5aff43b5c 100644 --- a/src/client/cli/client.cpp +++ b/src/client/cli/client.cpp @@ -36,6 +36,7 @@ #include "cmd/restart.h" #include "cmd/set.h" #include "cmd/shell.h" +#include "cmd/snapshot.h" #include "cmd/start.h" #include "cmd/stop.h" #include "cmd/suspend.h" @@ -95,6 +96,7 @@ mp::Client::Client(ClientConfig& config) add_command(); add_command(); add_command(); + add_command(); add_command(); add_command(); add_command(); diff --git a/src/client/cli/cmd/CMakeLists.txt b/src/client/cli/cmd/CMakeLists.txt index 873baf98506..6b22e00303b 100644 --- a/src/client/cli/cmd/CMakeLists.txt +++ b/src/client/cli/cmd/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(commands STATIC restart.cpp set.cpp shell.cpp + snapshot.cpp start.cpp stop.cpp suspend.cpp diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp new file mode 100644 index 00000000000..7eb6dab3b7a --- /dev/null +++ b/src/client/cli/cmd/snapshot.cpp @@ -0,0 +1,49 @@ +/* + * 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 "snapshot.h" +#include "common_cli.h" + +#include + +namespace mp = multipass; +namespace cmd = mp::cmd; + +mp::ReturnCode cmd::Snapshot::run(mp::ArgParser* parser) +{ + return parser->returnCodeFrom(parse_args(parser)); // TODO@ricab implement +} + +std::string cmd::Snapshot::name() const +{ + return "snapshot"; +} + +QString cmd::Snapshot::short_help() const +{ + return QStringLiteral("Take a snapshot of an instance"); +} + +QString cmd::Snapshot::description() const +{ + return QStringLiteral("Take a snapshot of an instance that can later be restored to recover the current state."); +} + +mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) +{ + return parser->commandParse(this); // TODO@ricab implement +} diff --git a/src/client/cli/cmd/snapshot.h b/src/client/cli/cmd/snapshot.h new file mode 100644 index 00000000000..ab9ba4020cb --- /dev/null +++ b/src/client/cli/cmd/snapshot.h @@ -0,0 +1,40 @@ +/* + * 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_SNAPSHOT_H +#define MULTIPASS_SNAPSHOT_H + +#include + +namespace multipass::cmd +{ +class Snapshot : public Command +{ +public: + using Command::Command; + ReturnCode run(ArgParser* parser) override; + + std::string name() const override; + QString short_help() const override; + QString description() const override; + +private: + ParseCode parse_args(ArgParser* parser); +}; +} // namespace multipass::cmd + +#endif // MULTIPASS_SNAPSHOT_H From 7d4a4cc37ffb1a85302059b447306abe64f97c15 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 2 Mar 2023 00:24:25 +0000 Subject: [PATCH 006/627] [cli] Establish `snapshot` syntax --- src/client/cli/cmd/snapshot.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 7eb6dab3b7a..016ee4fef24 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -45,5 +45,17 @@ QString cmd::Snapshot::description() const mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) { + parser->addPositionalArgument("instance", "The instance to take a snapshot of."); + parser->addPositionalArgument( + "snapshot", + "An optional name for the snapshot (default: \"snapshotN\", where N is an index number, i.e. the next " + "non-negative integer that has not been used to generate a snapshot name for yet, and which is " + "currently available).", + "[snapshot]"); + + QCommandLineOption comment_opt{ + {"comment", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; + parser->addOption(comment_opt); + return parser->commandParse(this); // TODO@ricab implement } From 3991d38ae578c1843631c2266d158e18a885b497 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 3 Mar 2023 12:21:34 +0000 Subject: [PATCH 007/627] [cli] Parse `snapshot` command --- src/client/cli/cmd/snapshot.cpp | 25 ++++++++++++++++++++++++- src/client/cli/cmd/snapshot.h | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 016ee4fef24..e37b9079d9d 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -57,5 +57,28 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) {"comment", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; parser->addOption(comment_opt); - return parser->commandParse(this); // TODO@ricab implement + if (auto status = parser->commandParse(this); status != ParseCode::Ok) + return status; + + const auto positional_args = parser->positionalArguments(); + const auto num_args = positional_args.count(); + if (num_args < 1) + { + cerr << "Need the name of an instance to snapshot.\n"; + return ParseCode::CommandLineError; + } + + if (num_args > 2) + { + cerr << "Too many arguments supplied\n"; + return ParseCode::CommandLineError; + } + + request.set_instance(positional_args.first().toStdString()); + request.set_comment(parser->value(comment_opt).toStdString()); + + if (num_args == 2) + request.set_snapshot(positional_args.at(1).toStdString()); + + return ParseCode::Ok; } diff --git a/src/client/cli/cmd/snapshot.h b/src/client/cli/cmd/snapshot.h index ab9ba4020cb..e51ae7b8e69 100644 --- a/src/client/cli/cmd/snapshot.h +++ b/src/client/cli/cmd/snapshot.h @@ -34,6 +34,7 @@ class Snapshot : public Command private: ParseCode parse_args(ArgParser* parser); + SnapshotRequest request; }; } // namespace multipass::cmd From f965d6450374b0695d48536ace3b715c10bb76b3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 3 Mar 2023 18:13:31 +0000 Subject: [PATCH 008/627] [cli] Implement snapshot command --- src/client/cli/cmd/snapshot.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index e37b9079d9d..368f42505c4 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -16,6 +16,8 @@ */ #include "snapshot.h" + +#include "animated_spinner.h" #include "common_cli.h" #include @@ -25,7 +27,24 @@ namespace cmd = mp::cmd; mp::ReturnCode cmd::Snapshot::run(mp::ArgParser* parser) { - return parser->returnCodeFrom(parse_args(parser)); // TODO@ricab implement + if (auto ret = parse_args(parser); ret != ParseCode::Ok) + return parser->returnCodeFrom(ret); + + AnimatedSpinner spinner{cout}; + + auto on_success = [this, &spinner](mp::SnapshotReply& reply) { + spinner.stop(); + fmt::print(cout, "Snapshot taken: {}.{}\n", request.instance(), reply.snapshot()); + return ReturnCode::Ok; + }; + + auto on_failure = [this, &spinner](grpc::Status& status) { + spinner.stop(); + return standard_failure_handler_for(name(), cerr, status); + }; + + spinner.start("Taking snapshot "); + return dispatch(&RpcMethod::snapshot, request, on_success, on_failure); } std::string cmd::Snapshot::name() const From 8d1557f13e2d27c1b90b311483f2006ab8be4de2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 3 Mar 2023 16:47:50 +0000 Subject: [PATCH 009/627] [cli] Honor verbosity in `snapshot` --- src/client/cli/cmd/snapshot.cpp | 2 ++ src/rpc/multipass.proto | 1 + 2 files changed, 3 insertions(+) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 368f42505c4..d7845e14d3a 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -99,5 +99,7 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) if (num_args == 2) request.set_snapshot(positional_args.at(1).toStdString()); + request.set_verbosity_level(parser->verbosityLevel()); + return ParseCode::Ok; } diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index af910db2570..c5bf46131a5 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -447,6 +447,7 @@ message SnapshotRequest { string instance = 1; string snapshot = 2; string comment = 3; + int32 verbosity_level = 4; } message SnapshotReply { From 7e795b1bab40355b9822a2f4d1d726a6abbe5337 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 3 Mar 2023 17:26:14 +0000 Subject: [PATCH 010/627] [daemon] Handle snapshot RPC With a placeholder implementation. --- src/daemon/daemon.cpp | 19 +++++++++++++++++++ src/daemon/daemon.h | 4 ++++ src/daemon/daemon_rpc.cpp | 10 ++++++++++ src/daemon/daemon_rpc.h | 4 ++++ 4 files changed, 37 insertions(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a7d287072dc..c06ac7f1590 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -655,6 +655,7 @@ auto connect_rpc(mp::DaemonRpc& rpc, mp::Daemon& daemon) QObject::connect(&rpc, &mp::DaemonRpc::on_set, &daemon, &mp::Daemon::set); QObject::connect(&rpc, &mp::DaemonRpc::on_keys, &daemon, &mp::Daemon::keys); QObject::connect(&rpc, &mp::DaemonRpc::on_authenticate, &daemon, &mp::Daemon::authenticate); + QObject::connect(&rpc, &mp::DaemonRpc::on_snapshot, &daemon, &mp::Daemon::snapshot); } enum class InstanceGroup @@ -2347,6 +2348,24 @@ catch (const std::exception& e) status_promise->set_value(grpc::Status(grpc::StatusCode::INTERNAL, e.what(), "")); } +void mp::Daemon::snapshot(const mp::SnapshotRequest* request, + grpc::ServerReaderWriterInterface* server, + std::promise* status_promise) +try +{ + mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), + *config->logger, server}; + + sleep(3); // TODO@ricab placeholder; implement + mpl::log(mpl::Level::debug, category, "Snapshot placeholder"); + + status_promise->set_value(grpc::Status::OK); +} +catch (const std::exception& e) +{ + status_promise->set_value(grpc::Status(grpc::StatusCode::INTERNAL, e.what(), "")); +} + void mp::Daemon::on_shutdown() { } diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 7a9f6a2c069..a7030a21286 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -136,6 +136,10 @@ public slots: grpc::ServerReaderWriterInterface* server, std::promise* status_promise); + virtual void snapshot(const SnapshotRequest* request, + grpc::ServerReaderWriterInterface* server, + std::promise* status_promise); + private: void release_resources(const std::string& instance); void create_vm(const CreateRequest* request, grpc::ServerReaderWriterInterface* server, diff --git a/src/daemon/daemon_rpc.cpp b/src/daemon/daemon_rpc.cpp index 7a63b891e25..e674d85d434 100644 --- a/src/daemon/daemon_rpc.cpp +++ b/src/daemon/daemon_rpc.cpp @@ -366,6 +366,16 @@ grpc::Status mp::DaemonRpc::keys(grpc::ServerContext* context, grpc::ServerReade std::bind(&DaemonRpc::on_keys, this, &request, server, std::placeholders::_1), client_cert_from(context)); } +grpc::Status mp::DaemonRpc::snapshot(grpc::ServerContext* context, + grpc::ServerReaderWriter* server) +{ + SnapshotRequest request; + server->Read(&request); + + return verify_client_and_dispatch_operation( + std::bind(&DaemonRpc::on_snapshot, this, &request, server, std::placeholders::_1), client_cert_from(context)); +} + template grpc::Status mp::DaemonRpc::verify_client_and_dispatch_operation(OperationSignal signal, const std::string& client_cert) { diff --git a/src/daemon/daemon_rpc.h b/src/daemon/daemon_rpc.h index af1d6890dd9..c4b44dd201b 100644 --- a/src/daemon/daemon_rpc.h +++ b/src/daemon/daemon_rpc.h @@ -98,6 +98,8 @@ class DaemonRpc : public QObject, public multipass::Rpc::Service, private Disabl void on_authenticate(const AuthenticateRequest* request, grpc::ServerReaderWriter* server, std::promise* status_promise); + void on_snapshot(const SnapshotRequest* request, grpc::ServerReaderWriter* server, + std::promise* status_promise); private: template @@ -145,6 +147,8 @@ class DaemonRpc : public QObject, public multipass::Rpc::Service, private Disabl grpc::Status keys(grpc::ServerContext* context, grpc::ServerReaderWriter* server) override; grpc::Status authenticate(grpc::ServerContext* context, grpc::ServerReaderWriter* server) override; + grpc::Status snapshot(grpc::ServerContext* context, + grpc::ServerReaderWriter* server) override; }; } // namespace multipass #endif // MULTIPASS_DAEMON_RPC_H From 92a28cd12c176b6a72a5fe9733bc686f063271b3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 3 Mar 2023 18:12:27 +0000 Subject: [PATCH 011/627] [daemon] Complement placeholder snapshot impl --- src/daemon/daemon.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index c06ac7f1590..76f03193efa 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2356,8 +2356,16 @@ try mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; - sleep(3); // TODO@ricab placeholder; implement - mpl::log(mpl::Level::debug, category, "Snapshot placeholder"); + { // TODO@ricab replace placeholder implementation + sleep(3); + + mpl::log(mpl::Level::debug, category, "Snapshot placeholder"); + + SnapshotReply reply; + auto snapshot_name = request->snapshot(); + reply.set_snapshot(snapshot_name.empty() ? "placeholder-name" : snapshot_name); + server->Write(reply); + } status_promise->set_value(grpc::Status::OK); } From 84f55a2631eb77458d0e193fcd0ebaba3c8c3125 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 3 Mar 2023 17:15:02 +0000 Subject: [PATCH 012/627] [cli] Handle streaming replies in `snapshot` --- src/client/cli/cmd/snapshot.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index d7845e14d3a..df64d6a705d 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -18,6 +18,7 @@ #include "snapshot.h" #include "animated_spinner.h" +#include "common_callbacks.h" #include "common_cli.h" #include @@ -44,7 +45,8 @@ mp::ReturnCode cmd::Snapshot::run(mp::ArgParser* parser) }; spinner.start("Taking snapshot "); - return dispatch(&RpcMethod::snapshot, request, on_success, on_failure); + return dispatch(&RpcMethod::snapshot, request, on_success, on_failure, + make_logging_spinner_callback(spinner, cerr)); } std::string cmd::Snapshot::name() const From 65629cdf7d6a4ca9e4503a4b8eae23c1034cf96c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 16 Mar 2023 07:37:00 -0700 Subject: [PATCH 013/627] [cli] add restore command --- src/client/cli/cmd/restore.cpp | 161 +++++++++++++++++++++++++++++++++ src/client/cli/cmd/restore.h | 42 +++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/client/cli/cmd/restore.cpp create mode 100644 src/client/cli/cmd/restore.h diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp new file mode 100644 index 00000000000..9fce21b7590 --- /dev/null +++ b/src/client/cli/cmd/restore.cpp @@ -0,0 +1,161 @@ +/* + * 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 "restore.h" +#include "animated_spinner.h" +#include "common_callbacks.h" +#include "common_cli.h" + +#include +#include +#include + +#include + +namespace mp = multipass; +namespace cmd = mp::cmd; + +namespace +{ +const std::regex yes{"y|yes", std::regex::icase | std::regex::optimize}; +const std::regex no{"n|no", std::regex::icase | std::regex::optimize}; +} // namespace + +mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) +{ + if (auto ret = parse_args(parser); ret != ParseCode::Ok) + return parser->returnCodeFrom(ret); + + AnimatedSpinner spinner{cout}; + + auto on_success = [this, &spinner](mp::RestoreReply& reply) { + spinner.stop(); + fmt::print(cout, "Snapshot restored: {}.{}\n", request.instance(), request.snapshot()); + return ReturnCode::Ok; + }; + + auto on_failure = [this, &spinner](grpc::Status& status) { + spinner.stop(); + return standard_failure_handler_for(name(), cerr, status); + }; + + spinner.start("Restoring snapshot "); + return dispatch(&RpcMethod::restore, request, on_success, on_failure, + make_logging_spinner_callback(spinner, cerr)); +} + +std::string cmd::Restore::name() const +{ + return "restore"; +} + +QString cmd::Restore::short_help() const +{ + return QStringLiteral("Restore an instance from a snapshot"); +} + +QString cmd::Restore::description() const +{ + return QStringLiteral("Restore an instance to the state of a previously taken snapshot."); +} + +mp::ParseCode cmd::Restore::parse_args(mp::ArgParser* parser) +{ + parser->addPositionalArgument("instance.snapshot", + "The instance to restore and snapshot to use, in . format, where " + " is the name of an instance, and is the name of a snapshot", + "."); + + QCommandLineOption destructive({"d", "destructive"}, "Discard the current state of the instance"); + parser->addOption(destructive); + + auto status = parser->commandParse(this); + if (status != ParseCode::Ok) + return status; + + const auto positional_args = parser->positionalArguments(); + const auto num_args = positional_args.count(); + if (num_args < 1) + { + cerr << "Need the name of an instance and snapshot to restore.\n"; + return ParseCode::CommandLineError; + } + + if (num_args > 1) + { + cerr << "Too many arguments supplied\n"; + return ParseCode::CommandLineError; + } + + auto instance = parser->positionalArguments().at(0); + auto instance_name = instance.section('.', 0, 0); + auto snapshot_name = instance.section('.', 1); + + if (instance_name.isEmpty()) + { + cerr << "Need the name of an instance to restore.\n"; + return ParseCode::CommandLineError; + } + + if (snapshot_name.isEmpty()) + { + cerr << "Need the name of a snapshot to restore from.\n"; + return ParseCode::CommandLineError; + } + + request.set_instance(instance_name.toStdString()); + request.set_snapshot(snapshot_name.toStdString()); + request.set_destructive(parser->isSet(destructive)); + + if (!parser->isSet(destructive)) + { + if (term->is_live()) + { + try + { + request.set_destructive(confirm_destruction(instance_name)); + } + catch (const mp::PromptException& e) + { + std::cerr << e.what() << std::endl; + return ParseCode::CommandLineError; + } + } + else + { + return ParseCode::CommandFail; + } + } + + request.set_verbosity_level(parser->verbosityLevel()); + + return ParseCode::Ok; +} + +bool cmd::Restore::confirm_destruction(const QString& instance_name) +{ + static constexpr auto prompt_text = + "Do you want to take a snapshot of {} before discarding its current state? (Yes/no)"; + static constexpr auto invalid_input = "Please answer yes/no"; + mp::PlainPrompter prompter(term); + + auto answer = prompter.prompt(fmt::format(prompt_text, instance_name)); + while (!std::regex_match(answer, yes) && !std::regex_match(answer, no)) + answer = prompter.prompt(invalid_input); + + return std::regex_match(answer, no); +} diff --git a/src/client/cli/cmd/restore.h b/src/client/cli/cmd/restore.h new file mode 100644 index 00000000000..6d7b6bbc152 --- /dev/null +++ b/src/client/cli/cmd/restore.h @@ -0,0 +1,42 @@ +/* + * 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_RESTORE_H +#define MULTIPASS_RESTORE_H + +#include + +namespace multipass::cmd +{ +class Restore : public Command +{ +public: + using Command::Command; + ReturnCode run(ArgParser* parser) override; + + std::string name() const override; + QString short_help() const override; + QString description() const override; + +private: + ParseCode parse_args(ArgParser* parser); + bool confirm_destruction(const QString& instance_name); + RestoreRequest request; +}; +} // namespace multipass::cmd + +#endif // MULTIPASS_RESTORE_H From 67f381f765ce44b2fb21fa8693f53ab14893590b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 16 Mar 2023 07:37:58 -0700 Subject: [PATCH 014/627] [rpc] add restore rpc protocol --- src/rpc/multipass.proto | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index c5bf46131a5..c62a349874a 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -40,6 +40,7 @@ service Rpc { rpc keys (stream KeysRequest) returns (stream KeysReply); rpc authenticate (stream AuthenticateRequest) returns (stream AuthenticateReply); rpc snapshot (stream SnapshotRequest) returns (stream SnapshotReply); + rpc restore (stream RestoreRequest) returns (stream RestoreReply); } message LaunchRequest { @@ -454,3 +455,14 @@ message SnapshotReply { string snapshot = 1; // automatically generated unless specifically requested string log_line = 2; } + +message RestoreRequest { + string instance = 1; + string snapshot = 2; + bool destructive = 3; + int32 verbosity_level = 4; +} + +message RestoreReply { + string log_line = 1; +} From aadc583ec8338afe15740734d10bbe0a54f936ae Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 16 Mar 2023 07:51:07 -0700 Subject: [PATCH 015/627] [cli] link restore with CMake and cli --- src/client/cli/client.cpp | 2 ++ src/client/cli/cmd/CMakeLists.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/src/client/cli/client.cpp b/src/client/cli/client.cpp index 7b5aff43b5c..9de747ff5b3 100644 --- a/src/client/cli/client.cpp +++ b/src/client/cli/client.cpp @@ -34,6 +34,7 @@ #include "cmd/recover.h" #include "cmd/remote_settings_handler.h" #include "cmd/restart.h" +#include "cmd/restore.h" #include "cmd/set.h" #include "cmd/shell.h" #include "cmd/snapshot.h" @@ -94,6 +95,7 @@ mp::Client::Client(ClientConfig& config) add_command(); add_command(aliases); add_command(); + add_command(); add_command(); add_command(); add_command(); diff --git a/src/client/cli/cmd/CMakeLists.txt b/src/client/cli/cmd/CMakeLists.txt index 6b22e00303b..d5606f61535 100644 --- a/src/client/cli/cmd/CMakeLists.txt +++ b/src/client/cli/cmd/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(commands STATIC recover.cpp remote_settings_handler.cpp restart.cpp + restore.cpp set.cpp shell.cpp snapshot.cpp From 9d1fe1bdef7ead04d3dd4bff1bbc44b30736c74b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 16 Mar 2023 07:51:35 -0700 Subject: [PATCH 016/627] [daemon] link daemon with restore cmd --- src/daemon/daemon.cpp | 28 ++++++++++++++++++++++++++++ src/daemon/daemon.h | 4 ++++ src/daemon/daemon_rpc.cpp | 10 ++++++++++ src/daemon/daemon_rpc.h | 4 ++++ 4 files changed, 46 insertions(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 76f03193efa..623ad90889b 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -656,6 +656,7 @@ auto connect_rpc(mp::DaemonRpc& rpc, mp::Daemon& daemon) QObject::connect(&rpc, &mp::DaemonRpc::on_keys, &daemon, &mp::Daemon::keys); QObject::connect(&rpc, &mp::DaemonRpc::on_authenticate, &daemon, &mp::Daemon::authenticate); QObject::connect(&rpc, &mp::DaemonRpc::on_snapshot, &daemon, &mp::Daemon::snapshot); + QObject::connect(&rpc, &mp::DaemonRpc::on_restore, &daemon, &mp::Daemon::restore); } enum class InstanceGroup @@ -2374,6 +2375,33 @@ catch (const std::exception& e) status_promise->set_value(grpc::Status(grpc::StatusCode::INTERNAL, e.what(), "")); } +void mp::Daemon::restore(const mp::RestoreRequest* request, + grpc::ServerReaderWriterInterface* server, + std::promise* status_promise) +try +{ + mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, + server}; + + { // TODO@ricab replace placeholder implementation + + sleep(3); + + mpl::log(mpl::Level::debug, category, "Restore placeholder"); + + RestoreReply reply; + auto snapshot_name = request->snapshot(); + + server->Write(reply); + } + + status_promise->set_value(grpc::Status::OK); +} +catch (const std::exception& e) +{ + status_promise->set_value(grpc::Status(grpc::StatusCode::INTERNAL, e.what(), "")); +} + void mp::Daemon::on_shutdown() { } diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index a7030a21286..61a46ab9fe1 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -140,6 +140,10 @@ public slots: grpc::ServerReaderWriterInterface* server, std::promise* status_promise); + virtual void restore(const RestoreRequest* request, + grpc::ServerReaderWriterInterface* server, + std::promise* status_promise); + private: void release_resources(const std::string& instance); void create_vm(const CreateRequest* request, grpc::ServerReaderWriterInterface* server, diff --git a/src/daemon/daemon_rpc.cpp b/src/daemon/daemon_rpc.cpp index e674d85d434..d41511fd444 100644 --- a/src/daemon/daemon_rpc.cpp +++ b/src/daemon/daemon_rpc.cpp @@ -376,6 +376,16 @@ grpc::Status mp::DaemonRpc::snapshot(grpc::ServerContext* context, std::bind(&DaemonRpc::on_snapshot, this, &request, server, std::placeholders::_1), client_cert_from(context)); } +grpc::Status mp::DaemonRpc::restore(grpc::ServerContext* context, + grpc::ServerReaderWriter* server) +{ + RestoreRequest request; + server->Read(&request); + + return verify_client_and_dispatch_operation( + std::bind(&DaemonRpc::on_restore, this, &request, server, std::placeholders::_1), client_cert_from(context)); +} + template grpc::Status mp::DaemonRpc::verify_client_and_dispatch_operation(OperationSignal signal, const std::string& client_cert) { diff --git a/src/daemon/daemon_rpc.h b/src/daemon/daemon_rpc.h index c4b44dd201b..76e195c1378 100644 --- a/src/daemon/daemon_rpc.h +++ b/src/daemon/daemon_rpc.h @@ -100,6 +100,8 @@ class DaemonRpc : public QObject, public multipass::Rpc::Service, private Disabl std::promise* status_promise); void on_snapshot(const SnapshotRequest* request, grpc::ServerReaderWriter* server, std::promise* status_promise); + void on_restore(const RestoreRequest* request, grpc::ServerReaderWriter* server, + std::promise* status_promise); private: template @@ -149,6 +151,8 @@ class DaemonRpc : public QObject, public multipass::Rpc::Service, private Disabl grpc::ServerReaderWriter* server) override; grpc::Status snapshot(grpc::ServerContext* context, grpc::ServerReaderWriter* server) override; + grpc::Status restore(grpc::ServerContext* context, + grpc::ServerReaderWriter* server) override; }; } // namespace multipass #endif // MULTIPASS_DAEMON_RPC_H From 9fd7b47151c174bc9610e6afbc6243c3a56a7f40 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 16 Mar 2023 07:55:17 -0700 Subject: [PATCH 017/627] [test] mock restore cmd/test --- tests/mock_client_rpc.h | 6 ++++++ tests/test_cli_client.cpp | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tests/mock_client_rpc.h b/tests/mock_client_rpc.h index fc9d18898dc..a8d9c93c06a 100644 --- a/tests/mock_client_rpc.h +++ b/tests/mock_client_rpc.h @@ -194,6 +194,12 @@ class MockRpcStub : public multipass::Rpc::StubInterface AsyncsnapshotRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), PrepareAsyncsnapshotRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); + MOCK_METHOD((grpc::ClientReaderWriterInterface*), restoreRaw, + (grpc::ClientContext * context), (override)); + MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), + AsyncrestoreRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), (override)); + MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), + PrepareAsyncrestoreRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); }; } // namespace multipass::test diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 91f47429ebb..4a75a78b7a9 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -136,6 +136,10 @@ struct MockDaemonRpc : public mp::DaemonRpc (grpc::ServerContext * context, (grpc::ServerReaderWriter * server)), (override)); + MOCK_METHOD(grpc::Status, restore, + (grpc::ServerContext * context, + (grpc::ServerReaderWriter * server)), + (override)); }; struct Client : public Test @@ -3135,6 +3139,43 @@ TEST_F(Client, help_cmd_launch_same_launch_cmd_help) EXPECT_THAT(help_cmd_launch.str(), Eq(launch_cmd_help.str())); } +// restore cli tests +TEST_F(Client, restoreCmdHelpOk) +{ + EXPECT_EQ(send_command({"restore", "--help"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, restoreCmdGoodArgsOk) +{ + EXPECT_CALL(mock_daemon, restore); + EXPECT_EQ(send_command({"restore", "foo.snapshot1", "--destructive"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, restoreCmdTooFewArgsFails) +{ + EXPECT_EQ(send_command({"restore", "--destructive"}), mp::ReturnCode::CommandLineError); +} + +TEST_F(Client, restoreCmdTooManyArgsFails) +{ + EXPECT_EQ(send_command({"restore", "foo.snapshot1", "bar.snapshot2"}), mp::ReturnCode::CommandLineError); +} + +TEST_F(Client, restoreCmdMissingInstanceFails) +{ + EXPECT_EQ(send_command({"restore", ".snapshot1"}), mp::ReturnCode::CommandLineError); +} + +TEST_F(Client, restoreCmdMissingSnapshotFails) +{ + EXPECT_EQ(send_command({"restore", "foo."}), mp::ReturnCode::CommandLineError); +} + +TEST_F(Client, restoreCmdInvalidOptionFails) +{ + EXPECT_EQ(send_command({"restore", "--foo"}), mp::ReturnCode::CommandLineError); +} + // authenticate cli tests TEST_F(Client, authenticateCmdGoodPassphraseOk) { From 52914cab06e0fdc4b17c9dfcfce7d3254a41f600 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 27 Mar 2023 15:12:58 -0700 Subject: [PATCH 018/627] review changes --- src/client/cli/cmd/restore.cpp | 24 ++++++++---------------- src/daemon/daemon.cpp | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 9fce21b7590..98b78f5c6a4 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -97,28 +97,20 @@ mp::ParseCode cmd::Restore::parse_args(mp::ArgParser* parser) if (num_args > 1) { - cerr << "Too many arguments supplied\n"; + cerr << "Too many arguments supplied.\n"; return ParseCode::CommandLineError; } - auto instance = parser->positionalArguments().at(0); - auto instance_name = instance.section('.', 0, 0); - auto snapshot_name = instance.section('.', 1); - - if (instance_name.isEmpty()) - { - cerr << "Need the name of an instance to restore.\n"; - return ParseCode::CommandLineError; - } - - if (snapshot_name.isEmpty()) + const auto tokens = parser->positionalArguments().at(0).split('.'); + if (tokens.size() != 2 || tokens[0].isEmpty() || tokens[1].isEmpty()) { - cerr << "Need the name of a snapshot to restore from.\n"; + cerr << "Invalid format. Specify the instance to restore and snapshot to use in the form " + "..\n"; return ParseCode::CommandLineError; } - request.set_instance(instance_name.toStdString()); - request.set_snapshot(snapshot_name.toStdString()); + request.set_instance(tokens[0].toStdString()); + request.set_snapshot(tokens[1].toStdString()); request.set_destructive(parser->isSet(destructive)); if (!parser->isSet(destructive)) @@ -127,7 +119,7 @@ mp::ParseCode cmd::Restore::parse_args(mp::ArgParser* parser) { try { - request.set_destructive(confirm_destruction(instance_name)); + request.set_destructive(confirm_destruction(tokens[0])); } catch (const mp::PromptException& e) { diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 623ad90889b..c25033d8b36 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2357,7 +2357,7 @@ try mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; - { // TODO@ricab replace placeholder implementation + { // TODO@snapshots replace placeholder implementation sleep(3); mpl::log(mpl::Level::debug, category, "Snapshot placeholder"); From a510efdb66e7921d0df6e93f4267a332e3a1baf7 Mon Sep 17 00:00:00 2001 From: ScottH <59572507+sharder996@users.noreply.github.com> Date: Mon, 27 Mar 2023 15:14:59 -0700 Subject: [PATCH 019/627] Update TODO reference Co-authored-by: Ricardo Abreu Signed-off-by: ScottH <59572507+sharder996@users.noreply.github.com> --- src/daemon/daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index c25033d8b36..0f1abc55ff4 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2383,7 +2383,7 @@ try mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; - { // TODO@ricab replace placeholder implementation + { // TODO@snapshots replace placeholder implementation sleep(3); From 6e442bf8ffeef1e025042422ea2f22b3e82039ba Mon Sep 17 00:00:00 2001 From: ScottH <59572507+sharder996@users.noreply.github.com> Date: Tue, 28 Mar 2023 07:30:51 -0700 Subject: [PATCH 020/627] Update cli error msg Co-authored-by: Ricardo Abreu Signed-off-by: ScottH <59572507+sharder996@users.noreply.github.com> --- src/client/cli/cmd/restore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 98b78f5c6a4..f293bfdac06 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -104,7 +104,7 @@ mp::ParseCode cmd::Restore::parse_args(mp::ArgParser* parser) const auto tokens = parser->positionalArguments().at(0).split('.'); if (tokens.size() != 2 || tokens[0].isEmpty() || tokens[1].isEmpty()) { - cerr << "Invalid format. Specify the instance to restore and snapshot to use in the form " + cerr << "Invalid format. Please specify the instance to restore and snapshot to use in the form " "..\n"; return ParseCode::CommandLineError; } From 1dbf9cb586849b3a7b7cbd0558b65bb0d1b9fc36 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 20 Mar 2023 13:41:08 -0700 Subject: [PATCH 021/627] [tests] snapshot cli tests --- tests/test_cli_client.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 4a75a78b7a9..65a91369757 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -136,6 +136,10 @@ struct MockDaemonRpc : public mp::DaemonRpc (grpc::ServerContext * context, (grpc::ServerReaderWriter * server)), (override)); + MOCK_METHOD(grpc::Status, snapshot, + (grpc::ServerContext * context, + (grpc::ServerReaderWriter * server)), + (override)); MOCK_METHOD(grpc::Status, restore, (grpc::ServerContext * context, (grpc::ServerReaderWriter * server)), @@ -3139,6 +3143,34 @@ TEST_F(Client, help_cmd_launch_same_launch_cmd_help) EXPECT_THAT(help_cmd_launch.str(), Eq(launch_cmd_help.str())); } +// snapshot cli tests +TEST_F(Client, snapshotCmdHelpOk) +{ + EXPECT_EQ(send_command({"snapshot", "--help"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, snapshotCmdGoodArgsOk) +{ + EXPECT_CALL(mock_daemon, snapshot); + EXPECT_EQ(send_command({"snapshot", "foo", "rocky"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, snapshotCmdTooFewArgsFails) +{ + EXPECT_EQ(send_command({"snapshot", "-m", "Who controls the past controls the future"}), + mp::ReturnCode::CommandLineError); +} + +TEST_F(Client, snapshotCmdTooManyArgsFails) +{ + EXPECT_EQ(send_command({"snaphot", "foo", "bar"}), mp::ReturnCode::CommandLineError); +} + +TEST_F(Client, snapshotCmdInvalidOptionFails) +{ + EXPECT_EQ(send_command({"snapshot", "--snap"}), mp::ReturnCode::CommandLineError); +} + // restore cli tests TEST_F(Client, restoreCmdHelpOk) { From 294a87095fd0e7cba8b0d9d61e0710bd84b69149 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 20 Mar 2023 13:42:10 -0700 Subject: [PATCH 022/627] [tests] mock term restore tests --- tests/test_cli_client.cpp | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 65a91369757..306769d4a14 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -3208,6 +3208,46 @@ TEST_F(Client, restoreCmdInvalidOptionFails) EXPECT_EQ(send_command({"restore", "--foo"}), mp::ReturnCode::CommandLineError); } +struct RestoreCommandClient : public Client +{ + RestoreCommandClient() + { + EXPECT_CALL(mock_terminal, cout).WillRepeatedly(ReturnRef(cout)); + EXPECT_CALL(mock_terminal, cerr).WillRepeatedly(ReturnRef(cerr)); + EXPECT_CALL(mock_terminal, cin).WillRepeatedly(ReturnRef(cin)); + } + + std::ostringstream cerr, cout; + std::istringstream cin; + mpt::MockTerminal mock_terminal; +}; + +TEST_F(RestoreCommandClient, restoreCmdConfirmsDesruction) +{ + cin.str("invalid input\nyes\n"); + + EXPECT_CALL(mock_terminal, cin_is_live()).WillOnce(Return(true)); + EXPECT_CALL(mock_terminal, cout_is_live()).WillOnce(Return(true)); + + EXPECT_CALL(mock_daemon, restore(_, _)).WillOnce([](auto, auto* server) { + mp::RestoreRequest request; + server->Read(&request); + + EXPECT_FALSE(request.destructive()); + return grpc::Status{}; + }); + + EXPECT_EQ(setup_client_and_run({"restore", "foo.snapshot1"}, mock_terminal), mp::ReturnCode::Ok); + EXPECT_TRUE(cout.str().find("Please answer yes/no")); +} + +TEST_F(RestoreCommandClient, restoreCmdNotDestructiveNotLiveTermFails) +{ + EXPECT_CALL(mock_terminal, cin_is_live()).WillOnce(Return(false)); + + EXPECT_EQ(setup_client_and_run({"restore", "foo.snapshot1"}, mock_terminal), mp::ReturnCode::CommandFail); +} + // authenticate cli tests TEST_F(Client, authenticateCmdGoodPassphraseOk) { From cd0da35cb8b00167b61b1587d4f2bc06a74ae19b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 14 Mar 2023 17:25:40 +0000 Subject: [PATCH 023/627] [interface] Define a generic Snapshot interface --- include/multipass/snapshot.h | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 include/multipass/snapshot.h diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h new file mode 100644 index 00000000000..3189048a787 --- /dev/null +++ b/include/multipass/snapshot.h @@ -0,0 +1,51 @@ +/* + * 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_SNAPSHOT_H +#define MULTIPASS_SNAPSHOT_H + +#include "disabled_copy_move.h" +#include "virtual_machine.h" + +#include +#include + +class QJsonObject; + +namespace multipass +{ +class MemorySize; + +class Snapshot : private DisabledCopyMove +{ +public: + virtual ~Snapshot() = default; + + virtual const std::string& get_name() const = 0; + virtual const std::string& get_comment() const = 0; + virtual const Snapshot* get_parent() const = 0; + + virtual int get_num_cores() const = 0; + virtual MemorySize get_mem_size() const = 0; + virtual MemorySize get_disk_space() const = 0; + virtual VirtualMachine::State get_state() const = 0; + virtual const std::unordered_map& get_mounts() const = 0; + virtual const QJsonObject& get_metadata() const = 0; +}; +} // namespace multipass + +#endif // MULTIPASS_SNAPSHOT_H From 6cf081d2ffdd74a55de5dacfaeb32e3931f62d0c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 14 Mar 2023 23:15:12 +0000 Subject: [PATCH 024/627] [shared] Add an empty BaseSnapshot implementation --- src/platform/backends/shared/CMakeLists.txt | 1 + .../backends/shared/base_snapshot.cpp | 20 +++++++++++ src/platform/backends/shared/base_snapshot.h | 34 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 src/platform/backends/shared/base_snapshot.cpp create mode 100644 src/platform/backends/shared/base_snapshot.h diff --git a/src/platform/backends/shared/CMakeLists.txt b/src/platform/backends/shared/CMakeLists.txt index e7fd55ee1d7..5e4d57778c4 100644 --- a/src/platform/backends/shared/CMakeLists.txt +++ b/src/platform/backends/shared/CMakeLists.txt @@ -13,6 +13,7 @@ # along with this program. If not, see . add_library(shared STATIC + base_snapshot.cpp base_virtual_machine.cpp base_virtual_machine_factory.cpp sshfs_server_process_spec.cpp diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp new file mode 100644 index 00000000000..5f37bfa960b --- /dev/null +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -0,0 +1,20 @@ +/* + * 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 "base_snapshot.h" + +namespace mp = multipass; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h new file mode 100644 index 00000000000..a5c534601b8 --- /dev/null +++ b/src/platform/backends/shared/base_snapshot.h @@ -0,0 +1,34 @@ +/* + * 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_SNAPSHOT_H +#define MULTIPASS_BASE_SNAPSHOT_H + +#include +#include + +namespace multipass +{ + +class BaseSnapshot : public Snapshot +{ +public: +}; + +} // namespace multipass + +#endif // MULTIPASS_BASE_SNAPSHOT_H From 6d977f7a517a2fd2b9423212cc4f0e458e183b0d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 14 Mar 2023 17:33:08 +0000 Subject: [PATCH 025/627] [util] Mark MemorySize's default ctor noexcept --- include/multipass/memory_size.h | 2 +- src/utils/memory_size.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/multipass/memory_size.h b/include/multipass/memory_size.h index 1585f25c4c7..0e7ae0e4247 100644 --- a/include/multipass/memory_size.h +++ b/include/multipass/memory_size.h @@ -32,7 +32,7 @@ class MemorySize friend bool operator<=(const MemorySize& a, const MemorySize& b); friend bool operator>=(const MemorySize& a, const MemorySize& b); - MemorySize(); + MemorySize() noexcept; explicit MemorySize(const std::string& val); long long in_bytes() const noexcept; long long in_kilobytes() const noexcept; diff --git a/src/utils/memory_size.cpp b/src/utils/memory_size.cpp index 925e2648c26..b8eb25338e9 100644 --- a/src/utils/memory_size.cpp +++ b/src/utils/memory_size.cpp @@ -79,7 +79,7 @@ long long in_bytes(const std::string& mem_value) } } // namespace -mp::MemorySize::MemorySize() : bytes{0LL} +mp::MemorySize::MemorySize() noexcept : bytes{0LL} { } From 4552c28c3764430c9756fefe9521236b1014ee6c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 14 Mar 2023 17:33:33 +0000 Subject: [PATCH 026/627] [interface] Mark Snapshot getters noexcept --- include/multipass/snapshot.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 3189048a787..a35b1a40cf8 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -35,16 +35,16 @@ class Snapshot : private DisabledCopyMove public: virtual ~Snapshot() = default; - virtual const std::string& get_name() const = 0; - virtual const std::string& get_comment() const = 0; - virtual const Snapshot* get_parent() const = 0; - - virtual int get_num_cores() const = 0; - virtual MemorySize get_mem_size() const = 0; - virtual MemorySize get_disk_space() const = 0; - virtual VirtualMachine::State get_state() const = 0; - virtual const std::unordered_map& get_mounts() const = 0; - virtual const QJsonObject& get_metadata() const = 0; + virtual const std::string& get_name() const noexcept = 0; + virtual const std::string& get_comment() const noexcept = 0; + virtual const Snapshot* get_parent() const noexcept = 0; + + virtual int get_num_cores() const noexcept = 0; + virtual MemorySize get_mem_size() const noexcept = 0; + virtual MemorySize get_disk_space() const noexcept = 0; + virtual VirtualMachine::State get_state() const noexcept = 0; + virtual const std::unordered_map& get_mounts() const noexcept = 0; + virtual const QJsonObject& get_metadata() const noexcept = 0; }; } // namespace multipass From 987d03568b0c6fe8782d9b0f137cd6356454e0d8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 14 Mar 2023 22:56:24 +0000 Subject: [PATCH 027/627] [shared] Implement basic BaseSnapshot ctors --- include/multipass/snapshot.h | 1 + .../backends/shared/base_snapshot.cpp | 25 +++++++++++++++++++ src/platform/backends/shared/base_snapshot.h | 24 ++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index a35b1a40cf8..3dd742656cb 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -29,6 +29,7 @@ class QJsonObject; namespace multipass { class MemorySize; +struct VMMount; class Snapshot : private DisabledCopyMove { diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 5f37bfa960b..674f590ed1e 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -16,5 +16,30 @@ */ #include "base_snapshot.h" +#include "daemon/vm_specs.h" // TODO@ricab move this + +#include namespace mp = multipass; + +mp::BaseSnapshot::BaseSnapshot(std::string name, std::string comment, const mp::Snapshot* parent, int num_cores, + mp::MemorySize mem_size, mp::MemorySize disk_space, mp::VirtualMachine::State state, + std::unordered_map mounts, QJsonObject metadata) + : name{std::move(name)}, + comment{std::move(comment)}, + parent{parent}, + num_cores{num_cores}, + mem_size{mem_size}, + disk_space{disk_space}, + state{state}, + mounts{std::move(mounts)}, + metadata{std::move(metadata)} +{ +} + +mp::BaseSnapshot::BaseSnapshot(std::string name, std::string comment, const mp::Snapshot* parent, + const mp::VMSpecs& specs) + : BaseSnapshot{std::move(name), std::move(comment), parent, specs.num_cores, specs.mem_size, + specs.disk_space, specs.state, specs.mounts, specs.metadata} +{ +} diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index a5c534601b8..394ffe2db66 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -18,17 +18,37 @@ #ifndef MULTIPASS_BASE_SNAPSHOT_H #define MULTIPASS_BASE_SNAPSHOT_H -#include #include +#include +#include + +#include + namespace multipass { +class VMSpecs; class BaseSnapshot : public Snapshot { public: -}; + BaseSnapshot(std::string name, std::string comment, const Snapshot* parent, int num_cores, MemorySize mem_size, + MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, + QJsonObject metadata); + + BaseSnapshot(std::string name, std::string comment, const Snapshot* parent, const VMSpecs& specs); +private: + std::string name; + std::string comment; + const Snapshot* parent; + int num_cores; + MemorySize mem_size; + MemorySize disk_space; + VirtualMachine::State state; + std::unordered_map mounts; + QJsonObject metadata; +}; } // namespace multipass #endif // MULTIPASS_BASE_SNAPSHOT_H From 9c2efb243479d506bbcf1a34726e4108c00df325 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 14 Mar 2023 22:53:55 +0000 Subject: [PATCH 028/627] [shared] Implement getters in BaseSnapshot --- src/platform/backends/shared/base_snapshot.h | 55 ++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 394ffe2db66..6de7783dabc 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -38,6 +38,16 @@ class BaseSnapshot : public Snapshot BaseSnapshot(std::string name, std::string comment, const Snapshot* parent, const VMSpecs& specs); + const std::string& get_name() const noexcept override; + const std::string& get_comment() const noexcept override; + const Snapshot* get_parent() const noexcept override; + int get_num_cores() const noexcept override; + MemorySize get_mem_size() const noexcept override; + MemorySize get_disk_space() const noexcept override; + VirtualMachine::State get_state() const noexcept override; + const std::unordered_map& get_mounts() const noexcept override; + const QJsonObject& get_metadata() const noexcept override; + private: std::string name; std::string comment; @@ -51,4 +61,49 @@ class BaseSnapshot : public Snapshot }; } // namespace multipass +inline const std::string& multipass::BaseSnapshot::get_name() const noexcept +{ + return name; +} + +inline const std::string& multipass::BaseSnapshot::get_comment() const noexcept +{ + return comment; +} + +inline auto multipass::BaseSnapshot::get_parent() const noexcept -> const Snapshot* +{ + return parent; +} + +inline int multipass::BaseSnapshot::get_num_cores() const noexcept +{ + return num_cores; +} + +inline auto multipass::BaseSnapshot::get_mem_size() const noexcept -> MemorySize +{ + return mem_size; +} + +inline auto multipass::BaseSnapshot::get_disk_space() const noexcept -> MemorySize +{ + return disk_space; +} + +inline auto multipass::BaseSnapshot::get_state() const noexcept -> VirtualMachine::State +{ + return state; +} + +inline auto multipass::BaseSnapshot::get_mounts() const noexcept -> const std::unordered_map& +{ + return mounts; +} + +inline const QJsonObject& multipass::BaseSnapshot::get_metadata() const noexcept +{ + return metadata; +} + #endif // MULTIPASS_BASE_SNAPSHOT_H From 99569003e16f73ebffe7bed91132935ac45c03b0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 15 Mar 2023 00:48:55 +0000 Subject: [PATCH 029/627] [shared] Compose snapshots into VMs --- include/multipass/virtual_machine.h | 5 +++++ .../backends/shared/base_virtual_machine.cpp | 8 ++++++++ .../backends/shared/base_virtual_machine.h | 16 ++++++++++++++-- tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 8 ++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index fc9323496c6..9a28fabcdb3 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace multipass @@ -35,6 +36,7 @@ class MemorySize; class SSHKeyProvider; struct VMMount; class MountHandler; +class Snapshot; class VirtualMachine : private DisabledCopyMove { @@ -81,6 +83,9 @@ class VirtualMachine : private DisabledCopyMove const std::string& target, const VMMount& mount) = 0; + using SnapshotMap = std::unordered_map>; + virtual const SnapshotMap& get_snapshots() const noexcept = 0; + VirtualMachine::State state; const std::string vm_name; std::condition_variable state_wait; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 02aaddb9f39..45a165e507c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -19,6 +19,7 @@ #include #include +#include namespace mp = multipass; namespace mpl = multipass::logging; @@ -26,6 +27,13 @@ namespace mpl = multipass::logging; namespace multipass { +BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name) + : VirtualMachine(state, vm_name){}; + +BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; + +BaseVirtualMachine::~BaseVirtualMachine() = default; // Snapshot is now fully defined, this can call unique_ptr's dtor + std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& key_provider) { std::vector all_ipv4; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 4ee04174986..0d1e9380c0f 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -38,8 +38,9 @@ namespace multipass class BaseVirtualMachine : public VirtualMachine { public: - BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name) : VirtualMachine(state, vm_name){}; - BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; + BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name); + BaseVirtualMachine(const std::string& vm_name); + ~BaseVirtualMachine(); // allow composing unique_ptr to fwd-declared Snapshots std::vector get_all_ipv4(const SSHKeyProvider& key_provider) override; std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, @@ -48,7 +49,18 @@ class BaseVirtualMachine : public VirtualMachine { throw NotImplementedOnThisBackendException("native mounts"); }; + + const SnapshotMap& get_snapshots() const noexcept override; + +protected: + SnapshotMap snapshots; }; + +inline auto multipass::BaseVirtualMachine::get_snapshots() const noexcept -> const SnapshotMap& +{ + return snapshots; +} + } // namespace multipass #endif // MULTIPASS_BASE_VIRTUAL_MACHINE_H diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 81df94b4408..5cc2e12e142 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -66,6 +66,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, resize_disk, (const MemorySize& new_size), (override)); MOCK_METHOD(std::unique_ptr, make_native_mount_handler, (const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount), (override)); + MOCK_METHOD(const VirtualMachine::SnapshotMap&, get_snapshots, (), (const, override, noexcept)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 5ed0f25f5d5..180f3da8959 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -19,6 +19,7 @@ #define MULTIPASS_STUB_VIRTUAL_MACHINE_H #include "stub_mount_handler.h" +#include #include namespace multipass @@ -116,6 +117,13 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { return std::make_unique(); } + + const SnapshotMap& get_snapshots() const noexcept override + { + return snapshots; + } + + SnapshotMap snapshots{}; }; } // namespace test } // namespace multipass From fdbc6563bf75d960d201f4cb2b129de83cad8281 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 15 Mar 2023 10:19:02 +0000 Subject: [PATCH 030/627] [shared] Match `struct` use in fwd declaration To make compilers happy. --- src/platform/backends/shared/base_snapshot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 6de7783dabc..7c8cd23b3a2 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -27,7 +27,7 @@ namespace multipass { -class VMSpecs; +struct VMSpecs; class BaseSnapshot : public Snapshot { From 91294d847021fac81a3cafc322819856077546ee Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 15 Mar 2023 12:30:52 +0000 Subject: [PATCH 031/627] [shared] Take str params by ref, to leverage SSO --- src/platform/backends/shared/base_snapshot.cpp | 15 ++++++++------- src/platform/backends/shared/base_snapshot.h | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 674f590ed1e..82808dc8a32 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -22,11 +22,12 @@ namespace mp = multipass; -mp::BaseSnapshot::BaseSnapshot(std::string name, std::string comment, const mp::Snapshot* parent, int num_cores, - mp::MemorySize mem_size, mp::MemorySize disk_space, mp::VirtualMachine::State state, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) + const mp::Snapshot* parent, int num_cores, mp::MemorySize mem_size, + mp::MemorySize disk_space, mp::VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata) - : name{std::move(name)}, - comment{std::move(comment)}, + : name{name}, + comment{comment}, parent{parent}, num_cores{num_cores}, mem_size{mem_size}, @@ -37,9 +38,9 @@ mp::BaseSnapshot::BaseSnapshot(std::string name, std::string comment, const mp:: { } -mp::BaseSnapshot::BaseSnapshot(std::string name, std::string comment, const mp::Snapshot* parent, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const mp::Snapshot* parent, const mp::VMSpecs& specs) - : BaseSnapshot{std::move(name), std::move(comment), parent, specs.num_cores, specs.mem_size, - specs.disk_space, specs.state, specs.mounts, specs.metadata} + : BaseSnapshot{name, comment, parent, specs.num_cores, specs.mem_size, specs.disk_space, + specs.state, specs.mounts, specs.metadata} { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 7c8cd23b3a2..fc2ffedcd69 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -32,11 +32,11 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(std::string name, std::string comment, const Snapshot* parent, int num_cores, MemorySize mem_size, - MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, - QJsonObject metadata); + BaseSnapshot(const std::string& name, const std::string& comment, const Snapshot* parent, int num_cores, + MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, + std::unordered_map mounts, QJsonObject metadata); - BaseSnapshot(std::string name, std::string comment, const Snapshot* parent, const VMSpecs& specs); + BaseSnapshot(const std::string& name, const std::string& comment, const Snapshot* parent, const VMSpecs& specs); const std::string& get_name() const noexcept override; const std::string& get_comment() const noexcept override; From 17d141172dbad48b0f5ac1484b76211c1402c8ad Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 24 Mar 2023 10:43:32 +0000 Subject: [PATCH 032/627] [shared] Add method to take snapshots in VMs --- include/multipass/virtual_machine.h | 3 + src/daemon/daemon.cpp | 1 + .../backends/shared/base_virtual_machine.cpp | 8 ++ .../backends/shared/base_virtual_machine.h | 3 +- tests/mock_virtual_machine.h | 2 + tests/stub_snapshot.h | 82 +++++++++++++++++++ tests/stub_virtual_machine.h | 8 +- 7 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 tests/stub_snapshot.h diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 9a28fabcdb3..6673050a322 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -35,6 +35,7 @@ namespace multipass class MemorySize; class SSHKeyProvider; struct VMMount; +struct VMSpecs; class MountHandler; class Snapshot; @@ -85,6 +86,8 @@ class VirtualMachine : private DisabledCopyMove using SnapshotMap = std::unordered_map>; virtual const SnapshotMap& get_snapshots() const noexcept = 0; + virtual const Snapshot& take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 0f1abc55ff4..8de599db6be 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 45a165e507c..46288d6338c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -16,6 +16,7 @@ */ #include "base_virtual_machine.h" +#include "base_snapshot.h" // TODO@ricab may be able to remove this #include #include @@ -71,4 +72,11 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& return all_ipv4; } +const Snapshot& BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) +{ + static BaseSnapshot ret{{}, {}, nullptr, specs}; // TODO@ricab placeholder impl + return ret; +} + } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 0d1e9380c0f..29767628513 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -40,7 +40,7 @@ class BaseVirtualMachine : public VirtualMachine public: BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name); BaseVirtualMachine(const std::string& vm_name); - ~BaseVirtualMachine(); // allow composing unique_ptr to fwd-declared Snapshots + ~BaseVirtualMachine(); // allow composing unique_ptr to fwd-declared Snapshots NOLINT(modernize-use-override) std::vector get_all_ipv4(const SSHKeyProvider& key_provider) override; std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, @@ -51,6 +51,7 @@ class BaseVirtualMachine : public VirtualMachine }; const SnapshotMap& get_snapshots() const noexcept override; + const Snapshot& take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override; protected: SnapshotMap snapshots; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 5cc2e12e142..cf0395b22e6 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -67,6 +67,8 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::unique_ptr, make_native_mount_handler, (const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount), (override)); MOCK_METHOD(const VirtualMachine::SnapshotMap&, get_snapshots, (), (const, override, noexcept)); + MOCK_METHOD(const Snapshot&, take_snapshot, + (const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h new file mode 100644 index 00000000000..0864a08625a --- /dev/null +++ b/tests/stub_snapshot.h @@ -0,0 +1,82 @@ +/* + * 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_SNAPSHOT_H +#define MULTIPASS_STUB_SNAPSHOT_H + +#include +#include + +#include + +namespace multipass::test +{ +struct StubSnapshot : public Snapshot +{ + const std::string& get_name() const noexcept override + { + return name; + } + + const std::string& get_comment() const noexcept override + { + return comment; + } + + const Snapshot* get_parent() const noexcept override + { + return nullptr; + } + + int get_num_cores() const noexcept override + { + return 0; + } + + MemorySize get_mem_size() const noexcept override + { + return MemorySize{}; + } + + MemorySize get_disk_space() const noexcept override + { + return MemorySize{}; + } + + VirtualMachine::State get_state() const noexcept override + { + return VirtualMachine::State::off; + } + + const std::unordered_map& get_mounts() const noexcept override + { + return mounts; + } + + const QJsonObject& get_metadata() const noexcept override + { + return metadata; + } + + std::string name{}; + std::string comment{}; + std::unordered_map mounts; + QJsonObject metadata; +}; +} // namespace multipass::test + +#endif // MULTIPASS_STUB_SNAPSHOT_H diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 180f3da8959..228bb4652fe 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -19,7 +19,7 @@ #define MULTIPASS_STUB_VIRTUAL_MACHINE_H #include "stub_mount_handler.h" -#include +#include "stub_snapshot.h" #include namespace multipass @@ -123,7 +123,13 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return snapshots; } + const Snapshot& take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override + { + return snapshot; + } + SnapshotMap snapshots{}; + StubSnapshot snapshot; }; } // namespace test } // namespace multipass From 165b63054ff1f7630346a7ccf4fa9cc4aa011c96 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 23 Mar 2023 21:26:21 +0000 Subject: [PATCH 033/627] [daemon] Delegate snapshot taking to VMs --- src/daemon/daemon.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 8de599db6be..099e418908e 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2358,18 +2358,28 @@ try mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; - { // TODO@snapshots replace placeholder implementation - sleep(3); + const auto& instance_name = request->instance(); + auto [instance_trail, status] = find_instance_and_react(operative_instances, deleted_instances, instance_name, + require_operative_instances_reaction); + + if (status.ok()) + { + assert(instance_trail.index() == 0); + auto* vm_ptr = std::get<0>(instance_trail)->second.get(); + assert(vm_ptr); - mpl::log(mpl::Level::debug, category, "Snapshot placeholder"); + const auto spec_it = vm_instance_specs.find(instance_name); + assert(spec_it != vm_instance_specs.end() && "missing instance specs"); + + const auto& snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + // TODO@ricab persist generic snapshot info SnapshotReply reply; - auto snapshot_name = request->snapshot(); - reply.set_snapshot(snapshot_name.empty() ? "placeholder-name" : snapshot_name); + reply.set_snapshot(snapshot.get_name()); server->Write(reply); } - status_promise->set_value(grpc::Status::OK); + status_promise->set_value(status); } catch (const std::exception& e) { From 499286232235d6105d2a5d4c5769e70dfb4949b8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 24 Mar 2023 10:57:43 +0000 Subject: [PATCH 034/627] [daemon] Create new snapshot --- src/platform/backends/shared/base_virtual_machine.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 46288d6338c..1a6e0b78ad9 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -75,8 +75,14 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& const Snapshot& BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) { - static BaseSnapshot ret{{}, {}, nullptr, specs}; // TODO@ricab placeholder impl - return ret; + // TODO@ricab generate name + // TODO@ricab figure out parent + // TODO@ricab generate implementation-specific snapshot instead + const auto [it, success] = + snapshots.try_emplace(name, std::make_unique(name, comment, nullptr, specs)); + + // TODO@ricab refuse repeated + return *it->second; } } // namespace multipass From f4cb9ca593f8ebbf8d1d1e5525a80f76ab3f00d6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 27 Mar 2023 12:06:56 +0100 Subject: [PATCH 035/627] [daemon] Refuse snapshot whose name is taken --- .../exceptions/snapshot_name_taken.h | 38 +++++++++++++++++++ src/daemon/daemon.cpp | 5 +++ .../backends/shared/base_virtual_machine.cpp | 15 +++++++- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 include/multipass/exceptions/snapshot_name_taken.h diff --git a/include/multipass/exceptions/snapshot_name_taken.h b/include/multipass/exceptions/snapshot_name_taken.h new file mode 100644 index 00000000000..11a17cf490c --- /dev/null +++ b/include/multipass/exceptions/snapshot_name_taken.h @@ -0,0 +1,38 @@ +/* + * 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_SNAPSHOT_NAME_TAKEN_H +#define MULTIPASS_SNAPSHOT_NAME_TAKEN_H + +#include +#include + +#include + +namespace multipass +{ +class SnapshotNameTaken : public std::runtime_error +{ +public: + SnapshotNameTaken(const std::string& instance_name, const std::string& snapshot_name) + : std::runtime_error{fmt::format(R"(Snapshot "{}.{}" already exists)", instance_name, snapshot_name)} + { + } +}; +} // namespace multipass + +#endif // MULTIPASS_SNAPSHOT_NAME_TAKEN_H diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 099e418908e..e5efc20fc70 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -2381,6 +2382,10 @@ try status_promise->set_value(status); } +catch (const SnapshotNameTaken& 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::INTERNAL, e.what(), "")); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1a6e0b78ad9..4567659c0b8 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -18,6 +18,7 @@ #include "base_virtual_machine.h" #include "base_snapshot.h" // TODO@ricab may be able to remove this +#include #include #include #include @@ -65,7 +66,7 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& } catch (const SSHException& e) { - mpl::log(mpl::Level::debug, "base_vm", fmt::format("Error getting extra IP addresses: {}", e.what())); + mpl::log(mpl::Level::debug, vm_name, fmt::format("Error getting extra IP addresses: {}", e.what())); } } @@ -81,7 +82,17 @@ const Snapshot& BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const st const auto [it, success] = snapshots.try_emplace(name, std::make_unique(name, comment, nullptr, specs)); - // TODO@ricab refuse repeated + if (success) + { + mpl::log(mpl::Level::debug, vm_name, + fmt::format("New snapshot: {}; Total snapshots: {}", it->first, snapshots.size())); + } + else + { + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); + throw SnapshotNameTaken{vm_name, it->first}; + } + return *it->second; } From da85c02fa859d3b3f5429820555d2b6a09ebecd6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 24 Mar 2023 12:37:34 +0000 Subject: [PATCH 036/627] [shared] Record head snapshot and snapshot parents --- src/platform/backends/shared/base_virtual_machine.cpp | 5 +++-- src/platform/backends/shared/base_virtual_machine.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4567659c0b8..e344683b155 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -77,13 +77,14 @@ const Snapshot& BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const st const std::string& comment) { // TODO@ricab generate name - // TODO@ricab figure out parent // TODO@ricab generate implementation-specific snapshot instead + // TODO@ricab lock const auto [it, success] = - snapshots.try_emplace(name, std::make_unique(name, comment, nullptr, specs)); + snapshots.try_emplace(name, std::make_unique(name, comment, head_snapshot, specs)); if (success) { + head_snapshot = it->second.get(); mpl::log(mpl::Level::debug, vm_name, fmt::format("New snapshot: {}; Total snapshots: {}", it->first, snapshots.size())); } diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 29767628513..6526d6ba258 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -55,6 +55,7 @@ class BaseVirtualMachine : public VirtualMachine protected: SnapshotMap snapshots; + Snapshot* head_snapshot = nullptr; }; inline auto multipass::BaseVirtualMachine::get_snapshots() const noexcept -> const SnapshotMap& From 91a693f90e1d8676a9e28355e7f038522ad56324 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 24 Mar 2023 13:11:51 +0000 Subject: [PATCH 037/627] [shared] Improve snapshot code --- .../backends/shared/base_virtual_machine.cpp | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e344683b155..84b97aacf21 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -85,16 +85,26 @@ const Snapshot& BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const st if (success) { head_snapshot = it->second.get(); - mpl::log(mpl::Level::debug, vm_name, - fmt::format("New snapshot: {}; Total snapshots: {}", it->first, snapshots.size())); - } - else - { - mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); - throw SnapshotNameTaken{vm_name, it->first}; + + if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) + { + auto num_snapshots = snapshots.size(); + auto* parent = head_snapshot->get_parent(); + // TODO@ricab release lock + + assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); + const auto& parent_name = parent ? parent->get_name() : ""; + + mpl::log(log_detail_lvl, vm_name, + fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, + num_snapshots)); + } + + return *head_snapshot; } - return *it->second; + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); + throw SnapshotNameTaken{vm_name, it->first}; } } // namespace multipass From f3ae4c987a874fb5fdba529ac300635ccb079c04 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 27 Mar 2023 12:23:06 +0100 Subject: [PATCH 038/627] [shared] Implement thread safety in snapshot Protect snapshot taking against concurrent access. --- include/multipass/virtual_machine.h | 12 +++-- src/daemon/daemon.cpp | 13 +++-- .../backends/shared/base_virtual_machine.cpp | 50 ++++++++++++------- .../backends/shared/base_virtual_machine.h | 8 ++- tests/mock_virtual_machine.h | 2 +- tests/stub_virtual_machine.h | 5 +- 6 files changed, 60 insertions(+), 30 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 6673050a322..8da3a830600 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -30,6 +30,9 @@ #include #include +#include // TODO@ricab replace with generic utility for safe const refs +#include // TODO@ricab replace with generic utility for safe const refs + namespace multipass { class MemorySize; @@ -85,9 +88,12 @@ class VirtualMachine : private DisabledCopyMove const VMMount& mount) = 0; using SnapshotMap = std::unordered_map>; - virtual const SnapshotMap& get_snapshots() const noexcept = 0; - virtual const Snapshot& take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) = 0; + virtual const SnapshotMap& get_snapshots() const noexcept = 0; // TODO@ricab lock it + + using LockingConstSnapshotRef = + std::pair>; // TODO@ricab generalize + virtual LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e5efc20fc70..2af987e464d 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2372,11 +2372,16 @@ try const auto spec_it = vm_instance_specs.find(instance_name); assert(spec_it != vm_instance_specs.end() && "missing instance specs"); - const auto& snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); - // TODO@ricab persist generic snapshot info - SnapshotReply reply; - reply.set_snapshot(snapshot.get_name()); + + { + const auto& [snapshot, lock] = + vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + // TODO@ricab persist generic snapshot info + + reply.set_snapshot(snapshot.get_name()); + } + server->Write(reply); } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 84b97aacf21..43758bed67f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -73,38 +73,50 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& return all_ipv4; } -const Snapshot& BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) +BaseVirtualMachine::LockingConstSnapshotRef +BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) { // TODO@ricab generate name // TODO@ricab generate implementation-specific snapshot instead - // TODO@ricab lock - const auto [it, success] = - snapshots.try_emplace(name, std::make_unique(name, comment, head_snapshot, specs)); - if (success) { - head_snapshot = it->second.get(); + std::unique_lock write_lock{snapshot_mutex}; - if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) + const auto [it, success] = + snapshots.try_emplace(name, std::make_unique(name, comment, head_snapshot, specs)); + + if (success) { - auto num_snapshots = snapshots.size(); - auto* parent = head_snapshot->get_parent(); - // TODO@ricab release lock + head_snapshot = it->second.get(); - assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); - const auto& parent_name = parent ? parent->get_name() : ""; + // No writing from this point on + std::shared_lock read_lock{snapshot_mutex, std::defer_lock}; + { // TODO@snapshots might as well make this into a generic util + std::unique_lock transfer_lock{transfer_mutex}; // lock transfer from write to read lock - mpl::log(log_detail_lvl, vm_name, - fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, - num_snapshots)); - } + write_lock.unlock(); + read_lock.lock(); + } + + if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) + { + auto num_snapshots = snapshots.size(); + auto* parent = head_snapshot->get_parent(); - return *head_snapshot; + assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); + const auto& parent_name = parent ? parent->get_name() : ""; + + mpl::log(log_detail_lvl, vm_name, + fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, + num_snapshots)); + } + + return {*head_snapshot, std::move(read_lock)}; + } } mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); - throw SnapshotNameTaken{vm_name, it->first}; + throw SnapshotNameTaken{vm_name, name}; } } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 6526d6ba258..716588f3017 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -29,6 +29,9 @@ #include #include +#include +#include + namespace mp = multipass; namespace mpl = multipass::logging; namespace mpu = multipass::utils; @@ -51,11 +54,14 @@ class BaseVirtualMachine : public VirtualMachine }; const SnapshotMap& get_snapshots() const noexcept override; - const Snapshot& take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override; + LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) override; protected: SnapshotMap snapshots; Snapshot* head_snapshot = nullptr; + std::shared_mutex snapshot_mutex; // TODO@ricab will probably want this to be mutable + std::mutex transfer_mutex; }; inline auto multipass::BaseVirtualMachine::get_snapshots() const noexcept -> const SnapshotMap& diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index cf0395b22e6..1e1f1031e42 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -67,7 +67,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::unique_ptr, make_native_mount_handler, (const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount), (override)); MOCK_METHOD(const VirtualMachine::SnapshotMap&, get_snapshots, (), (const, override, noexcept)); - MOCK_METHOD(const Snapshot&, take_snapshot, + MOCK_METHOD(VirtualMachine::LockingConstSnapshotRef, take_snapshot, (const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 228bb4652fe..ca810a29fab 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -123,9 +123,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return snapshots; } - const Snapshot& take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override + LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) override { - return snapshot; + return {snapshot, std::shared_lock{}}; } SnapshotMap snapshots{}; From 1ae5cdada533b46fac4cfa9d707450616110f6d5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 27 Mar 2023 11:57:07 +0100 Subject: [PATCH 039/627] [todo] Update TODO tags --- include/multipass/virtual_machine.h | 8 ++++---- src/daemon/daemon.cpp | 2 +- src/platform/backends/shared/base_snapshot.cpp | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 6 +++--- src/platform/backends/shared/base_virtual_machine.h | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 8da3a830600..a2cc500e2d5 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -30,8 +30,8 @@ #include #include -#include // TODO@ricab replace with generic utility for safe const refs -#include // TODO@ricab replace with generic utility for safe const refs +#include // TODO@snapshots replace with generic utility for safe const refs +#include // TODO@snapshots replace with generic utility for safe const refs namespace multipass { @@ -88,10 +88,10 @@ class VirtualMachine : private DisabledCopyMove const VMMount& mount) = 0; using SnapshotMap = std::unordered_map>; - virtual const SnapshotMap& get_snapshots() const noexcept = 0; // TODO@ricab lock it + virtual const SnapshotMap& get_snapshots() const noexcept = 0; // TODO@snapshots lock it using LockingConstSnapshotRef = - std::pair>; // TODO@ricab generalize + std::pair>; // TODO@snapshots generalize virtual LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 2af987e464d..93c7aa1c237 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2377,7 +2377,7 @@ try { const auto& [snapshot, lock] = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); - // TODO@ricab persist generic snapshot info + // TODO@snapshots persist generic snapshot info reply.set_snapshot(snapshot.get_name()); } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 82808dc8a32..1fc8381ac67 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -16,7 +16,7 @@ */ #include "base_snapshot.h" -#include "daemon/vm_specs.h" // TODO@ricab move this +#include "daemon/vm_specs.h" // TODO@snapshots move this #include diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 43758bed67f..2bfaa677bc9 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -16,7 +16,7 @@ */ #include "base_virtual_machine.h" -#include "base_snapshot.h" // TODO@ricab may be able to remove this +#include "base_snapshot.h" // TODO@snapshots may be able to remove this #include #include @@ -76,8 +76,8 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& BaseVirtualMachine::LockingConstSnapshotRef BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) { - // TODO@ricab generate name - // TODO@ricab generate implementation-specific snapshot instead + // TODO@snapshots generate name + // TODO@snapshots generate implementation-specific snapshot instead { std::unique_lock write_lock{snapshot_mutex}; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 716588f3017..81aae18c50b 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -60,7 +60,7 @@ class BaseVirtualMachine : public VirtualMachine protected: SnapshotMap snapshots; Snapshot* head_snapshot = nullptr; - std::shared_mutex snapshot_mutex; // TODO@ricab will probably want this to be mutable + std::shared_mutex snapshot_mutex; // TODO@snapshots will probably want this to be mutable std::mutex transfer_mutex; }; From 029bae42c900568f6239865fe8fe781acdd83bd7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 27 Mar 2023 20:52:55 +0100 Subject: [PATCH 040/627] [shared] Tweak log for null snapshot parent Use '--' for snapshots with no parent, same as prescribed for the `info` cmd. --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2bfaa677bc9..2782a948758 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -104,7 +104,7 @@ BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, auto* parent = head_snapshot->get_parent(); assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); - const auto& parent_name = parent ? parent->get_name() : ""; + const auto& parent_name = parent ? parent->get_name() : "--"; mpl::log(log_detail_lvl, vm_name, fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, From 79edab32af872eff965d4918f62947fe77f8464b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 12:13:11 +0100 Subject: [PATCH 041/627] [cli] Allow `-c` for comments in `snapshot` too --- src/client/cli/cmd/snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index df64d6a705d..95ebc1c399a 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -75,7 +75,7 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) "[snapshot]"); QCommandLineOption comment_opt{ - {"comment", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; + {"comment", "c", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; parser->addOption(comment_opt); if (auto status = parser->commandParse(this); status != ParseCode::Ok) From 249e4c127b2962c69ac2136579ea557b28d48431 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 12:27:27 +0100 Subject: [PATCH 042/627] [tests] Test comment arg in `snapshot` --- tests/test_cli_client.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 306769d4a14..49eda399947 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -3155,6 +3155,22 @@ TEST_F(Client, snapshotCmdGoodArgsOk) EXPECT_EQ(send_command({"snapshot", "foo", "rocky"}), mp::ReturnCode::Ok); } +TEST_F(Client, snapshotCmdCommentOptionAlternativesOk) +{ + EXPECT_CALL(mock_daemon, snapshot).Times(3); + EXPECT_EQ(send_command({"snapshot", "--comment", "a comment", "foo"}), mp::ReturnCode::Ok); + EXPECT_EQ(send_command({"snapshot", "-c", "a comment", "foo"}), mp::ReturnCode::Ok); + EXPECT_EQ(send_command({"snapshot", "-m", "a comment", "foo"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, snapshotCmdCommentConsumesArg) +{ + EXPECT_CALL(mock_daemon, snapshot).Times(0); + EXPECT_EQ(send_command({"snapshot", "--comment", "foo"}), mp::ReturnCode::CommandLineError); + EXPECT_EQ(send_command({"snapshot", "-c", "foo"}), mp::ReturnCode::CommandLineError); + EXPECT_EQ(send_command({"snapshot", "-m", "foo"}), mp::ReturnCode::CommandLineError); +} + TEST_F(Client, snapshotCmdTooFewArgsFails) { EXPECT_EQ(send_command({"snapshot", "-m", "Who controls the past controls the future"}), From 871eb910c36709edbad19cb2580cc9ce1e228832 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 12:33:44 +0100 Subject: [PATCH 043/627] [cli] Fix description for name arg in `snapshot` --- src/client/cli/cmd/snapshot.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 95ebc1c399a..ca8120132a8 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -67,12 +67,10 @@ QString cmd::Snapshot::description() const mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) { parser->addPositionalArgument("instance", "The instance to take a snapshot of."); - parser->addPositionalArgument( - "snapshot", - "An optional name for the snapshot (default: \"snapshotN\", where N is an index number, i.e. the next " - "non-negative integer that has not been used to generate a snapshot name for yet, and which is " - "currently available).", - "[snapshot]"); + parser->addPositionalArgument("snapshot", + "An optional name for the snapshot (default: \"snapshotN\", where N is one plus the " + "number of snapshot that were ever taken for .", + "[snapshot]"); QCommandLineOption comment_opt{ {"comment", "c", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; From 3ea95bb3d02e1888e77fa1d65aaedd32fb922939 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 12:41:30 +0100 Subject: [PATCH 044/627] [test] Adapt tests for `snapshot` name as option (Still failing.) --- tests/test_cli_client.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 49eda399947..21da093f2f7 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -3149,10 +3149,24 @@ TEST_F(Client, snapshotCmdHelpOk) EXPECT_EQ(send_command({"snapshot", "--help"}), mp::ReturnCode::Ok); } -TEST_F(Client, snapshotCmdGoodArgsOk) +TEST_F(Client, snapshotCmdNoOptionsOk) { EXPECT_CALL(mock_daemon, snapshot); - EXPECT_EQ(send_command({"snapshot", "foo", "rocky"}), mp::ReturnCode::Ok); + EXPECT_EQ(send_command({"snapshot", "foo"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, snapshotCmdNameAlternativesOk) +{ + EXPECT_CALL(mock_daemon, snapshot).Times(2); + EXPECT_EQ(send_command({"snapshot", "-n", "bar", "foo"}), mp::ReturnCode::Ok); + EXPECT_EQ(send_command({"snapshot", "--name", "bar", "foo"}), mp::ReturnCode::Ok); +} + +TEST_F(Client, snapshotCmdNameConsumesArg) +{ + EXPECT_CALL(mock_daemon, snapshot).Times(0); + EXPECT_EQ(send_command({"snapshot", "--name", "foo"}), mp::ReturnCode::CommandLineError); + EXPECT_EQ(send_command({"snapshot", "-n", "foo"}), mp::ReturnCode::CommandLineError); } TEST_F(Client, snapshotCmdCommentOptionAlternativesOk) @@ -3179,7 +3193,7 @@ TEST_F(Client, snapshotCmdTooFewArgsFails) TEST_F(Client, snapshotCmdTooManyArgsFails) { - EXPECT_EQ(send_command({"snaphot", "foo", "bar"}), mp::ReturnCode::CommandLineError); + EXPECT_EQ(send_command({"snapshot", "foo", "bar"}), mp::ReturnCode::CommandLineError); } TEST_F(Client, snapshotCmdInvalidOptionFails) From 7a26b2a5db8445797a6fea2b0c7ea72b530ae0ad Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 12:42:44 +0100 Subject: [PATCH 045/627] [cli] Turn name into an option in `snapshot` --- src/client/cli/cmd/snapshot.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index ca8120132a8..d114d0a5b13 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -67,14 +67,13 @@ QString cmd::Snapshot::description() const mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) { parser->addPositionalArgument("instance", "The instance to take a snapshot of."); - parser->addPositionalArgument("snapshot", - "An optional name for the snapshot (default: \"snapshotN\", where N is one plus the " - "number of snapshot that were ever taken for .", - "[snapshot]"); - + QCommandLineOption name_opt({"n", "name"}, + "An optional name for the snapshot (default: \"snapshotN\", where N is one plus the " + "number of snapshot that were ever taken for .", + "name"); QCommandLineOption comment_opt{ {"comment", "c", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; - parser->addOption(comment_opt); + parser->addOptions({name_opt, comment_opt}); if (auto status = parser->commandParse(this); status != ParseCode::Ok) return status; @@ -87,7 +86,7 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) return ParseCode::CommandLineError; } - if (num_args > 2) + if (num_args > 1) { cerr << "Too many arguments supplied\n"; return ParseCode::CommandLineError; @@ -95,10 +94,7 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) request.set_instance(positional_args.first().toStdString()); request.set_comment(parser->value(comment_opt).toStdString()); - - if (num_args == 2) - request.set_snapshot(positional_args.at(1).toStdString()); - + request.set_snapshot(parser->value(name_opt).toStdString()); request.set_verbosity_level(parser->verbosityLevel()); return ParseCode::Ok; From 7c09f0b84dd6bf15e35c35e07cc5b21679aad4bd Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 12:21:56 +0100 Subject: [PATCH 046/627] [shared] Replace get_ with view_snapshots Replace `get_snapshots` with `view_snapshots` and return a list of copied shared_ptrs, that share ownership. (Breaks compilation.) --- include/multipass/virtual_machine.h | 5 ++--- src/platform/backends/shared/base_virtual_machine.h | 4 ++-- tests/stub_virtual_machine.h | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index a2cc500e2d5..acbee3be234 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -27,7 +27,6 @@ #include #include #include -#include #include #include // TODO@snapshots replace with generic utility for safe const refs @@ -87,8 +86,8 @@ class VirtualMachine : private DisabledCopyMove const std::string& target, const VMMount& mount) = 0; - using SnapshotMap = std::unordered_map>; - virtual const SnapshotMap& get_snapshots() const noexcept = 0; // TODO@snapshots lock it + using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views + virtual SnapshotVista view_snapshots() const noexcept = 0; using LockingConstSnapshotRef = std::pair>; // TODO@snapshots generalize diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 81aae18c50b..61b12f2ae84 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -53,7 +53,7 @@ class BaseVirtualMachine : public VirtualMachine throw NotImplementedOnThisBackendException("native mounts"); }; - const SnapshotMap& get_snapshots() const noexcept override; + SnapshotVista view_snapshots() const noexcept override; LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override; @@ -64,7 +64,7 @@ class BaseVirtualMachine : public VirtualMachine std::mutex transfer_mutex; }; -inline auto multipass::BaseVirtualMachine::get_snapshots() const noexcept -> const SnapshotMap& +inline auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista { return snapshots; } diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index ca810a29fab..eddf45bddd8 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -118,7 +118,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return std::make_unique(); } - const SnapshotMap& get_snapshots() const noexcept override + SnapshotVista view_snapshots() const noexcept override { return snapshots; } From a0e73a3667b7402a7768e4b36f891d6ddc5d3000 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 12:26:43 +0100 Subject: [PATCH 047/627] [shared] Replaced LockingRef with shared_ptr Return a shared_ptr to const instead of a locking const ref, to reduce the risk of deadlocks. (Build still broken.) --- include/multipass/virtual_machine.h | 9 ++------- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- src/platform/backends/shared/base_virtual_machine.h | 4 ++-- tests/stub_virtual_machine.h | 4 ++-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index acbee3be234..48310abd774 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -29,9 +29,6 @@ #include #include -#include // TODO@snapshots replace with generic utility for safe const refs -#include // TODO@snapshots replace with generic utility for safe const refs - namespace multipass { class MemorySize; @@ -89,10 +86,8 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; - using LockingConstSnapshotRef = - std::pair>; // TODO@snapshots generalize - virtual LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) = 0; + virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2782a948758..c7818c75084 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -73,8 +73,8 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& return all_ipv4; } -BaseVirtualMachine::LockingConstSnapshotRef -BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) +std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) { // TODO@snapshots generate name // TODO@snapshots generate implementation-specific snapshot instead diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 61b12f2ae84..53f1c1c5552 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -54,8 +54,8 @@ class BaseVirtualMachine : public VirtualMachine }; SnapshotVista view_snapshots() const noexcept override; - LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) override; + std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) override; protected: SnapshotMap snapshots; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index eddf45bddd8..5ead1bbdecd 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -123,8 +123,8 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return snapshots; } - LockingConstSnapshotRef take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) override + std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) override { return {snapshot, std::shared_lock{}}; } From ac94fc2e28e223ae3bf587bb7856180f0d584ac8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 12:48:30 +0100 Subject: [PATCH 048/627] [shared] Use shared_ptrs in VM's snapshots map (Build still broken.) --- src/platform/backends/shared/base_virtual_machine.cpp | 2 -- src/platform/backends/shared/base_virtual_machine.h | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c7818c75084..f49aebd9829 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -34,8 +34,6 @@ BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::s BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; -BaseVirtualMachine::~BaseVirtualMachine() = default; // Snapshot is now fully defined, this can call unique_ptr's dtor - std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& key_provider) { std::vector all_ipv4; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 53f1c1c5552..4e47af85269 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -29,8 +29,10 @@ #include #include +#include #include #include +#include namespace mp = multipass; namespace mpl = multipass::logging; @@ -43,7 +45,6 @@ class BaseVirtualMachine : public VirtualMachine public: BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name); BaseVirtualMachine(const std::string& vm_name); - ~BaseVirtualMachine(); // allow composing unique_ptr to fwd-declared Snapshots NOLINT(modernize-use-override) std::vector get_all_ipv4(const SSHKeyProvider& key_provider) override; std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, @@ -58,6 +59,7 @@ class BaseVirtualMachine : public VirtualMachine const std::string& comment) override; protected: + using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; Snapshot* head_snapshot = nullptr; std::shared_mutex snapshot_mutex; // TODO@snapshots will probably want this to be mutable From d0b959f28646513723f3bae92f183d0a4d950552 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 12:51:46 +0100 Subject: [PATCH 049/627] [shared] Implement view_snapshots (Build still broken.) --- .../backends/shared/base_virtual_machine.cpp | 12 ++++++++++++ src/platform/backends/shared/base_virtual_machine.h | 7 +------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f49aebd9829..f75c9cc6da4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -71,6 +71,18 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& return all_ipv4; } +auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista +{ + SnapshotVista ret; + + std::shared_lock read_lock{snapshot_mutex}; + ret.reserve(snapshots.size()); + std::transform(std::cbegin(snapshots), std::cend(snapshots), std::back_inserter(ret), + [](const auto& pair) { return pair.second; }); + + return ret; +} + std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) { diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 4e47af85269..23f1f8ecc99 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -62,15 +62,10 @@ class BaseVirtualMachine : public VirtualMachine using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; Snapshot* head_snapshot = nullptr; - std::shared_mutex snapshot_mutex; // TODO@snapshots will probably want this to be mutable + mutable std::shared_mutex snapshot_mutex; std::mutex transfer_mutex; }; -inline auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista -{ - return snapshots; -} - } // namespace multipass #endif // MULTIPASS_BASE_VIRTUAL_MACHINE_H From 9a15b2d1d2be75f90a231a187c540741d2c06a2c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 12:58:24 +0100 Subject: [PATCH 050/627] [shared] Use shared_ptr in take_snapshot (Build still broken.) --- src/platform/backends/shared/base_virtual_machine.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f75c9cc6da4..7619e546175 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -93,11 +93,12 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& std::unique_lock write_lock{snapshot_mutex}; const auto [it, success] = - snapshots.try_emplace(name, std::make_unique(name, comment, head_snapshot, specs)); + snapshots.try_emplace(name, std::make_shared(name, comment, head_snapshot, specs)); if (success) { - head_snapshot = it->second.get(); + auto ret = it->second; + head_snapshot = ret.get(); // No writing from this point on std::shared_lock read_lock{snapshot_mutex, std::defer_lock}; @@ -121,7 +122,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& num_snapshots)); } - return {*head_snapshot, std::move(read_lock)}; + return ret; } } From dfd3ea5d8aad862ec84e51c189dda5b338412954 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 13:01:23 +0100 Subject: [PATCH 051/627] [daemon] Adapt to new snapshot return (Build still broken.) --- src/daemon/daemon.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 93c7aa1c237..04ecdaf15d9 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2375,11 +2375,10 @@ try SnapshotReply reply; { - const auto& [snapshot, lock] = - vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); // TODO@snapshots persist generic snapshot info - reply.set_snapshot(snapshot.get_name()); + reply.set_snapshot(snapshot->get_name()); } server->Write(reply); From 2024941b76674d3097a2f9bade30bfaa8c149171 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 13:28:16 +0100 Subject: [PATCH 052/627] [tests] Adapt VM mocks to new snapshots interface (Fixes build.) --- tests/mock_virtual_machine.h | 4 ++-- tests/stub_virtual_machine.h | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 1e1f1031e42..b509f038bcf 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -66,8 +66,8 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, resize_disk, (const MemorySize& new_size), (override)); MOCK_METHOD(std::unique_ptr, make_native_mount_handler, (const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount), (override)); - MOCK_METHOD(const VirtualMachine::SnapshotMap&, get_snapshots, (), (const, override, noexcept)); - MOCK_METHOD(VirtualMachine::LockingConstSnapshotRef, take_snapshot, + MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); + MOCK_METHOD(std::shared_ptr, take_snapshot, (const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 5ead1bbdecd..c2f32e6e3a0 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -120,16 +120,15 @@ struct StubVirtualMachine final : public multipass::VirtualMachine SnapshotVista view_snapshots() const noexcept override { - return snapshots; + return {}; } std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override { - return {snapshot, std::shared_lock{}}; + return {}; } - SnapshotMap snapshots{}; StubSnapshot snapshot; }; } // namespace test From 477aadf8d2198cd3a97dc130c3dd346b10f7c114 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 15:24:03 +0100 Subject: [PATCH 053/627] [shared] Simplify locking when taking snapshots --- .../backends/shared/base_virtual_machine.cpp | 14 +++----------- .../backends/shared/base_virtual_machine.h | 2 -- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7619e546175..2442c2bf515 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -99,20 +99,12 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& { auto ret = it->second; head_snapshot = ret.get(); - - // No writing from this point on - std::shared_lock read_lock{snapshot_mutex, std::defer_lock}; - { // TODO@snapshots might as well make this into a generic util - std::unique_lock transfer_lock{transfer_mutex}; // lock transfer from write to read lock - - write_lock.unlock(); - read_lock.lock(); - } + auto num_snapshots = snapshots.size(); + write_lock.unlock(); if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) { - auto num_snapshots = snapshots.size(); - auto* parent = head_snapshot->get_parent(); + auto* parent = ret->get_parent(); assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); const auto& parent_name = parent ? parent->get_name() : "--"; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 23f1f8ecc99..bb96cb7bff4 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -30,7 +30,6 @@ #include #include -#include #include #include @@ -63,7 +62,6 @@ class BaseVirtualMachine : public VirtualMachine SnapshotMap snapshots; Snapshot* head_snapshot = nullptr; mutable std::shared_mutex snapshot_mutex; - std::mutex transfer_mutex; }; } // namespace multipass From b1c45fbc7974d55d91182a9d86a231c296c91c59 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 15:25:49 +0100 Subject: [PATCH 054/627] [shared] Return copies of snapshot name/comment To ease thread synchronization. --- include/multipass/snapshot.h | 6 +++--- src/platform/backends/shared/base_snapshot.h | 8 ++++---- tests/stub_snapshot.h | 10 ++++------ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 3dd742656cb..522e7b0a4d6 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -36,9 +36,9 @@ class Snapshot : private DisabledCopyMove public: virtual ~Snapshot() = default; - virtual const std::string& get_name() const noexcept = 0; - virtual const std::string& get_comment() const noexcept = 0; - virtual const Snapshot* get_parent() const noexcept = 0; + virtual std::string get_name() const noexcept = 0; + virtual std::string get_comment() const noexcept = 0; + virtual const Snapshot* get_parent() const noexcept = 0; // TODO@ricab shptr virtual int get_num_cores() const noexcept = 0; virtual MemorySize get_mem_size() const noexcept = 0; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index fc2ffedcd69..14862ce2838 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -38,8 +38,8 @@ class BaseSnapshot : public Snapshot BaseSnapshot(const std::string& name, const std::string& comment, const Snapshot* parent, const VMSpecs& specs); - const std::string& get_name() const noexcept override; - const std::string& get_comment() const noexcept override; + std::string get_name() const noexcept override; + std::string get_comment() const noexcept override; const Snapshot* get_parent() const noexcept override; int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; @@ -61,12 +61,12 @@ class BaseSnapshot : public Snapshot }; } // namespace multipass -inline const std::string& multipass::BaseSnapshot::get_name() const noexcept +inline std::string multipass::BaseSnapshot::get_name() const noexcept { return name; } -inline const std::string& multipass::BaseSnapshot::get_comment() const noexcept +inline std::string multipass::BaseSnapshot::get_comment() const noexcept { return comment; } diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 0864a08625a..f5bc2142aaf 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -27,14 +27,14 @@ namespace multipass::test { struct StubSnapshot : public Snapshot { - const std::string& get_name() const noexcept override + std::string get_name() const noexcept override { - return name; + return {}; } - const std::string& get_comment() const noexcept override + std::string get_comment() const noexcept override { - return comment; + return {}; } const Snapshot* get_parent() const noexcept override @@ -72,8 +72,6 @@ struct StubSnapshot : public Snapshot return metadata; } - std::string name{}; - std::string comment{}; std::unordered_map mounts; QJsonObject metadata; }; From 278381be3da9f0651ab65af90b91233d8af6a3fb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 16:51:46 +0100 Subject: [PATCH 055/627] [shared] Share ownership of snapshot parents --- include/multipass/snapshot.h | 5 ++++- src/platform/backends/shared/base_snapshot.cpp | 12 ++++++------ src/platform/backends/shared/base_snapshot.h | 13 +++++++------ .../backends/shared/base_virtual_machine.cpp | 3 +-- src/platform/backends/shared/base_virtual_machine.h | 2 +- tests/stub_snapshot.h | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 522e7b0a4d6..dc00ece9416 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -21,6 +21,7 @@ #include "disabled_copy_move.h" #include "virtual_machine.h" +#include #include #include @@ -38,12 +39,14 @@ class Snapshot : private DisabledCopyMove virtual std::string get_name() const noexcept = 0; virtual std::string get_comment() const noexcept = 0; - virtual const Snapshot* get_parent() const noexcept = 0; // TODO@ricab shptr + virtual std::shared_ptr get_parent() const noexcept = 0; virtual int get_num_cores() const noexcept = 0; virtual MemorySize get_mem_size() const noexcept = 0; virtual MemorySize get_disk_space() const noexcept = 0; virtual VirtualMachine::State get_state() const noexcept = 0; + + // Note that these return references - careful not to change them in the meantime virtual const std::unordered_map& get_mounts() const noexcept = 0; virtual const QJsonObject& get_metadata() const noexcept = 0; }; diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 1fc8381ac67..2eed4fa8af1 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -23,12 +23,12 @@ namespace mp = multipass; mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) - const mp::Snapshot* parent, int num_cores, mp::MemorySize mem_size, - mp::MemorySize disk_space, mp::VirtualMachine::State state, + std::shared_ptr parent, int num_cores, MemorySize mem_size, + MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata) : name{name}, comment{comment}, - parent{parent}, + parent{std::move(parent)}, num_cores{num_cores}, mem_size{mem_size}, disk_space{disk_space}, @@ -38,9 +38,9 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme { } -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const mp::Snapshot* parent, - const mp::VMSpecs& specs) - : BaseSnapshot{name, comment, parent, specs.num_cores, specs.mem_size, specs.disk_space, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, const VMSpecs& specs) + : BaseSnapshot{name, comment, std::move(parent), specs.num_cores, specs.mem_size, specs.disk_space, specs.state, specs.mounts, specs.metadata} { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 14862ce2838..b5f23ebfb33 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -32,15 +32,16 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(const std::string& name, const std::string& comment, const Snapshot* parent, int num_cores, - MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, + BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, + int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata); - BaseSnapshot(const std::string& name, const std::string& comment, const Snapshot* parent, const VMSpecs& specs); + BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, + const VMSpecs& specs); std::string get_name() const noexcept override; std::string get_comment() const noexcept override; - const Snapshot* get_parent() const noexcept override; + std::shared_ptr get_parent() const noexcept override; int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; MemorySize get_disk_space() const noexcept override; @@ -51,7 +52,7 @@ class BaseSnapshot : public Snapshot private: std::string name; std::string comment; - const Snapshot* parent; + std::shared_ptr parent; int num_cores; MemorySize mem_size; MemorySize disk_space; @@ -71,7 +72,7 @@ inline std::string multipass::BaseSnapshot::get_comment() const noexcept return comment; } -inline auto multipass::BaseSnapshot::get_parent() const noexcept -> const Snapshot* +inline auto multipass::BaseSnapshot::get_parent() const noexcept -> std::shared_ptr { return parent; } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2442c2bf515..4d775db22d7 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -97,8 +97,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& if (success) { - auto ret = it->second; - head_snapshot = ret.get(); + auto ret = head_snapshot = it->second; auto num_snapshots = snapshots.size(); write_lock.unlock(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index bb96cb7bff4..4a305f870f2 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -60,7 +60,7 @@ class BaseVirtualMachine : public VirtualMachine protected: using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; - Snapshot* head_snapshot = nullptr; + std::shared_ptr head_snapshot = nullptr; mutable std::shared_mutex snapshot_mutex; }; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index f5bc2142aaf..788492d925f 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -37,7 +37,7 @@ struct StubSnapshot : public Snapshot return {}; } - const Snapshot* get_parent() const noexcept override + std::shared_ptr get_parent() const noexcept override { return nullptr; } From fa5620aa83ce2ad111fe55a4a2b095c632ff87e5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 16:54:08 +0100 Subject: [PATCH 056/627] [shared] Fix unprotected read of parent snapshot --- src/platform/backends/shared/base_virtual_machine.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4d775db22d7..70ecd9f440c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -99,14 +99,13 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& { auto ret = head_snapshot = it->second; auto num_snapshots = snapshots.size(); + auto parent = ret->get_parent(); write_lock.unlock(); + assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) { - auto* parent = ret->get_parent(); - - assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); - const auto& parent_name = parent ? parent->get_name() : "--"; + auto parent_name = parent ? parent->get_name() : "--"; mpl::log(log_detail_lvl, vm_name, fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, From 50efe6ca7834ed2e99d39d0540d3723408a50e3f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 17:50:37 +0100 Subject: [PATCH 057/627] [shared] Add setters for some snapshot properties Make the rest immutable. --- include/multipass/snapshot.h | 12 +++-- src/platform/backends/shared/base_snapshot.h | 55 +++++++++++++++----- tests/stub_snapshot.h | 12 +++++ 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index dc00ece9416..3408313b038 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -37,18 +37,22 @@ class Snapshot : private DisabledCopyMove public: virtual ~Snapshot() = default; - virtual std::string get_name() const noexcept = 0; - virtual std::string get_comment() const noexcept = 0; - virtual std::shared_ptr get_parent() const noexcept = 0; + virtual std::string get_name() const = 0; + virtual std::string get_comment() const = 0; + virtual std::shared_ptr get_parent() const = 0; virtual int get_num_cores() const noexcept = 0; virtual MemorySize get_mem_size() const noexcept = 0; virtual MemorySize get_disk_space() const noexcept = 0; virtual VirtualMachine::State get_state() const noexcept = 0; - // Note that these return references - careful not to change them in the meantime + // Note that these return references - careful not to delete the snapshot while they are in use virtual const std::unordered_map& get_mounts() const noexcept = 0; virtual const QJsonObject& get_metadata() const noexcept = 0; + + virtual void set_name(const std::string&) = 0; + virtual void set_comment(const std::string&) = 0; + virtual void set_parent(std::shared_ptr) = 0; }; } // namespace multipass diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index b5f23ebfb33..634a38e6d6c 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -25,6 +25,8 @@ #include +#include + namespace multipass { struct VMSpecs; @@ -39,9 +41,9 @@ class BaseSnapshot : public Snapshot BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs); - std::string get_name() const noexcept override; - std::string get_comment() const noexcept override; - std::shared_ptr get_parent() const noexcept override; + std::string get_name() const override; + std::string get_comment() const override; + std::shared_ptr get_parent() const override; int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; MemorySize get_disk_space() const noexcept override; @@ -49,31 +51,42 @@ class BaseSnapshot : public Snapshot const std::unordered_map& get_mounts() const noexcept override; const QJsonObject& get_metadata() const noexcept override; + void set_name(const std::string& n) override; + void set_comment(const std::string& c) override; + void set_parent(std::shared_ptr p) override; + private: std::string name; std::string comment; std::shared_ptr parent; - int num_cores; - MemorySize mem_size; - MemorySize disk_space; - VirtualMachine::State state; - std::unordered_map mounts; - QJsonObject metadata; + + // This class is non-copyable and having these const simplifies thread safety + const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const MemorySize mem_size; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const MemorySize disk_space; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const VirtualMachine::State state; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const std::unordered_map mounts; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const QJsonObject metadata; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + + mutable std::shared_mutex mutex; }; } // namespace multipass -inline std::string multipass::BaseSnapshot::get_name() const noexcept +inline std::string multipass::BaseSnapshot::get_name() const { + const std::shared_lock lock{mutex}; return name; } -inline std::string multipass::BaseSnapshot::get_comment() const noexcept +inline std::string multipass::BaseSnapshot::get_comment() const { + const std::shared_lock lock{mutex}; return comment; } -inline auto multipass::BaseSnapshot::get_parent() const noexcept -> std::shared_ptr +inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr { + const std::shared_lock lock{mutex}; return parent; } @@ -107,4 +120,22 @@ inline const QJsonObject& multipass::BaseSnapshot::get_metadata() const noexcept return metadata; } +inline void multipass::BaseSnapshot::set_name(const std::string& n) +{ + const std::unique_lock lock{mutex}; + name = n; +} + +inline void multipass::BaseSnapshot::set_comment(const std::string& c) +{ + const std::unique_lock lock{mutex}; + comment = c; +} + +inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) +{ + const std::unique_lock lock{mutex}; + parent = std::move(p); +} + #endif // MULTIPASS_BASE_SNAPSHOT_H diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 788492d925f..53ce99e7707 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -72,6 +72,18 @@ struct StubSnapshot : public Snapshot return metadata; } + void set_name(const std::string&) override + { + } + + void set_comment(const std::string&) override + { + } + + void set_parent(std::shared_ptr) override + { + } + std::unordered_map mounts; QJsonObject metadata; }; From 5336b9290ee5fb235ba1c857dc30d8b960e13af5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 18:49:46 +0100 Subject: [PATCH 058/627] [shared] Ask the snapshot to serialize to JSON (With placeholder implementation.) --- include/multipass/snapshot.h | 4 +++- src/daemon/daemon.cpp | 1 + src/platform/backends/shared/base_snapshot.cpp | 8 ++++++++ src/platform/backends/shared/base_snapshot.h | 2 ++ tests/stub_snapshot.h | 5 +++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 3408313b038..cefeac19740 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -34,7 +34,7 @@ struct VMMount; class Snapshot : private DisabledCopyMove { -public: +public: // TODO@snapshots drop any accessors that we end up not needing virtual ~Snapshot() = default; virtual std::string get_name() const = 0; @@ -50,6 +50,8 @@ class Snapshot : private DisabledCopyMove virtual const std::unordered_map& get_mounts() const noexcept = 0; virtual const QJsonObject& get_metadata() const noexcept = 0; + virtual QJsonObject serialize() const = 0; + virtual void set_name(const std::string&) = 0; virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 04ecdaf15d9..09e51035945 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2376,6 +2376,7 @@ try { auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + auto snapshot_json = snapshot->serialize(); // TODO@snapshots persist generic snapshot info reply.set_snapshot(snapshot->get_name()); diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 2eed4fa8af1..e52ad8d5b87 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -20,6 +20,8 @@ #include +#include // TODO@snapshots may be able to drop after extracting JSON utilities + namespace mp = multipass; mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) @@ -44,3 +46,9 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme specs.state, specs.mounts, specs.metadata} { } + +QJsonObject multipass::BaseSnapshot::serialize() const +{ + QJsonObject ret{}; + return ret; +} diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 634a38e6d6c..fda54d086c4 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -51,6 +51,8 @@ class BaseSnapshot : public Snapshot const std::unordered_map& get_mounts() const noexcept override; const QJsonObject& get_metadata() const noexcept override; + QJsonObject serialize() const override; + void set_name(const std::string& n) override; void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 53ce99e7707..c3ec6cd79dd 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -72,6 +72,11 @@ struct StubSnapshot : public Snapshot return metadata; } + QJsonObject serialize() const override + { + return {}; + } + void set_name(const std::string&) override { } From 2aa4be322bfded71fa85d5a20a4c7e065e24ccf9 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 18:47:01 +0100 Subject: [PATCH 059/627] [shared] Implement generic snapshot serialization --- .../backends/shared/base_snapshot.cpp | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index e52ad8d5b87..7063c1cafb4 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -50,5 +50,56 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme QJsonObject multipass::BaseSnapshot::serialize() const { QJsonObject ret{}; + const std::shared_lock lock{mutex}; + + ret.insert("name", QString::fromStdString(name)); + ret.insert("comment", QString::fromStdString(comment)); + ret.insert("parent", QString::fromStdString(parent->get_name())); + ret.insert("num_cores", num_cores); + ret.insert("mem_size", QString::number(mem_size.in_bytes())); + ret.insert("disk_space", QString::number(disk_space.in_bytes())); + ret.insert("state", static_cast(state)); + ret.insert("metadata", metadata); + + // Extract mount serialization + QJsonArray json_mounts; + for (const auto& mount : mounts) + { + QJsonObject entry; + entry.insert("source_path", QString::fromStdString(mount.second.source_path)); + entry.insert("target_path", QString::fromStdString(mount.first)); + + QJsonArray uid_mappings; + + for (const auto& map : mount.second.uid_mappings) + { + QJsonObject map_entry; + map_entry.insert("host_uid", map.first); + map_entry.insert("instance_uid", map.second); + + uid_mappings.append(map_entry); + } + + entry.insert("uid_mappings", uid_mappings); + + QJsonArray gid_mappings; + + for (const auto& map : mount.second.gid_mappings) + { + QJsonObject map_entry; + map_entry.insert("host_gid", map.first); + map_entry.insert("instance_gid", map.second); + + gid_mappings.append(map_entry); + } + + entry.insert("gid_mappings", gid_mappings); + + entry.insert("mount_type", static_cast(mount.second.mount_type)); + json_mounts.append(entry); + } + + ret.insert("mounts", json_mounts); + return ret; } From bfd7b56bc2a59917d9244c650298acc3a9e8ea63 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 19:09:52 +0100 Subject: [PATCH 060/627] [daemon] Persist Multipass's view VM snapshot --- include/multipass/snapshot.h | 2 +- src/daemon/daemon.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index cefeac19740..985df5959d4 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -52,7 +52,7 @@ class Snapshot : private DisabledCopyMove virtual QJsonObject serialize() const = 0; - virtual void set_name(const std::string&) = 0; + virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; }; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 09e51035945..8fd43cca60b 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2375,9 +2375,13 @@ try SnapshotReply reply; { - auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); - auto snapshot_json = snapshot->serialize(); - // TODO@snapshots persist generic snapshot info + const auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + const auto snapshot_json = snapshot->serialize(); + + const auto instance_dir = mp::utils::base_dir( + fetch_image_for(instance_name, config->factory->fetch_type(), *config->vault).image_path); + auto snapshot_record_file = instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + ".json"); + mp::write_json(snapshot_json, std::move(snapshot_record_file)); reply.set_snapshot(snapshot->get_name()); } From d0d611b7fdaa632ca2588d83d62ebfbaabef4cb5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 19:52:33 +0100 Subject: [PATCH 061/627] [daemon] Refactor getting a snapshot's parent name --- include/multipass/snapshot.h | 1 + src/daemon/daemon.cpp | 3 +-- src/platform/backends/shared/base_snapshot.cpp | 2 +- src/platform/backends/shared/base_snapshot.h | 7 +++++++ src/platform/backends/shared/base_virtual_machine.cpp | 9 +++------ tests/stub_snapshot.h | 5 +++++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 985df5959d4..274bba6477d 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -39,6 +39,7 @@ class Snapshot : private DisabledCopyMove virtual std::string get_name() const = 0; virtual std::string get_comment() const = 0; + virtual std::string get_parent_name() const = 0; virtual std::shared_ptr get_parent() const = 0; virtual int get_num_cores() const noexcept = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 8fd43cca60b..d366bd770ed 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2376,12 +2376,11 @@ try { const auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); - const auto snapshot_json = snapshot->serialize(); const auto instance_dir = mp::utils::base_dir( fetch_image_for(instance_name, config->factory->fetch_type(), *config->vault).image_path); auto snapshot_record_file = instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + ".json"); - mp::write_json(snapshot_json, std::move(snapshot_record_file)); + mp::write_json(snapshot->serialize(), std::move(snapshot_record_file)); reply.set_snapshot(snapshot->get_name()); } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 7063c1cafb4..09575316dcf 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -54,7 +54,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const ret.insert("name", QString::fromStdString(name)); ret.insert("comment", QString::fromStdString(comment)); - ret.insert("parent", QString::fromStdString(parent->get_name())); + ret.insert("parent", QString::fromStdString(get_parent_name())); ret.insert("num_cores", num_cores); ret.insert("mem_size", QString::number(mem_size.in_bytes())); ret.insert("disk_space", QString::number(disk_space.in_bytes())); diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index fda54d086c4..99842afa086 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -43,6 +43,7 @@ class BaseSnapshot : public Snapshot std::string get_name() const override; std::string get_comment() const override; + std::string get_parent_name() const override; std::shared_ptr get_parent() const override; int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; @@ -86,6 +87,12 @@ inline std::string multipass::BaseSnapshot::get_comment() const return comment; } +inline std::string multipass::BaseSnapshot::get_parent_name() const +{ + const std::shared_lock lock{mutex}; + return parent ? parent->get_name() : "--"; +} + inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr { const std::shared_lock lock{mutex}; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 70ecd9f440c..7dbf2bb8c5b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -99,18 +99,15 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& { auto ret = head_snapshot = it->second; auto num_snapshots = snapshots.size(); - auto parent = ret->get_parent(); + auto parent_name = ret->get_parent_name(); + assert(bool(ret->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); + write_lock.unlock(); - assert(bool(parent) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) - { - auto parent_name = parent ? parent->get_name() : "--"; - mpl::log(log_detail_lvl, vm_name, fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, num_snapshots)); - } return ret; } diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index c3ec6cd79dd..024e2ca86c5 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -37,6 +37,11 @@ struct StubSnapshot : public Snapshot return {}; } + std::string get_parent_name() const override + { + return {}; + } + std::shared_ptr get_parent() const noexcept override { return nullptr; From 626936ed944ae919cea6110076d7075dfc6dd90d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 20:08:24 +0100 Subject: [PATCH 062/627] [daemon] Nest snapshot JSON in a snapshot node --- .../backends/shared/base_snapshot.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 09575316dcf..f36d17c1830 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -49,17 +49,17 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme QJsonObject multipass::BaseSnapshot::serialize() const { - QJsonObject ret{}; + QJsonObject ret, snapshot{}; const std::shared_lock lock{mutex}; - ret.insert("name", QString::fromStdString(name)); - ret.insert("comment", QString::fromStdString(comment)); - ret.insert("parent", QString::fromStdString(get_parent_name())); - ret.insert("num_cores", num_cores); - ret.insert("mem_size", QString::number(mem_size.in_bytes())); - ret.insert("disk_space", QString::number(disk_space.in_bytes())); - ret.insert("state", static_cast(state)); - ret.insert("metadata", metadata); + snapshot.insert("name", QString::fromStdString(name)); + snapshot.insert("comment", QString::fromStdString(comment)); + snapshot.insert("parent", QString::fromStdString(get_parent_name())); + snapshot.insert("num_cores", num_cores); + snapshot.insert("mem_size", QString::number(mem_size.in_bytes())); + snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); + snapshot.insert("state", static_cast(state)); + snapshot.insert("metadata", metadata); // Extract mount serialization QJsonArray json_mounts; @@ -99,7 +99,8 @@ QJsonObject multipass::BaseSnapshot::serialize() const json_mounts.append(entry); } - ret.insert("mounts", json_mounts); + snapshot.insert("mounts", json_mounts); + ret.insert("snapshot", snapshot); return ret; } From 69c0809050ba6b23d9d911db5aeb147a66516d85 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 31 Mar 2023 20:13:40 +0100 Subject: [PATCH 063/627] [daemon] Add a TODO --- src/daemon/daemon.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d366bd770ed..39ac5ad5d7b 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2377,9 +2377,11 @@ try { const auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + // TODO better way to find the instance's directory? const auto instance_dir = mp::utils::base_dir( fetch_image_for(instance_name, config->factory->fetch_type(), *config->vault).image_path); auto snapshot_record_file = instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + ".json"); + mp::write_json(snapshot->serialize(), std::move(snapshot_record_file)); reply.set_snapshot(snapshot->get_name()); From 2524f409b7ddb131aa10c9cd09984acd282e1e70 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 14:50:20 +0100 Subject: [PATCH 064/627] [daemon] Use .snapshot.json as snapshot extension And extract it to a constant. --- src/daemon/daemon.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 39ac5ad5d7b..16180b10c24 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -88,6 +88,7 @@ using error_string = std::string; constexpr auto category = "daemon"; constexpr auto instance_db_name = "multipassd-vm-instances.json"; +constexpr auto snapshot_extension = ".snapshot.json"; constexpr auto reboot_cmd = "sudo reboot"; constexpr auto stop_ssh_cmd = "sudo systemctl stop ssh"; const std::string sshfs_error_template = "Error enabling mount support in '{}'" @@ -2380,7 +2381,8 @@ try // TODO better way to find the instance's directory? const auto instance_dir = mp::utils::base_dir( fetch_image_for(instance_name, config->factory->fetch_type(), *config->vault).image_path); - auto snapshot_record_file = instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + ".json"); + auto snapshot_record_file = + instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + snapshot_extension); mp::write_json(snapshot->serialize(), std::move(snapshot_record_file)); From 4fa7791fe70b2a07db3cb3813b3c3305a20e1506 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 4 Apr 2023 10:15:33 +0100 Subject: [PATCH 065/627] [shared] Persist only existing snapshot parents --- src/platform/backends/shared/base_snapshot.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f36d17c1830..d95ab9ed1d3 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -54,13 +54,15 @@ QJsonObject multipass::BaseSnapshot::serialize() const snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); - snapshot.insert("parent", QString::fromStdString(get_parent_name())); snapshot.insert("num_cores", num_cores); snapshot.insert("mem_size", QString::number(mem_size.in_bytes())); snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); snapshot.insert("state", static_cast(state)); snapshot.insert("metadata", metadata); + if (parent) + snapshot.insert("parent", QString::fromStdString(get_parent_name())); + // Extract mount serialization QJsonArray json_mounts; for (const auto& mount : mounts) From 17fd32ac69397705d2ddaf8df729dbe6c36a51ab Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 16:55:04 +0100 Subject: [PATCH 066/627] [util] Wrap `QDir::entryInfoList` in `FileOps` --- include/multipass/file_ops.h | 4 ++++ src/utils/file_ops.cpp | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/include/multipass/file_ops.h b/include/multipass/file_ops.h index d62af532c75..4d1ab20a438 100644 --- a/include/multipass/file_ops.h +++ b/include/multipass/file_ops.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,9 @@ class FileOps : public Singleton // QDir operations virtual bool exists(const QDir& dir) const; virtual bool isReadable(const QDir& dir) const; + virtual QFileInfoList entryInfoList(const QDir& dir, const QStringList& nameFilters, + QDir::Filters filters = QDir::NoFilter, + QDir::SortFlags sort = QDir::NoSort) const; virtual bool mkpath(const QDir& dir, const QString& dirName) const; virtual bool rmdir(QDir& dir, const QString& dirName) const; diff --git a/src/utils/file_ops.cpp b/src/utils/file_ops.cpp index e0b7560f897..b5010777e20 100644 --- a/src/utils/file_ops.cpp +++ b/src/utils/file_ops.cpp @@ -34,6 +34,12 @@ bool mp::FileOps::isReadable(const QDir& dir) const return dir.isReadable(); } +QFileInfoList multipass::FileOps::entryInfoList(const QDir& dir, const QStringList& nameFilters, QDir::Filters filters, + QDir::SortFlags sort) const +{ + return dir.entryInfoList(nameFilters, filters, sort); +} + bool mp::FileOps::mkpath(const QDir& dir, const QString& dirName) const { return dir.mkpath(dirName); From da0336bee7cbe3832c4ebf16d8048dd9bf6db405 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 16:57:45 +0100 Subject: [PATCH 067/627] [daemon] Obtain a list of snapshot files per VM --- src/daemon/daemon.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 16180b10c24..fc620b4ad19 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -448,6 +448,19 @@ auto fetch_image_for(const std::string& name, const mp::FetchType& fetch_type, m return vault.fetch_image(fetch_type, query, stub_prepare, stub_progress, false, std::nullopt); } +QDir instance_directory(const std::string& instance_name, const mp::DaemonConfig& config) +{ // TODO should we establish a more direct way to get to the instance's directory? + return mp::utils::base_dir(fetch_image_for(instance_name, config.factory->fetch_type(), *config.vault).image_path); +} + +void load_snapshots(mp::VirtualMachine& vm, const QDir& dir) +{ + [[maybe_unused]] // TODO@ricab remove + auto snapshot_files = MP_FILEOPS.entryInfoList(dir, {QString{"*%1"}.arg(snapshot_extension)}, + QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); + // TODO@ricab +} + auto try_mem_size(const std::string& val) -> std::optional { try @@ -1254,7 +1267,8 @@ mp::Daemon::Daemon(std::unique_ptr the_config) {}}; auto& instance_record = spec.deleted ? deleted_instances : operative_instances; - instance_record[name] = config->factory->create_virtual_machine(vm_desc, *this); + auto instance = instance_record[name] = config->factory->create_virtual_machine(vm_desc, *this); + load_snapshots(*instance, instance_directory(name, *config)); allocated_mac_addrs = std::move(new_macs); // Add the new macs to the daemon's list only if we got this far @@ -2378,9 +2392,7 @@ try { const auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); - // TODO better way to find the instance's directory? - const auto instance_dir = mp::utils::base_dir( - fetch_image_for(instance_name, config->factory->fetch_type(), *config->vault).image_path); + const auto instance_dir = instance_directory(instance_name, *config); auto snapshot_record_file = instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + snapshot_extension); From 5cf7d459e166677c50cf305d4d4e4201c0f09e0d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 19:11:51 +0100 Subject: [PATCH 068/627] [shared] Add VM method to load a snapshots With placeholder implementation for now. --- include/multipass/virtual_machine.h | 4 +++- src/platform/backends/shared/base_virtual_machine.cpp | 5 +++++ src/platform/backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 4 ++++ 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 48310abd774..a03fb695e4e 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -21,6 +21,8 @@ #include "disabled_copy_move.h" #include "ip_address.h" +#include + #include #include #include @@ -85,9 +87,9 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; - virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; + virtual void load_snapshot(const QJsonObject& json) = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7dbf2bb8c5b..714a4298f2b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -117,4 +117,9 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& throw SnapshotNameTaken{vm_name, name}; } +void BaseVirtualMachine::load_snapshot(const QJsonObject& json) +{ + // TODO@ricab implement +} + } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 4a305f870f2..ac0ada42f32 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -56,6 +56,7 @@ class BaseVirtualMachine : public VirtualMachine SnapshotVista view_snapshots() const noexcept override; std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override; + void load_snapshot(const QJsonObject& json) override; protected: using SnapshotMap = std::unordered_map>; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index b509f038bcf..059206eb3b8 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -69,6 +69,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); + MOCK_METHOD(void, load_snapshot, (const QJsonObject& json), (override)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index c2f32e6e3a0..df5a0792820 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -129,6 +129,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + void load_snapshot(const QJsonObject& json) override + { + } + StubSnapshot snapshot; }; } // namespace test From 3e4fc79eab3ed5dec8abd94e195d8a5a38bd354d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 18:18:17 +0100 Subject: [PATCH 069/627] [daemon] Read snapshot JSONs and delegate to VM --- src/daemon/daemon.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index fc620b4ad19..6c3c3044142 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -455,10 +455,25 @@ QDir instance_directory(const std::string& instance_name, const mp::DaemonConfig void load_snapshots(mp::VirtualMachine& vm, const QDir& dir) { - [[maybe_unused]] // TODO@ricab remove auto snapshot_files = MP_FILEOPS.entryInfoList(dir, {QString{"*%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); - // TODO@ricab + for (const auto& finfo : snapshot_files) + { + QFile file{finfo.filePath()}; + if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) + throw std::runtime_error{fmt::format("Could not open snapshot file for for reading: {}", file.fileName())}; + + QJsonParseError parse_error{}; + const auto& data = MP_FILEOPS.read_all(file); + + if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) + throw std::runtime_error{fmt::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), + parse_error.errorString())}; + else if (json.isEmpty()) + throw std::runtime_error{fmt::format("Empty snapshot JSON: {}", file.fileName())}; + else + vm.load_snapshot(json); + } } auto try_mem_size(const std::string& val) -> std::optional From a0d9c0a4be95dc0c4221a0780e667413cf31896f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 18:37:11 +0100 Subject: [PATCH 070/627] [shared] Delegate loading from VM to Snapshot With a placeholder implementation on the Snapshot, for now. --- src/platform/backends/shared/base_snapshot.cpp | 5 +++++ src/platform/backends/shared/base_snapshot.h | 1 + src/platform/backends/shared/base_virtual_machine.cpp | 9 ++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index d95ab9ed1d3..5b0e4f61239 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -47,6 +47,11 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme { } +mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json) + : BaseSnapshot{"", "", nullptr, 0, {}, {}, {}, {}, {}} // TODO@ricab implement +{ +} + QJsonObject multipass::BaseSnapshot::serialize() const { QJsonObject ret, snapshot{}; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 99842afa086..beac8b798d9 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -41,6 +41,7 @@ class BaseSnapshot : public Snapshot BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs); + BaseSnapshot(const QJsonObject& json); std::string get_name() const override; std::string get_comment() const override; std::string get_parent_name() const override; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 714a4298f2b..c4fd24c8d45 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -119,7 +119,14 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& void BaseVirtualMachine::load_snapshot(const QJsonObject& json) { - // TODO@ricab implement + // TODO@snapshots move to specific VM implementations and make specific snapshot from there + auto snapshot = std::make_shared(json); + const auto& name = snapshot->get_name(); + if (!snapshots.try_emplace(name, snapshot).second) + { + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); + throw SnapshotNameTaken{vm_name, name}; + } } } // namespace multipass From 94670afabdd881428ca3c1dac74abc1fa348c8b6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 4 Apr 2023 12:02:28 +0100 Subject: [PATCH 071/627] [shared] Implement easy items in snapshot loading --- src/platform/backends/shared/base_snapshot.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 5b0e4f61239..23d20a817bc 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -21,6 +21,9 @@ #include #include // TODO@snapshots may be able to drop after extracting JSON utilities +#include + +#include namespace mp = multipass; @@ -48,8 +51,18 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme } mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json) - : BaseSnapshot{"", "", nullptr, 0, {}, {}, {}, {}, {}} // TODO@ricab implement + : BaseSnapshot{json["name"].toString().toStdString(), // name + json["comment"].toString().toStdString(), // comment + nullptr, // parent TODO@ricab + json["num_cores"].toInt(), // num_cores + MemorySize{json["mem_size"].toString().toStdString()}, // mem_size + MemorySize{json["disk_space"].toString().toStdString()}, // disk_space + static_cast(json["state"].toInt()), // state + {} /* TODO@ricab mounts*/, // mounts + json["metadata"].toObject()} // metadata { + if (name.empty() || !num_cores || !mem_size.in_bytes() || !disk_space.in_bytes()) + throw std::runtime_error{fmt::format("Bad snapshot data: {}", QJsonDocument{json}.toJson())}; } QJsonObject multipass::BaseSnapshot::serialize() const From def9e7bbf3435ff0194c3d21680c1b98a29ef941 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Apr 2023 20:50:30 +0100 Subject: [PATCH 072/627] [shared] Implement loading snapshot mounts --- .../backends/shared/base_snapshot.cpp | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 23d20a817bc..ca7c93525ff 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -18,6 +18,7 @@ #include "base_snapshot.h" #include "daemon/vm_specs.h" // TODO@snapshots move this +#include // TODO@snapshots may be able to drop after extracting JSON utilities #include #include // TODO@snapshots may be able to drop after extracting JSON utilities @@ -27,6 +28,43 @@ namespace mp = multipass; +namespace +{ +std::unordered_map load_mounts(const QJsonArray& json) +{ + std::unordered_map mounts; + for (const auto& entry : json) + { + mp::id_mappings uid_mappings; + mp::id_mappings gid_mappings; + + auto target_path = entry.toObject()["target_path"].toString().toStdString(); + auto source_path = entry.toObject()["source_path"].toString().toStdString(); + + for (QJsonValueRef uid_entry : entry.toObject()["uid_mappings"].toArray()) + { + uid_mappings.push_back( + {uid_entry.toObject()["host_uid"].toInt(), uid_entry.toObject()["instance_uid"].toInt()}); + } + + for (QJsonValueRef gid_entry : entry.toObject()["gid_mappings"].toArray()) + { + gid_mappings.push_back( + {gid_entry.toObject()["host_gid"].toInt(), gid_entry.toObject()["instance_gid"].toInt()}); + } + + uid_mappings = mp::unique_id_mappings(uid_mappings); + gid_mappings = mp::unique_id_mappings(gid_mappings); + auto mount_type = mp::VMMount::MountType(entry.toObject()["mount_type"].toInt()); + + mp::VMMount mount{source_path, gid_mappings, uid_mappings, mount_type}; + mounts[target_path] = mount; + } + + return mounts; +} +} // namespace + mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) std::shared_ptr parent, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, @@ -58,7 +96,7 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json) MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space static_cast(json["state"].toInt()), // state - {} /* TODO@ricab mounts*/, // mounts + load_mounts(json["mounts"].toArray()), // mounts json["metadata"].toObject()} // metadata { if (name.empty() || !num_cores || !mem_size.in_bytes() || !disk_space.in_bytes()) From 1f89ca1c515c1d98b5ca5081bd1b7817d73d199e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 4 Apr 2023 10:05:15 +0100 Subject: [PATCH 073/627] [shared] Implement loading snapshot parents --- include/multipass/virtual_machine.h | 1 + src/platform/backends/shared/base_snapshot.cpp | 18 ++++++++++++++++-- src/platform/backends/shared/base_snapshot.h | 6 ++++-- .../backends/shared/base_virtual_machine.cpp | 7 ++++++- .../backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 5 +++++ 7 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index a03fb695e4e..7a5171fdbf0 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -87,6 +87,7 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; + virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; virtual void load_snapshot(const QJsonObject& json) = 0; diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index ca7c93525ff..8e234b7b23b 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -63,6 +63,20 @@ std::unordered_map load_mounts(const QJsonArray& json) return mounts; } + +std::shared_ptr find_parent(const QJsonObject& json, const mp::VirtualMachine& vm) +{ + auto parent_name = json["parent"].toString().toStdString(); + try + { + return parent_name.empty() ? nullptr : vm.get_snapshot(parent_name); + } + catch (std::out_of_range&) + { + throw std::runtime_error{fmt::format("Missing snapshot parent. Snapshot name: {}; parent name: {}", + json["name"].toString(), parent_name)}; + } +} } // namespace mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) @@ -88,10 +102,10 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme { } -mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json) +mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, const VirtualMachine& vm) : BaseSnapshot{json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment - nullptr, // parent TODO@ricab + find_parent(json, vm), // parent json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index beac8b798d9..45e7b7060ae 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -37,19 +37,21 @@ class BaseSnapshot : public Snapshot BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata); - BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs); + BaseSnapshot(const QJsonObject& json, const VirtualMachine& vm); - BaseSnapshot(const QJsonObject& json); std::string get_name() const override; std::string get_comment() const override; std::string get_parent_name() const override; std::shared_ptr get_parent() const override; + int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; MemorySize get_disk_space() const noexcept override; VirtualMachine::State get_state() const noexcept override; + + // Note that these return references - careful not to delete the snapshot while they are in use const std::unordered_map& get_mounts() const noexcept override; const QJsonObject& get_metadata() const noexcept override; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c4fd24c8d45..b113bd6ac92 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -83,6 +83,11 @@ auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotV return ret; } +std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) const +{ + return snapshots.at(name); +} + std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) { @@ -120,7 +125,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& void BaseVirtualMachine::load_snapshot(const QJsonObject& json) { // TODO@snapshots move to specific VM implementations and make specific snapshot from there - auto snapshot = std::make_shared(json); + auto snapshot = std::make_shared(json, *this); const auto& name = snapshot->get_name(); if (!snapshots.try_emplace(name, snapshot).second) { diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index ac0ada42f32..61e6def06e4 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -54,6 +54,7 @@ class BaseVirtualMachine : public VirtualMachine }; SnapshotVista view_snapshots() const noexcept override; + std::shared_ptr get_snapshot(const std::string& name) const override; std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override; void load_snapshot(const QJsonObject& json) override; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 059206eb3b8..11d940e4ccc 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -67,6 +67,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::unique_ptr, make_native_mount_handler, (const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount), (override)); MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); + MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); MOCK_METHOD(void, load_snapshot, (const QJsonObject& json), (override)); diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index df5a0792820..ac4e9e0469a 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -123,6 +123,11 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + std::shared_ptr get_snapshot(const std::string& name) const override + { + return {}; + } + std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override { From 67c2ecb772aa5164b9676c82f3c28c354b797a01 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 4 Apr 2023 12:06:17 +0100 Subject: [PATCH 074/627] [shared] Fix loading from outer snapshot JSON --- src/platform/backends/shared/base_snapshot.cpp | 5 +++++ src/platform/backends/shared/base_snapshot.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 8e234b7b23b..a92e36fc25b 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -103,6 +103,11 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme } mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, const VirtualMachine& vm) + : BaseSnapshot(InnerJsonTag{}, json["snapshot"].toObject(), vm) +{ +} + +mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, const VirtualMachine& vm) : BaseSnapshot{json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment find_parent(json, vm), // parent diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 45e7b7060ae..92b045c03c5 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -61,6 +61,12 @@ class BaseSnapshot : public Snapshot void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; +private: + struct InnerJsonTag + { + }; + BaseSnapshot(InnerJsonTag, const QJsonObject& json, const VirtualMachine& vm); + private: std::string name; std::string comment; From 9e52437dca707a3c49b923ff61863216e7273139 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 19:31:49 +0100 Subject: [PATCH 075/627] [shared] Update the head snapshot when loading --- src/platform/backends/shared/base_virtual_machine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index b113bd6ac92..c0898f928ad 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -127,11 +127,15 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) // TODO@snapshots move to specific VM implementations and make specific snapshot from there auto snapshot = std::make_shared(json, *this); const auto& name = snapshot->get_name(); - if (!snapshots.try_emplace(name, snapshot).second) + auto [it, success] = snapshots.try_emplace(name, snapshot); + + if (!success) { mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); throw SnapshotNameTaken{vm_name, name}; } + + head_snapshot = it->second; // TODO@snapshots persist/load this separately } } // namespace multipass From 6a462753af14d8996c1e717c3140e3465b042f88 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 4 Apr 2023 20:29:02 +0100 Subject: [PATCH 076/627] [shared] Privatize BaseSnapshot constructor --- src/platform/backends/shared/base_snapshot.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 92b045c03c5..f98abeb5908 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -34,9 +34,6 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, - int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, - std::unordered_map mounts, QJsonObject metadata); BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs); BaseSnapshot(const QJsonObject& json, const VirtualMachine& vm); @@ -66,6 +63,9 @@ class BaseSnapshot : public Snapshot { }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, const VirtualMachine& vm); + BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, + int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, + std::unordered_map mounts, QJsonObject metadata); private: std::string name; From 1a954da6b8488fb6a909ee7e8bdfa1456e187544 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 11:35:25 +0100 Subject: [PATCH 077/627] [shared] Fix creating snapshots with repeated name Don't create Snapshot objects whose name is taken, not even to be immediately destroyed. --- src/platform/backends/shared/base_virtual_machine.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c0898f928ad..7c81f5f6c57 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -92,17 +92,16 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& const std::string& comment) { // TODO@snapshots generate name - // TODO@snapshots generate implementation-specific snapshot instead - { std::unique_lock write_lock{snapshot_mutex}; - const auto [it, success] = - snapshots.try_emplace(name, std::make_shared(name, comment, head_snapshot, specs)); + const auto [it, success] = snapshots.try_emplace(name, nullptr); if (success) { - auto ret = head_snapshot = it->second; + // TODO@snapshots generate implementation-specific snapshot instead + auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); + auto num_snapshots = snapshots.size(); auto parent_name = ret->get_parent_name(); assert(bool(ret->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); From 523e5d2dfad7936b3221e952353b0c9091043dfd Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 11:56:05 +0100 Subject: [PATCH 078/627] [shared] Rollback map if snapshot creation fails --- src/platform/backends/shared/CMakeLists.txt | 1 + src/platform/backends/shared/base_virtual_machine.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/platform/backends/shared/CMakeLists.txt b/src/platform/backends/shared/CMakeLists.txt index 5e4d57778c4..a9474b3fc0a 100644 --- a/src/platform/backends/shared/CMakeLists.txt +++ b/src/platform/backends/shared/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(shared STATIC target_link_libraries(shared iso process + scope_guard utils Qt6::Core) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7c81f5f6c57..be48a28cebd 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -23,6 +23,8 @@ #include #include +#include + namespace mp = multipass; namespace mpl = multipass::logging; @@ -99,8 +101,11 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& if (success) { + auto rollback_on_failure = sg::make_scope_guard([this, it = it]() noexcept { snapshots.erase(it); }); + // TODO@snapshots generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); + rollback_on_failure.dismiss(); auto num_snapshots = snapshots.size(); auto parent_name = ret->get_parent_name(); From 7d07069261996aa7b052f8d92114fb7220d95243 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 12:37:12 +0100 Subject: [PATCH 079/627] [shared] Add VM method to persist latest snapshot With placeholder implementation for now. --- src/platform/backends/shared/base_virtual_machine.cpp | 6 ++++++ src/platform/backends/shared/base_virtual_machine.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index be48a28cebd..f7d0bac8267 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -105,6 +105,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& // TODO@snapshots generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); + + persist_head_snapshot(); rollback_on_failure.dismiss(); auto num_snapshots = snapshots.size(); @@ -142,4 +144,8 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) head_snapshot = it->second; // TODO@snapshots persist/load this separately } +void BaseVirtualMachine::persist_head_snapshot() const // TODO@snapshots implement +{ +} + } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 61e6def06e4..d8030a3d53e 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -59,6 +59,9 @@ class BaseVirtualMachine : public VirtualMachine const std::string& comment) override; void load_snapshot(const QJsonObject& json) override; +protected: + void persist_head_snapshot() const; + protected: using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; From 529673bae1561355e0b5181ffb8834b9ad23db29 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 12:50:10 +0100 Subject: [PATCH 080/627] [shared] Attempt to rollback unpersisted snapshot --- include/multipass/snapshot.h | 2 ++ src/platform/backends/shared/base_snapshot.h | 8 ++++++++ .../backends/shared/base_virtual_machine.cpp | 13 +++++++++++-- tests/stub_snapshot.h | 4 ++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 274bba6477d..9872e83339f 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -56,6 +56,8 @@ class Snapshot : private DisabledCopyMove virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; + + virtual void delet() = 0; // not using the destructor, we want snapshots to stick around when daemon quits }; } // namespace multipass diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index f98abeb5908..7f842825650 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -58,6 +58,8 @@ class BaseSnapshot : public Snapshot void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; + void delet() override; + private: struct InnerJsonTag { @@ -156,4 +158,10 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr parent = std::move(p); } +inline void multipass::BaseSnapshot::delet() +{ + // TODO@snapshots this is meant to be implemented by descendants + // placeholder implementation to avoid making this class abstract for now +} + #endif // MULTIPASS_BASE_SNAPSHOT_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f7d0bac8267..e9dfea7134e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -101,9 +102,17 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& if (success) { - auto rollback_on_failure = sg::make_scope_guard([this, it = it]() noexcept { snapshots.erase(it); }); + auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() noexcept { + if (it->second) // snapshot was created + { + head_snapshot = std::move(old_head); + mp::top_catch_all(vm_name, [it] { it->second->delet(); }); + } - // TODO@snapshots generate implementation-specific snapshot instead + snapshots.erase(it); + }); + + // TODO@snapshots - generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); persist_head_snapshot(); diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 024e2ca86c5..25a1b25ce3e 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -94,6 +94,10 @@ struct StubSnapshot : public Snapshot { } + void delet() override + { + } + std::unordered_map mounts; QJsonObject metadata; }; From 0611ade8eafa33eaefbc614de9bc5be6a758d945 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 13:17:44 +0100 Subject: [PATCH 081/627] [shared] Take a directory to persist new snapshots Add a dir parameter to VM::take_snapshot, to use to persist new snapshot data to. --- include/multipass/virtual_machine.h | 5 +++-- src/daemon/daemon.cpp | 6 ++++-- .../backends/shared/base_virtual_machine.cpp | 12 ++++++++---- src/platform/backends/shared/base_virtual_machine.h | 7 +++++-- tests/mock_virtual_machine.h | 2 +- tests/stub_virtual_machine.h | 2 +- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 7a5171fdbf0..4509fc34331 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -21,6 +21,7 @@ #include "disabled_copy_move.h" #include "ip_address.h" +#include #include #include @@ -88,8 +89,8 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; - virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) = 0; + virtual std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, + const std::string& name, const std::string& comment) = 0; virtual void load_snapshot(const QJsonObject& json) = 0; VirtualMachine::State state; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 6c3c3044142..a854e8e3129 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -453,7 +453,7 @@ QDir instance_directory(const std::string& instance_name, const mp::DaemonConfig return mp::utils::base_dir(fetch_image_for(instance_name, config.factory->fetch_type(), *config.vault).image_path); } -void load_snapshots(mp::VirtualMachine& vm, const QDir& dir) +void load_snapshots(mp::VirtualMachine& vm, const QDir& dir) // TODO@ricab move to VM { auto snapshot_files = MP_FILEOPS.entryInfoList(dir, {QString{"*%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); @@ -2405,8 +2405,10 @@ try SnapshotReply reply; { - const auto snapshot = vm_ptr->take_snapshot(spec_it->second, request->snapshot(), request->comment()); + const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, + request->snapshot(), request->comment()); + // TODO@ricab remove const auto instance_dir = instance_directory(instance_name, *config); auto snapshot_record_file = instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + snapshot_extension); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e9dfea7134e..a58eb256319 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -91,8 +91,8 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri return snapshots.at(name); } -std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, - const std::string& comment) +std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& dir, const VMSpecs& specs, + const std::string& name, const std::string& comment) { // TODO@snapshots generate name { @@ -115,7 +115,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& // TODO@snapshots - generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); - persist_head_snapshot(); + persist_head_snapshot(dir); rollback_on_failure.dismiss(); auto num_snapshots = snapshots.size(); @@ -153,8 +153,12 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) head_snapshot = it->second; // TODO@snapshots persist/load this separately } -void BaseVirtualMachine::persist_head_snapshot() const // TODO@snapshots implement +void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const // TODO@snapshots implement { + // auto snapshot_record_file = + // instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + snapshot_extension); + // + // mp::write_json(snapshot->serialize(), std::move(snapshot_record_file)); } } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index d8030a3d53e..a3689bbd059 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -55,12 +55,15 @@ class BaseVirtualMachine : public VirtualMachine SnapshotVista view_snapshots() const noexcept override; std::shared_ptr get_snapshot(const std::string& name) const override; - std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + + // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from + // derived classes is a big refactor + std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; void load_snapshot(const QJsonObject& json) override; protected: - void persist_head_snapshot() const; + void persist_head_snapshot(const QDir& dir) const; protected: using SnapshotMap = std::unordered_map>; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 11d940e4ccc..a89ace3528c 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -69,7 +69,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, - (const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); + (const QDir&, const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); MOCK_METHOD(void, load_snapshot, (const QJsonObject& json), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index ac4e9e0469a..898c613b28b 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -128,7 +128,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override { return {}; From 9952b95cbbfaf6d24e20becfacbd4c9063417821 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 15:15:51 +0100 Subject: [PATCH 082/627] [shared] Reduced visibility of base VM members Make base VM fields related to snapshots private, as well as a helper method. --- src/platform/backends/shared/base_virtual_machine.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a3689bbd059..792f5079b43 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -62,10 +62,10 @@ class BaseVirtualMachine : public VirtualMachine const std::string& comment) override; void load_snapshot(const QJsonObject& json) override; -protected: +private: void persist_head_snapshot(const QDir& dir) const; -protected: +private: using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; std::shared_ptr head_snapshot = nullptr; From 2fcd0cc024a338479b699ad7700c4d6d7e3e2265 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 17:39:14 +0100 Subject: [PATCH 083/627] [shared] Move snapshot persistence to the VM --- src/daemon/daemon.cpp | 9 +-------- .../backends/shared/base_virtual_machine.cpp | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a854e8e3129..a9bca8a403e 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -88,7 +88,7 @@ using error_string = std::string; constexpr auto category = "daemon"; constexpr auto instance_db_name = "multipassd-vm-instances.json"; -constexpr auto snapshot_extension = ".snapshot.json"; +constexpr auto snapshot_extension = ".snapshot.json"; // TODO@ricab remove once this is fully up to the VM constexpr auto reboot_cmd = "sudo reboot"; constexpr auto stop_ssh_cmd = "sudo systemctl stop ssh"; const std::string sshfs_error_template = "Error enabling mount support in '{}'" @@ -2408,13 +2408,6 @@ try const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, request->snapshot(), request->comment()); - // TODO@ricab remove - const auto instance_dir = instance_directory(instance_name, *config); - auto snapshot_record_file = - instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + snapshot_extension); - - mp::write_json(snapshot->serialize(), std::move(snapshot_record_file)); - reply.set_snapshot(snapshot->get_name()); } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index a58eb256319..7b1150069d9 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,11 @@ namespace mp = multipass; namespace mpl = multipass::logging; +namespace +{ +constexpr auto snapshot_extension = ".snapshot.json"; +} + namespace multipass { @@ -153,12 +159,13 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) head_snapshot = it->second; // TODO@snapshots persist/load this separately } -void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const // TODO@snapshots implement +void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const { - // auto snapshot_record_file = - // instance_dir.filePath(QString::fromStdString(snapshot->get_name()) + snapshot_extension); - // - // mp::write_json(snapshot->serialize(), std::move(snapshot_record_file)); + // TODO@snapshots add index to file name + auto snapshot_record_file = dir.filePath(QString::fromStdString(head_snapshot->get_name()) + snapshot_extension); + mp::write_json(head_snapshot->serialize(), std::move(snapshot_record_file)); + + // TODO@snapshots persist snap total and head snapshot } } // namespace multipass From e3839cf150a96e50d1c16d5b83bda34ae820fb56 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 17:59:19 +0100 Subject: [PATCH 084/627] [shared] Don't represent null parent name as "--" That is a presentation-level decision, so it should be up to the client. --- src/platform/backends/shared/base_snapshot.cpp | 4 +--- src/platform/backends/shared/base_snapshot.h | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index a92e36fc25b..5c34a3f3d25 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -134,9 +134,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); snapshot.insert("state", static_cast(state)); snapshot.insert("metadata", metadata); - - if (parent) - snapshot.insert("parent", QString::fromStdString(get_parent_name())); + snapshot.insert("parent", QString::fromStdString(get_parent_name())); // Extract mount serialization QJsonArray json_mounts; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 7f842825650..308c457e79a 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -101,7 +101,7 @@ inline std::string multipass::BaseSnapshot::get_comment() const inline std::string multipass::BaseSnapshot::get_parent_name() const { const std::shared_lock lock{mutex}; - return parent ? parent->get_name() : "--"; + return parent ? parent->get_name() : ""; } inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7b1150069d9..83b36920848 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -132,8 +132,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) mpl::log(log_detail_lvl, vm_name, - fmt::format("New snapshot: {}; Descendant of: {}; Total snapshots: {}", name, parent_name, - num_snapshots)); + fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", name, + parent_name, num_snapshots)); return ret; } From f9ae283d1ad679be566314df3ce5a2723d2f3255 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 19:21:11 +0100 Subject: [PATCH 085/627] [shared] Extract logging when taking snapshots --- .../backends/shared/base_virtual_machine.cpp | 29 ++++++++++++------- .../backends/shared/base_virtual_machine.h | 2 ++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 83b36920848..c38e71de77a 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -124,16 +124,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di persist_head_snapshot(dir); rollback_on_failure.dismiss(); - auto num_snapshots = snapshots.size(); - auto parent_name = ret->get_parent_name(); - assert(bool(ret->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); - - write_lock.unlock(); - - if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) - mpl::log(log_detail_lvl, vm_name, - fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", name, - parent_name, num_snapshots)); + log_latest_snapshot(std::move(write_lock)); return ret; } @@ -143,6 +134,24 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di throw SnapshotNameTaken{vm_name, name}; } +template +void BaseVirtualMachine::log_latest_snapshot(LockT lock) const +{ + auto num_snapshots = snapshots.size(); + auto parent_name = head_snapshot->get_parent_name(); + assert(bool(head_snapshot->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); + + if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) + { + auto name = head_snapshot->get_name(); + lock.unlock(); // unlock earlier + + mpl::log(log_detail_lvl, vm_name, + fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", name, parent_name, + num_snapshots)); + } +} + void BaseVirtualMachine::load_snapshot(const QJsonObject& json) { // TODO@snapshots move to specific VM implementations and make specific snapshot from there diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 792f5079b43..2efdb92a377 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -63,6 +63,8 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot(const QJsonObject& json) override; private: + template + void log_latest_snapshot(LockT lock) const; void persist_head_snapshot(const QDir& dir) const; private: From 756da124c7d655cb98d315c57831135465b04326 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 22:23:08 +0100 Subject: [PATCH 086/627] [tests] Drop unused parameter names in test VMs --- tests/mock_virtual_machine.h | 12 ++++++------ tests/stub_virtual_machine.h | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index a89ace3528c..59f0d45ca9c 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -61,16 +61,16 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, ensure_vm_is_running, (), (override)); MOCK_METHOD(void, wait_until_ssh_up, (std::chrono::milliseconds, const SSHKeyProvider&), (override)); MOCK_METHOD(void, update_state, (), (override)); - MOCK_METHOD(void, update_cpus, (int num_cores), (override)); - MOCK_METHOD(void, resize_memory, (const MemorySize& new_size), (override)); - MOCK_METHOD(void, resize_disk, (const MemorySize& new_size), (override)); + MOCK_METHOD(void, update_cpus, (int), (override)); + MOCK_METHOD(void, resize_memory, (const MemorySize&), (override)); + MOCK_METHOD(void, resize_disk, (const MemorySize&), (override)); MOCK_METHOD(std::unique_ptr, make_native_mount_handler, - (const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount), (override)); + (const SSHKeyProvider*, const std::string&, const VMMount&), (override)); MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, - (const QDir&, const VMSpecs& specs, const std::string& name, const std::string& comment), (override)); - MOCK_METHOD(void, load_snapshot, (const QJsonObject& json), (override)); + (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(void, load_snapshot, (const QJsonObject&), (override)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 898c613b28b..c5082d1ebca 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -112,8 +112,8 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } - std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, - const std::string& target, const VMMount& mount) override + std::unique_ptr make_native_mount_handler(const SSHKeyProvider*, const std::string&, + const VMMount&) override { return std::make_unique(); } @@ -123,18 +123,18 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - std::shared_ptr get_snapshot(const std::string& name) const override + std::shared_ptr get_snapshot(const std::string&) const override { return {}; } - std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, - const std::string& comment) override + std::shared_ptr take_snapshot(const QDir&, const VMSpecs&, const std::string&, + const std::string&) override { return {}; } - void load_snapshot(const QJsonObject& json) override + void load_snapshot(const QJsonObject&) override { } From 04a2f2ccceb453c2b0dc07bb85580f98b999d43e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 23:06:17 +0100 Subject: [PATCH 087/627] [shared] Move snapshot loading to VM --- include/multipass/virtual_machine.h | 3 +- src/daemon/daemon.cpp | 26 +---------------- .../backends/shared/base_virtual_machine.cpp | 28 +++++++++++++++++++ .../backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 4 +++ 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 4509fc34331..a80eeb42ce5 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -91,7 +91,8 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; - virtual void load_snapshot(const QJsonObject& json) = 0; + virtual void load_snapshot(const QJsonObject& json) = 0; // TODO@snapshots remove + virtual void load_snapshots(const QDir& snapshot_dir) = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a9bca8a403e..cfe32558f50 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -88,7 +88,6 @@ using error_string = std::string; constexpr auto category = "daemon"; constexpr auto instance_db_name = "multipassd-vm-instances.json"; -constexpr auto snapshot_extension = ".snapshot.json"; // TODO@ricab remove once this is fully up to the VM constexpr auto reboot_cmd = "sudo reboot"; constexpr auto stop_ssh_cmd = "sudo systemctl stop ssh"; const std::string sshfs_error_template = "Error enabling mount support in '{}'" @@ -453,29 +452,6 @@ QDir instance_directory(const std::string& instance_name, const mp::DaemonConfig return mp::utils::base_dir(fetch_image_for(instance_name, config.factory->fetch_type(), *config.vault).image_path); } -void load_snapshots(mp::VirtualMachine& vm, const QDir& dir) // TODO@ricab move to VM -{ - auto snapshot_files = MP_FILEOPS.entryInfoList(dir, {QString{"*%1"}.arg(snapshot_extension)}, - QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); - for (const auto& finfo : snapshot_files) - { - QFile file{finfo.filePath()}; - if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) - throw std::runtime_error{fmt::format("Could not open snapshot file for for reading: {}", file.fileName())}; - - QJsonParseError parse_error{}; - const auto& data = MP_FILEOPS.read_all(file); - - if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) - throw std::runtime_error{fmt::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), - parse_error.errorString())}; - else if (json.isEmpty()) - throw std::runtime_error{fmt::format("Empty snapshot JSON: {}", file.fileName())}; - else - vm.load_snapshot(json); - } -} - auto try_mem_size(const std::string& val) -> std::optional { try @@ -1283,7 +1259,7 @@ mp::Daemon::Daemon(std::unique_ptr the_config) auto& instance_record = spec.deleted ? deleted_instances : operative_instances; auto instance = instance_record[name] = config->factory->create_virtual_machine(vm_desc, *this); - load_snapshots(*instance, instance_directory(name, *config)); + instance->load_snapshots(instance_directory(name, *config)); allocated_mac_addrs = std::move(new_macs); // Add the new macs to the daemon's list only if we got this far diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c38e71de77a..62b94705ce7 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,10 @@ #include +#include +#include +#include + namespace mp = multipass; namespace mpl = multipass::logging; @@ -168,6 +173,29 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) head_snapshot = it->second; // TODO@snapshots persist/load this separately } +void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) +{ + auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*%1"}.arg(snapshot_extension)}, + QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); + for (const auto& finfo : snapshot_files) + { + QFile file{finfo.filePath()}; + if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) + throw std::runtime_error{fmt::format("Could not open snapshot file for for reading: {}", file.fileName())}; + + QJsonParseError parse_error{}; + const auto& data = MP_FILEOPS.read_all(file); + + if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) + throw std::runtime_error{fmt::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), + parse_error.errorString())}; + else if (json.isEmpty()) + throw std::runtime_error{fmt::format("Empty snapshot JSON: {}", file.fileName())}; + else + load_snapshot(json); + } +} + void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const { // TODO@snapshots add index to file name diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 2efdb92a377..33dd5fbea6d 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -61,6 +61,7 @@ class BaseVirtualMachine : public VirtualMachine std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; void load_snapshot(const QJsonObject& json) override; + void load_snapshots(const QDir& snapshot_dir) override; private: template diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 59f0d45ca9c..898d867e0a0 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -71,6 +71,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); MOCK_METHOD(void, load_snapshot, (const QJsonObject&), (override)); + MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index c5082d1ebca..7685e72056e 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -138,6 +138,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } + void load_snapshots(const QDir&) override + { + } + StubSnapshot snapshot; }; } // namespace test From 90a677b9c04e11ea6f45a673cb3f5cf4136c9e7e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 22:41:55 +0100 Subject: [PATCH 088/627] [shared] Remove single load from VM interface Turn single-snapshot loading into a private function of the BaseVM, removing it from the VM interface. --- include/multipass/virtual_machine.h | 1 - .../backends/shared/base_virtual_machine.cpp | 32 +++++++++---------- .../backends/shared/base_virtual_machine.h | 2 +- tests/mock_virtual_machine.h | 1 - tests/stub_virtual_machine.h | 4 --- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index a80eeb42ce5..4cf03e66cf5 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -91,7 +91,6 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; - virtual void load_snapshot(const QJsonObject& json) = 0; // TODO@snapshots remove virtual void load_snapshots(const QDir& snapshot_dir) = 0; VirtualMachine::State state; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 62b94705ce7..cc6174f15b7 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -157,22 +157,6 @@ void BaseVirtualMachine::log_latest_snapshot(LockT lock) const } } -void BaseVirtualMachine::load_snapshot(const QJsonObject& json) -{ - // TODO@snapshots move to specific VM implementations and make specific snapshot from there - auto snapshot = std::make_shared(json, *this); - const auto& name = snapshot->get_name(); - auto [it, success] = snapshots.try_emplace(name, snapshot); - - if (!success) - { - mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); - throw SnapshotNameTaken{vm_name, name}; - } - - head_snapshot = it->second; // TODO@snapshots persist/load this separately -} - void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) { auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*%1"}.arg(snapshot_extension)}, @@ -196,6 +180,22 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) } } +void BaseVirtualMachine::load_snapshot(const QJsonObject& json) +{ + // TODO@snapshots move to specific VM implementations and make specific snapshot from there + auto snapshot = std::make_shared(json, *this); + const auto& name = snapshot->get_name(); + auto [it, success] = snapshots.try_emplace(name, snapshot); + + if (!success) + { + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); + throw SnapshotNameTaken{vm_name, name}; + } + + head_snapshot = it->second; // TODO@snapshots persist/load this separately +} + void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const { // TODO@snapshots add index to file name diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 33dd5fbea6d..ba0fd1aaf56 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -60,12 +60,12 @@ class BaseVirtualMachine : public VirtualMachine // derived classes is a big refactor std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; - void load_snapshot(const QJsonObject& json) override; void load_snapshots(const QDir& snapshot_dir) override; private: template void log_latest_snapshot(LockT lock) const; + void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& dir) const; private: diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 898d867e0a0..38d3504fa02 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -70,7 +70,6 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); - MOCK_METHOD(void, load_snapshot, (const QJsonObject&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 7685e72056e..9f9af45f683 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -134,10 +134,6 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - void load_snapshot(const QJsonObject&) override - { - } - void load_snapshots(const QDir&) override { } From a5d470a8c26f3511a0c06fb2d6acbc1417adc590 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 22:46:58 +0100 Subject: [PATCH 089/627] [shared] Slight method reordering in base VM --- .../backends/shared/base_virtual_machine.cpp | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index cc6174f15b7..a083d4dcb2f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -139,24 +139,6 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di throw SnapshotNameTaken{vm_name, name}; } -template -void BaseVirtualMachine::log_latest_snapshot(LockT lock) const -{ - auto num_snapshots = snapshots.size(); - auto parent_name = head_snapshot->get_parent_name(); - assert(bool(head_snapshot->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); - - if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) - { - auto name = head_snapshot->get_name(); - lock.unlock(); // unlock earlier - - mpl::log(log_detail_lvl, vm_name, - fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", name, parent_name, - num_snapshots)); - } -} - void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) { auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*%1"}.arg(snapshot_extension)}, @@ -180,6 +162,24 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) } } +template +void BaseVirtualMachine::log_latest_snapshot(LockT lock) const +{ + auto num_snapshots = snapshots.size(); + auto parent_name = head_snapshot->get_parent_name(); + assert(bool(head_snapshot->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); + + if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) + { + auto name = head_snapshot->get_name(); + lock.unlock(); // unlock earlier + + mpl::log(log_detail_lvl, vm_name, + fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", name, parent_name, + num_snapshots)); + } +} + void BaseVirtualMachine::load_snapshot(const QJsonObject& json) { // TODO@snapshots move to specific VM implementations and make specific snapshot from there From df6ad9abd8b60f16f0924bd399e6eb0ffd834359 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 22:47:56 +0100 Subject: [PATCH 090/627] [shared] Lock snapshot loading --- src/platform/backends/shared/base_virtual_machine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index a083d4dcb2f..58acc8a399a 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -141,6 +141,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) { + std::unique_lock write_lock{snapshot_mutex}; + auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) From e6662b1cfda00b38a07806067544d140bcdd449d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 22:59:42 +0100 Subject: [PATCH 091/627] [shared] Track the total number of snapshots --- src/platform/backends/shared/base_virtual_machine.cpp | 4 +++- src/platform/backends/shared/base_virtual_machine.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 58acc8a399a..5c1bde1b0c0 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -110,7 +110,6 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di std::unique_lock write_lock{snapshot_mutex}; const auto [it, success] = snapshots.try_emplace(name, nullptr); - if (success) { auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() noexcept { @@ -128,6 +127,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di persist_head_snapshot(dir); rollback_on_failure.dismiss(); + ++snapshot_count; log_latest_snapshot(std::move(write_lock)); @@ -169,6 +169,8 @@ void BaseVirtualMachine::log_latest_snapshot(LockT lock) const { auto num_snapshots = snapshots.size(); auto parent_name = head_snapshot->get_parent_name(); + + assert(num_snapshots <= snapshot_count && "can't have more snapshots than were ever taken"); assert(bool(head_snapshot->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index ba0fd1aaf56..683d095f83c 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -72,6 +72,7 @@ class BaseVirtualMachine : public VirtualMachine using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; std::shared_ptr head_snapshot = nullptr; + size_t snapshot_count = 0; // tracks the number of snapshots ever taken (regardless or deletes) mutable std::shared_mutex snapshot_mutex; }; From fa5a8baa11342750ba8f3b93853156dbfe297384 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 23:02:14 +0100 Subject: [PATCH 092/627] [shared] Use snapshot count as index in filename Use the total number of snapshots ever taken as an index that is prepended to each snapshot's filename. --- src/platform/backends/shared/base_virtual_machine.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 5c1bde1b0c0..54c77ab2483 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -37,7 +37,7 @@ namespace mpl = multipass::logging; namespace { -constexpr auto snapshot_extension = ".snapshot.json"; +constexpr auto snapshot_extension = "snapshot.json"; } namespace multipass @@ -143,7 +143,7 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) { std::unique_lock write_lock{snapshot_mutex}; - auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*%1"}.arg(snapshot_extension)}, + auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*.%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) { @@ -202,8 +202,10 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const { - // TODO@snapshots add index to file name - auto snapshot_record_file = dir.filePath(QString::fromStdString(head_snapshot->get_name()) + snapshot_extension); + const auto filename = QString{"%1-%2.%3"}.arg( + QString::number(snapshot_count), QString::fromStdString(head_snapshot->get_name()), snapshot_extension); + + auto snapshot_record_file = snapshot_dir.filePath(filename); mp::write_json(head_snapshot->serialize(), std::move(snapshot_record_file)); // TODO@snapshots persist snap total and head snapshot From 0be247e5884997a1508ef35e56972c9a66ae7025 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 23:03:58 +0100 Subject: [PATCH 093/627] [shared] Improve param name --- include/multipass/virtual_machine.h | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 6 +++--- src/platform/backends/shared/base_virtual_machine.h | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 4cf03e66cf5..f5e6c136698 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -89,7 +89,7 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; - virtual std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, + virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 54c77ab2483..921b25439c3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -102,7 +102,7 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri return snapshots.at(name); } -std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& dir, const VMSpecs& specs, +std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) { // TODO@snapshots generate name @@ -125,7 +125,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& di // TODO@snapshots - generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); - persist_head_snapshot(dir); + persist_head_snapshot(snapshot_dir); rollback_on_failure.dismiss(); ++snapshot_count; @@ -200,7 +200,7 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) head_snapshot = it->second; // TODO@snapshots persist/load this separately } -void BaseVirtualMachine::persist_head_snapshot(const QDir& dir) const +void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const { const auto filename = QString{"%1-%2.%3"}.arg( QString::number(snapshot_count), QString::fromStdString(head_snapshot->get_name()), snapshot_extension); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 683d095f83c..cab0975ab74 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -58,15 +58,15 @@ class BaseVirtualMachine : public VirtualMachine // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor - std::shared_ptr take_snapshot(const QDir& dir, const VMSpecs& specs, const std::string& name, - const std::string& comment) override; + std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, + const std::string& name, const std::string& comment) override; void load_snapshots(const QDir& snapshot_dir) override; private: template void log_latest_snapshot(LockT lock) const; void load_snapshot(const QJsonObject& json); - void persist_head_snapshot(const QDir& dir) const; + void persist_head_snapshot(const QDir& snapshot_dir) const; private: using SnapshotMap = std::unordered_map>; From 52e7fd803e24e5d66b8ba9f8ab244ec3b8824bcc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 5 Apr 2023 23:58:15 +0100 Subject: [PATCH 094/627] [shared] Persist VM-level snapshot information --- .../backends/shared/base_virtual_machine.cpp | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 921b25439c3..7827fe582ad 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -38,6 +39,10 @@ namespace mpl = multipass::logging; namespace { constexpr auto snapshot_extension = "snapshot.json"; +constexpr auto head_filename = "snapshot-head"; +constexpr auto count_filename = "snapshot-count"; +constexpr auto index_digits = 4; // these two go together +constexpr auto max_snapshots = 1000ull; // replace suffix with uz for size_t in C++23 } namespace multipass @@ -108,6 +113,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn // TODO@snapshots generate name { std::unique_lock write_lock{snapshot_mutex}; + if (snapshot_count > max_snapshots) + throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded", max_snapshots)}; const auto [it, success] = snapshots.try_emplace(name, nullptr); if (success) @@ -202,13 +209,21 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const { - const auto filename = QString{"%1-%2.%3"}.arg( - QString::number(snapshot_count), QString::fromStdString(head_snapshot->get_name()), snapshot_extension); + assert(head_snapshot); - auto snapshot_record_file = snapshot_dir.filePath(filename); - mp::write_json(head_snapshot->serialize(), std::move(snapshot_record_file)); + const auto snapshot_filename = QString{"%1-%2.%3"} + .arg(snapshot_count, index_digits, 10, QLatin1Char('0')) + .arg(QString::fromStdString(head_snapshot->get_name()), snapshot_extension); - // TODO@snapshots persist snap total and head snapshot + mp::write_json(head_snapshot->serialize(), snapshot_dir.filePath(snapshot_filename)); + + // TODO@snapshots rollback snapshot file if writing generic info below fails + + auto overwrite = true; + MP_UTILS.make_file_with_content(snapshot_dir.filePath(head_filename).toStdString(), head_snapshot->get_name(), + overwrite); + MP_UTILS.make_file_with_content(snapshot_dir.filePath(count_filename).toStdString(), std::to_string(snapshot_count), + overwrite); } } // namespace multipass From 7f1005270f033ba8363e690ae4d869028e52850b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 03:17:30 +0100 Subject: [PATCH 095/627] [shared] Load VM-level snapshot information --- .../exceptions/file_not_found_exception.h | 37 +++++++++++++++++++ .../backends/shared/base_virtual_machine.cpp | 15 +++++++- src/utils/utils.cpp | 5 ++- 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 include/multipass/exceptions/file_not_found_exception.h diff --git a/include/multipass/exceptions/file_not_found_exception.h b/include/multipass/exceptions/file_not_found_exception.h new file mode 100644 index 00000000000..d4eff3f8076 --- /dev/null +++ b/include/multipass/exceptions/file_not_found_exception.h @@ -0,0 +1,37 @@ +/* + * 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_FILE_NOT_FOUND_EXCEPTION_H +#define MULTIPASS_FILE_NOT_FOUND_EXCEPTION_H + +#include +#include +#include + +namespace multipass +{ +class FileOpenFailedException : public std::runtime_error +{ +public: + explicit FileOpenFailedException(const std::string& name) + : std::runtime_error(fmt::format("failed to open file '{}': {}({})", name, strerror(errno), errno)) + { + } +}; +} // namespace multipass + +#endif // MULTIPASS_FILE_NOT_FOUND_EXCEPTION_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7827fe582ad..6e028d6bfd4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -18,6 +18,7 @@ #include "base_virtual_machine.h" #include "base_snapshot.h" // TODO@snapshots may be able to remove this +#include #include #include #include @@ -35,6 +36,7 @@ namespace mp = multipass; namespace mpl = multipass::logging; +namespace mpu = multipass::utils; namespace { @@ -169,6 +171,17 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) else load_snapshot(json); } + + try + { + snapshot_count = std::stoull(mpu::contents_of(snapshot_dir.filePath(count_filename))); + head_snapshot = snapshots.at(mpu::contents_of(snapshot_dir.filePath(head_filename))); + } + catch (FileOpenFailedException&) + { + if (!snapshots.empty()) + throw; + } } template @@ -203,8 +216,6 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); throw SnapshotNameTaken{vm_name, name}; } - - head_snapshot = it->second; // TODO@snapshots persist/load this separately } void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 7668009d322..b91b45d1263 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -472,12 +473,12 @@ QString mp::utils::make_uuid(const std::optional& seed) return uuid.toString(QUuid::WithoutBraces); } -std::string mp::utils::contents_of(const multipass::Path& file_path) +std::string mp::utils::contents_of(const multipass::Path& file_path) // TODO this should protect against long contents { const std::string name{file_path.toStdString()}; std::ifstream in(name, std::ios::in | std::ios::binary); if (!in) - throw std::runtime_error(fmt::format("failed to open file '{}': {}({})", name, strerror(errno), errno)); + throw FileOpenFailedException(name); std::stringstream stream; stream << in.rdbuf(); From b3169410893012e71e4168549988d340351e9a81 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 01:39:35 +0100 Subject: [PATCH 096/627] [shared] Add missing lock --- src/platform/backends/shared/base_virtual_machine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6e028d6bfd4..4acd3c1f33e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -106,6 +106,7 @@ auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotV std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) const { + const std::shared_lock read_lock{snapshot_mutex}; return snapshots.at(name); } From bf0d02a4d6d9f9e18e48190204f37cef73de53a1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 01:38:27 +0100 Subject: [PATCH 097/627] [shared] Make lock const --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4acd3c1f33e..8cdeb164220 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -96,7 +96,7 @@ auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotV { SnapshotVista ret; - std::shared_lock read_lock{snapshot_mutex}; + const std::shared_lock read_lock{snapshot_mutex}; ret.reserve(snapshots.size()); std::transform(std::cbegin(snapshots), std::cend(snapshots), std::back_inserter(ret), [](const auto& pair) { return pair.second; }); From f67c58348dd2cb9095b95c6d34bb3137adf359aa Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 01:41:04 +0100 Subject: [PATCH 098/627] [shared] Fix late update of snapshot count --- src/platform/backends/shared/base_virtual_machine.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 8cdeb164220..3e48378a4a7 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -125,6 +125,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() noexcept { if (it->second) // snapshot was created { + --snapshot_count; head_snapshot = std::move(old_head); mp::top_catch_all(vm_name, [it] { it->second->delet(); }); } @@ -135,9 +136,9 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn // TODO@snapshots - generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); + ++snapshot_count; persist_head_snapshot(snapshot_dir); rollback_on_failure.dismiss(); - ++snapshot_count; log_latest_snapshot(std::move(write_lock)); From a33dd068f5e2013d8a69ef4638f1902e16ee1bb5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 02:39:31 +0100 Subject: [PATCH 099/627] [shared] Use recursive mutexes for snapshots We need the mutexes to be recursive, so that we can re-lock in the same thread, as member functions can call each other and the Snapshot/VM classes may call each other back. Unfortunately, there is no recursive shared mutex in the standard library, nor could I find one in Qt or POCO. I don't think we should bring another 3rd-party just for this use case, so we loose the distinction between read/write access. --- src/platform/backends/shared/base_snapshot.cpp | 2 +- src/platform/backends/shared/base_snapshot.h | 12 ++++++------ .../backends/shared/base_virtual_machine.cpp | 10 +++++----- src/platform/backends/shared/base_virtual_machine.h | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 5c34a3f3d25..f1b62dfc784 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -125,7 +125,7 @@ mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, const Virt QJsonObject multipass::BaseSnapshot::serialize() const { QJsonObject ret, snapshot{}; - const std::shared_lock lock{mutex}; + const std::unique_lock lock{mutex}; snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 308c457e79a..b93f5fe7945 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -25,7 +25,7 @@ #include -#include +#include namespace multipass { @@ -82,31 +82,31 @@ class BaseSnapshot : public Snapshot const std::unordered_map mounts; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QJsonObject metadata; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - mutable std::shared_mutex mutex; + mutable std::recursive_mutex mutex; }; } // namespace multipass inline std::string multipass::BaseSnapshot::get_name() const { - const std::shared_lock lock{mutex}; + const std::unique_lock lock{mutex}; return name; } inline std::string multipass::BaseSnapshot::get_comment() const { - const std::shared_lock lock{mutex}; + const std::unique_lock lock{mutex}; return comment; } inline std::string multipass::BaseSnapshot::get_parent_name() const { - const std::shared_lock lock{mutex}; + const std::unique_lock lock{mutex}; return parent ? parent->get_name() : ""; } inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr { - const std::shared_lock lock{mutex}; + const std::unique_lock lock{mutex}; return parent; } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 3e48378a4a7..2b06be69d68 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -96,7 +96,7 @@ auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotV { SnapshotVista ret; - const std::shared_lock read_lock{snapshot_mutex}; + const std::unique_lock lock{snapshot_mutex}; ret.reserve(snapshots.size()); std::transform(std::cbegin(snapshots), std::cend(snapshots), std::back_inserter(ret), [](const auto& pair) { return pair.second; }); @@ -106,7 +106,7 @@ auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotV std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) const { - const std::shared_lock read_lock{snapshot_mutex}; + const std::unique_lock lock{snapshot_mutex}; return snapshots.at(name); } @@ -115,7 +115,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn { // TODO@snapshots generate name { - std::unique_lock write_lock{snapshot_mutex}; + std::unique_lock lock{snapshot_mutex}; if (snapshot_count > max_snapshots) throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded", max_snapshots)}; @@ -140,7 +140,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn persist_head_snapshot(snapshot_dir); rollback_on_failure.dismiss(); - log_latest_snapshot(std::move(write_lock)); + log_latest_snapshot(std::move(lock)); return ret; } @@ -152,7 +152,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) { - std::unique_lock write_lock{snapshot_mutex}; + std::unique_lock lock{snapshot_mutex}; auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*.%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index cab0975ab74..aa04ecc49e6 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -30,7 +30,7 @@ #include #include -#include +#include #include namespace mp = multipass; @@ -73,7 +73,7 @@ class BaseVirtualMachine : public VirtualMachine SnapshotMap snapshots; std::shared_ptr head_snapshot = nullptr; size_t snapshot_count = 0; // tracks the number of snapshots ever taken (regardless or deletes) - mutable std::shared_mutex snapshot_mutex; + mutable std::recursive_mutex snapshot_mutex; }; } // namespace multipass From 6d25461cc099da9363378f6cc15554c9bf4c5853 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 02:44:41 +0100 Subject: [PATCH 100/627] [shared] Avoid locking parent of a locked snapshot --- src/platform/backends/shared/base_snapshot.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index b93f5fe7945..2cf598fd046 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -100,8 +100,11 @@ inline std::string multipass::BaseSnapshot::get_comment() const inline std::string multipass::BaseSnapshot::get_parent_name() const { - const std::unique_lock lock{mutex}; - return parent ? parent->get_name() : ""; + std::unique_lock lock{mutex}; + auto par = parent; + lock.unlock(); // avoid locking another snapshot while locked in here + + return par ? par->get_name() : ""; } inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr From 7ca43030c1674f0098134b3e0e32791a48681a1e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 02:54:34 +0100 Subject: [PATCH 101/627] [shared] Extract loading a snapshot from a file --- .../backends/shared/base_virtual_machine.cpp | 35 ++++++++++--------- .../backends/shared/base_virtual_machine.h | 1 + 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2b06be69d68..f720cce1f92 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -157,22 +157,7 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*.%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) - { - QFile file{finfo.filePath()}; - if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) - throw std::runtime_error{fmt::format("Could not open snapshot file for for reading: {}", file.fileName())}; - - QJsonParseError parse_error{}; - const auto& data = MP_FILEOPS.read_all(file); - - if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) - throw std::runtime_error{fmt::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), - parse_error.errorString())}; - else if (json.isEmpty()) - throw std::runtime_error{fmt::format("Empty snapshot JSON: {}", file.fileName())}; - else - load_snapshot(json); - } + load_snapshot_from_file(finfo.filePath()); try { @@ -206,6 +191,24 @@ void BaseVirtualMachine::log_latest_snapshot(LockT lock) const } } +void BaseVirtualMachine::load_snapshot_from_file(const QString& filename) +{ + QFile file{filename}; + if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) + throw std::runtime_error{fmt::v9::format("Could not open snapshot file for for reading: {}", file.fileName())}; + + QJsonParseError parse_error{}; + const auto& data = MP_FILEOPS.read_all(file); + + if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) + throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), + parse_error.errorString())}; + else if (json.isEmpty()) + throw std::runtime_error{fmt::v9::format("Empty snapshot JSON: {}", file.fileName())}; + else + load_snapshot(json); +} + void BaseVirtualMachine::load_snapshot(const QJsonObject& json) { // TODO@snapshots move to specific VM implementations and make specific snapshot from there diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index aa04ecc49e6..7ef0e504889 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -65,6 +65,7 @@ class BaseVirtualMachine : public VirtualMachine private: template void log_latest_snapshot(LockT lock) const; + void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& snapshot_dir) const; From b170d631010f22dad1ebb194caeb76e472c561cb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 02:57:12 +0100 Subject: [PATCH 102/627] [shared] Extract loading generic snapshot info --- src/platform/backends/shared/base_virtual_machine.cpp | 5 +++++ src/platform/backends/shared/base_virtual_machine.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f720cce1f92..e549ee1b1ef 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -159,6 +159,11 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) for (const auto& finfo : snapshot_files) load_snapshot_from_file(finfo.filePath()); + load_generic_snapshot_info(snapshot_dir); +} + +void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) +{ try { snapshot_count = std::stoull(mpu::contents_of(snapshot_dir.filePath(count_filename))); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 7ef0e504889..2444f93cf41 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -65,6 +65,7 @@ class BaseVirtualMachine : public VirtualMachine private: template void log_latest_snapshot(LockT lock) const; + void load_generic_snapshot_info(const QDir& snapshot_dir); void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& snapshot_dir) const; From 7e78f196ab6b419d0ba72027fe91e9fa46eb9f70 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 6 Apr 2023 03:16:32 +0100 Subject: [PATCH 103/627] [shared] Rollback persisted files on failure --- .../backends/shared/base_virtual_machine.cpp | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e549ee1b1ef..6b2745f9d86 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -236,15 +236,36 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const .arg(snapshot_count, index_digits, 10, QLatin1Char('0')) .arg(QString::fromStdString(head_snapshot->get_name()), snapshot_extension); - mp::write_json(head_snapshot->serialize(), snapshot_dir.filePath(snapshot_filename)); + auto snapshot_path = snapshot_dir.filePath(snapshot_filename); + auto head_path = snapshot_dir.filePath(head_filename); + auto count_path = snapshot_dir.filePath(count_filename); - // TODO@snapshots rollback snapshot file if writing generic info below fails + auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filename]() noexcept { + QFile{snapshot_filename}.remove(); // best effort, ignore return + }); + + mp::write_json(head_snapshot->serialize(), snapshot_path); auto overwrite = true; - MP_UTILS.make_file_with_content(snapshot_dir.filePath(head_filename).toStdString(), head_snapshot->get_name(), - overwrite); - MP_UTILS.make_file_with_content(snapshot_dir.filePath(count_filename).toStdString(), std::to_string(snapshot_count), - overwrite); + QFile head_file{head_path}; + + auto rollback_head_file = + sg::make_scope_guard([this, &head_path, &head_file, overwrite, old_head = head_snapshot->get_parent_name(), + existed = head_file.exists()]() noexcept { + // best effort, ignore returns + if (!existed) + head_file.remove(); + else + mp::top_catch_all(vm_name, [&head_path, &old_head, overwrite] { + MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, overwrite); + }); + }); + + MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), overwrite); + MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), overwrite); + + rollback_snapshot_file.dismiss(); + rollback_head_file.dismiss(); } } // namespace multipass From 14a18c587c3f82939f94dc59fefecf8591424b50 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 10 Apr 2023 17:34:35 +0100 Subject: [PATCH 104/627] [shared] Fix formatting --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6b2745f9d86..8b29a3b5f54 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -45,7 +45,7 @@ constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; constexpr auto index_digits = 4; // these two go together constexpr auto max_snapshots = 1000ull; // replace suffix with uz for size_t in C++23 -} +} // namespace namespace multipass { From e182663f5956c839f0bee0f7f0a329b7ec3e070d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 10 Apr 2023 19:08:52 +0100 Subject: [PATCH 105/627] [daemon] Refuse snapshots of all but stopped VMs --- src/daemon/daemon.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index cfe32558f50..3045500f410 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2375,6 +2375,11 @@ try auto* vm_ptr = std::get<0>(instance_trail)->second.get(); assert(vm_ptr); + using St = VirtualMachine::State; + if (auto state = vm_ptr->current_state(); state != St::off && state != St::stopped) + return status_promise->set_value( + grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only take snapshots of stopped instances."}); + const auto spec_it = vm_instance_specs.find(instance_name); assert(spec_it != vm_instance_specs.end() && "missing instance specs"); From 799e0849eb3bf6ea32e609a67e2d1713aef54bbc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 10 Apr 2023 19:20:12 +0100 Subject: [PATCH 106/627] [shared] Refuse bad params in all Snapshot ctors And not only when constructing from JSON. --- src/platform/backends/shared/base_snapshot.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f1b62dfc784..da95c33667a 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -22,7 +22,6 @@ #include #include // TODO@snapshots may be able to drop after extracting JSON utilities -#include #include @@ -93,6 +92,14 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme mounts{std::move(mounts)}, metadata{std::move(metadata)} { + if (name.empty()) + throw std::runtime_error{"Snapshot names cannot be empty"}; + if (num_cores < 1) + throw std::runtime_error{fmt::format("Invalid number of cores for snapshot: {}", num_cores)}; + if (auto mem_bytes = mem_size.in_bytes(); mem_bytes < 1) + throw std::runtime_error{fmt::format("Invalid memory size for snapshot: {}", mem_bytes)}; + if (auto disk_bytes = disk_space.in_bytes(); disk_bytes < 1) + throw std::runtime_error{fmt::format("Invalid disk size for snapshot: {}", disk_bytes)}; } mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, @@ -118,8 +125,6 @@ mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, const Virt load_mounts(json["mounts"].toArray()), // mounts json["metadata"].toObject()} // metadata { - if (name.empty() || !num_cores || !mem_size.in_bytes() || !disk_space.in_bytes()) - throw std::runtime_error{fmt::format("Bad snapshot data: {}", QJsonDocument{json}.toJson())}; } QJsonObject multipass::BaseSnapshot::serialize() const From efdc09511fbeaa6645938395b75cb305db7dcba5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 10 Apr 2023 19:37:25 +0100 Subject: [PATCH 107/627] [daemon] Refuse invalid snapshot names --- src/daemon/daemon.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3045500f410..9815f07f7c8 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2380,6 +2380,11 @@ try return status_promise->set_value( grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only take snapshots of stopped instances."}); + auto snapshot_name = request->snapshot(); + if (!mp::utils::valid_hostname(snapshot_name)) + return status_promise->set_value( + grpc::Status{grpc::INVALID_ARGUMENT, fmt::format(R"(Invalid snapshot name: "{}".)", snapshot_name)}); + const auto spec_it = vm_instance_specs.find(instance_name); assert(spec_it != vm_instance_specs.end() && "missing instance specs"); @@ -2387,7 +2392,7 @@ try { const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, - request->snapshot(), request->comment()); + snapshot_name, request->comment()); reply.set_snapshot(snapshot->get_name()); } From c31870de7eaf9cbd6d761dc54a39db26850a4e68 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 10 Apr 2023 19:40:13 +0100 Subject: [PATCH 108/627] [cli] Improve help for the name arg in `snapshot` Mention validity rules for snapshot names, deferring to `launch` help. Fix stray parenthesis and missing plural. --- src/client/cli/cmd/snapshot.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index d114d0a5b13..833eb8fd341 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -68,8 +68,9 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) { parser->addPositionalArgument("instance", "The instance to take a snapshot of."); QCommandLineOption name_opt({"n", "name"}, - "An optional name for the snapshot (default: \"snapshotN\", where N is one plus the " - "number of snapshot that were ever taken for .", + "An optional name for the snapshot, subject to the same validity rules as instance " + "names (see `help launch`). Default: \"snapshotN\", where N is one plus the " + "number of snapshots that were ever taken for .", "name"); QCommandLineOption comment_opt{ {"comment", "c", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; From 243a07465fca5fd37c18f05ef39f357a8fa65935 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 28 Mar 2023 15:19:50 +0100 Subject: [PATCH 109/627] [rpc] Cover snapshots in `info` request --- src/rpc/multipass.proto | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index c62a349874a..db18cf22832 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -158,14 +158,16 @@ message FindReply { string log_line = 5; } -message InstanceNames { +message InstanceOrSnapshot { repeated string instance_name = 1; + repeated string snapshot_name = 2; // if this is present, the msg specifies a snapshot; otherwise, an instance } message InfoRequest { - InstanceNames instance_names = 1; - int32 verbosity_level = 2; - bool no_runtime_information = 3; + repeated InstanceOrSnapshot instances_snapshots = 1; + bool snapshot_overview = 2; + int32 verbosity_level = 3; + bool no_runtime_information = 4; } message IdMap { @@ -244,7 +246,6 @@ message ListReply { UpdateInfo update_info = 3; } - message NetworksRequest { int32 verbosity_level = 1; } @@ -291,6 +292,10 @@ message PingRequest { message PingReply { } +message InstanceNames { + repeated string instance_name = 1; +} + message RecoverRequest { InstanceNames instance_names = 1; int32 verbosity_level = 2; From 9ebb35e9cbbc7879212aebca794c0f277162af74 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 28 Mar 2023 23:21:10 +0100 Subject: [PATCH 110/627] [rpc] Cover snapshots in `info` reply --- src/rpc/multipass.proto | 74 +++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index db18cf22832..1438709c108 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -205,26 +205,64 @@ message InstanceStatus { Status status = 1; } +message InstanceSpecificDetails { + string image_release = 1; + string current_release = 2; + string id = 3; + string load = 4; + string memory_usage = 5; + string disk_usage = 6; + repeated string ipv4 = 7; + repeated string ipv6 = 8; + int32 num_snapshots = 9; +} + +message SnapshotSpecificDetails { + string snapshot_name = 1; + string size = 2; + int32 creation_timestamp = 3; // TODO@snapshots use timestamp type + string parent = 4; + repeated string children = 5; + string comment = 6; +} + +message DetailedInfoItem { + string instance_name = 1; + InstanceStatus instance_status = 2; + string memory_total = 3; + string disk_total = 4; + string cpu_count = 5; + MountInfo mount_info = 6; + + oneof extra_info { + InstanceSpecificDetails instance_info = 7; + SnapshotSpecificDetails snapshot_info = 8; + } +} + +message SnapshotOverviewInfoItem { + string instance_name = 1; + string snapshot_name = 2; + string parent = 3; + string comment = 4; +} + +message DetailedInfoReport { + repeated DetailedInfoItem details = 1; +} + +message SnapshotOverviewInfoReport { + repeated SnapshotOverviewInfoItem overview = 1; +} + message InfoReply { - message Info { - string name = 1; - InstanceStatus instance_status = 2; - string image_release = 3; - string current_release = 4; - string id = 5; - string load = 6; - string memory_usage = 7; - string memory_total = 8; - string disk_usage = 9; - string disk_total = 10; - repeated string ipv4 = 11; - repeated string ipv6 = 12; - MountInfo mount_info = 13; - string cpu_count = 14; - int32 num_snapshots = 15; + oneof info_contents + { + DetailedInfoReport details = 1; + SnapshotOverviewInfoReport snapshot_overview = 2; } - repeated Info info = 1; - string log_line = 2; + + string log_line = 3; } message ListRequest { From d9353474af783f336c5072333eacb8f8ec569549 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 28 Mar 2023 23:26:14 +0100 Subject: [PATCH 111/627] [rpc] Refactor fundamental snapshot info --- src/rpc/multipass.proto | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 1438709c108..c25a9d2fd7f 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -217,13 +217,17 @@ message InstanceSpecificDetails { int32 num_snapshots = 9; } -message SnapshotSpecificDetails { +message SnapshotFundamentalInfo { string snapshot_name = 1; + string parent = 2; + string comment = 3; +} + +message SnapshotSpecificDetails { + SnapshotFundamentalInfo fundamentals = 1; string size = 2; int32 creation_timestamp = 3; // TODO@snapshots use timestamp type - string parent = 4; - repeated string children = 5; - string comment = 6; + repeated string children = 4; } message DetailedInfoItem { @@ -242,9 +246,7 @@ message DetailedInfoItem { message SnapshotOverviewInfoItem { string instance_name = 1; - string snapshot_name = 2; - string parent = 3; - string comment = 4; + SnapshotFundamentalInfo fundamentals = 2; } message DetailedInfoReport { From 49eecfa46345ae7167c2f38061b7045b1cc899d7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 11:47:21 +0100 Subject: [PATCH 112/627] [rpc] Fix wrong repeated field --- src/rpc/multipass.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index c25a9d2fd7f..6c635ce628d 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -159,8 +159,8 @@ message FindReply { } message InstanceOrSnapshot { - repeated string instance_name = 1; - repeated string snapshot_name = 2; // if this is present, the msg specifies a snapshot; otherwise, an instance + string instance_name = 1; + string snapshot_name = 2; // if this is present, the msg specifies a snapshot; otherwise, an instance } message InfoRequest { From a2e25f7e90a6be90c207b64c8dcb0bfd502ccdf0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 11:53:37 +0100 Subject: [PATCH 113/627] [rpc] Pull timestamp into snapshots fundamentals --- src/rpc/multipass.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 6c635ce628d..7ec3275d7b3 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -221,12 +221,12 @@ message SnapshotFundamentalInfo { string snapshot_name = 1; string parent = 2; string comment = 3; + int32 creation_timestamp = 4; // TODO@snapshots use timestamp type } message SnapshotSpecificDetails { SnapshotFundamentalInfo fundamentals = 1; string size = 2; - int32 creation_timestamp = 3; // TODO@snapshots use timestamp type repeated string children = 4; } From 1d61954f7cd48f29d75325e08907bd69a87f36ab Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 11:56:36 +0100 Subject: [PATCH 114/627] [rpc] Rename message for SnapshotFundamentals --- src/rpc/multipass.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 7ec3275d7b3..6cb224dd092 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -217,7 +217,7 @@ message InstanceSpecificDetails { int32 num_snapshots = 9; } -message SnapshotFundamentalInfo { +message SnapshotFundamentals { string snapshot_name = 1; string parent = 2; string comment = 3; @@ -225,7 +225,7 @@ message SnapshotFundamentalInfo { } message SnapshotSpecificDetails { - SnapshotFundamentalInfo fundamentals = 1; + SnapshotFundamentals fundamentals = 1; string size = 2; repeated string children = 4; } @@ -246,7 +246,7 @@ message DetailedInfoItem { message SnapshotOverviewInfoItem { string instance_name = 1; - SnapshotFundamentalInfo fundamentals = 2; + SnapshotFundamentals fundamentals = 2; } message DetailedInfoReport { From 4401e5dd575413f4ffb1c7a6f863ae0563e58a13 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 29 Mar 2023 10:01:14 -0700 Subject: [PATCH 115/627] [rpc] change to using timestamp type --- src/rpc/multipass.proto | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 6cb224dd092..2978771745d 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -16,6 +16,8 @@ syntax = "proto3"; package multipass; +import "google/protobuf/timestamp.proto"; + service Rpc { rpc create (stream LaunchRequest) returns (stream LaunchReply); rpc launch (stream LaunchRequest) returns (stream LaunchReply); @@ -221,7 +223,7 @@ message SnapshotFundamentals { string snapshot_name = 1; string parent = 2; string comment = 3; - int32 creation_timestamp = 4; // TODO@snapshots use timestamp type + google.protobuf.Timestamp creation_timestamp = 4; } message SnapshotSpecificDetails { From 1cf5d990f611f484cc525f3dc4b7487a2974fc0b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 29 Mar 2023 10:02:24 -0700 Subject: [PATCH 116/627] [build] reference protobuf src dir --- 3rd-party/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rd-party/CMakeLists.txt b/3rd-party/CMakeLists.txt index 6bca077ef8b..d122905d78b 100644 --- a/3rd-party/CMakeLists.txt +++ b/3rd-party/CMakeLists.txt @@ -54,7 +54,7 @@ function(generate_grpc_cpp SRCS DEST) "${DEST}/${FIL_WE}.pb.cc" "${DEST}/${FIL_WE}.pb.h" COMMAND $ - ARGS --grpc_out=${DEST} --cpp_out=${DEST} --proto_path=${FIL_DIR} --plugin=protoc-gen-grpc=$ ${ABS_FIL} + ARGS --grpc_out=${DEST} --cpp_out=${DEST} --proto_path=${FIL_DIR} --proto_path=${grpc_SOURCE_DIR}/third_party/protobuf/src --plugin=protoc-gen-grpc=$ ${ABS_FIL} DEPENDS ${ABS_FIL} protoc grpc_cpp_plugin COMMENT "Running gRPC C++ protocol buffer compiler on ${FIL}" VERBATIM) From 36f444e65e32894fe51c8cef15daab805fd4a37f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 29 Mar 2023 18:21:16 +0100 Subject: [PATCH 117/627] [rpc] Use `optional` in optional string field --- src/rpc/multipass.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 2978771745d..0aeb25bbb00 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -162,7 +162,7 @@ message FindReply { message InstanceOrSnapshot { string instance_name = 1; - string snapshot_name = 2; // if this is present, the msg specifies a snapshot; otherwise, an instance + optional string snapshot_name = 2; // if this is present, the msg specifies a snapshot; otherwise, an instance } message InfoRequest { From 8aab2407fbe7cd3afce6fa41a9688d2068386202 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 10 Apr 2023 18:11:45 +0100 Subject: [PATCH 118/627] [rpc] Rename protobut types As per review suggestions. Co-authored-by: ScottH <59572507+sharder996@users.noreply.github.com> Signed-off-by: Ricardo Abreu --- src/rpc/multipass.proto | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 0aeb25bbb00..e7e01474fa1 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -207,7 +207,7 @@ message InstanceStatus { Status status = 1; } -message InstanceSpecificDetails { +message InstanceDetails { string image_release = 1; string current_release = 2; string id = 3; @@ -226,7 +226,7 @@ message SnapshotFundamentals { google.protobuf.Timestamp creation_timestamp = 4; } -message SnapshotSpecificDetails { +message SnapshotDetails { SnapshotFundamentals fundamentals = 1; string size = 2; repeated string children = 4; @@ -241,8 +241,8 @@ message DetailedInfoItem { MountInfo mount_info = 6; oneof extra_info { - InstanceSpecificDetails instance_info = 7; - SnapshotSpecificDetails snapshot_info = 8; + InstanceDetails instance_info = 7; + SnapshotDetails snapshot_info = 8; } } From f8bd686fe12debce86226c07b600b461a62d5e9c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 11 Apr 2023 20:07:02 +0100 Subject: [PATCH 119/627] [rpc] Rename field in InfoReply Addresses review request. --- src/rpc/multipass.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index e7e01474fa1..a8b792af6cf 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -262,7 +262,7 @@ message SnapshotOverviewInfoReport { message InfoReply { oneof info_contents { - DetailedInfoReport details = 1; + DetailedInfoReport detailed_report = 1; SnapshotOverviewInfoReport snapshot_overview = 2; } From f984a9efe772a604130a110637b361e28958905e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 12 Apr 2023 10:45:47 +0100 Subject: [PATCH 120/627] [rpc] Rename inner RPC message Addresses review request. --- src/rpc/multipass.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index a8b792af6cf..6d2aae5ee41 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -160,13 +160,13 @@ message FindReply { string log_line = 5; } -message InstanceOrSnapshot { +message InstanceSnapshotPair { string instance_name = 1; optional string snapshot_name = 2; // if this is present, the msg specifies a snapshot; otherwise, an instance } message InfoRequest { - repeated InstanceOrSnapshot instances_snapshots = 1; + repeated InstanceSnapshotPair instances_snapshots = 1; bool snapshot_overview = 2; int32 verbosity_level = 3; bool no_runtime_information = 4; From 1f14d434c3fe610341f156d2b9681126209e9d09 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 29 Mar 2023 16:41:17 -0700 Subject: [PATCH 121/627] [build] building and passing unit tests --- src/client/cli/cmd/alias.cpp | 2 +- src/client/cli/cmd/exec.cpp | 7 +-- src/client/cli/cmd/info.cpp | 25 ++++++++- src/client/cli/formatter/csv_formatter.cpp | 17 +++--- src/client/cli/formatter/json_formatter.cpp | 26 ++++----- src/client/cli/formatter/table_formatter.cpp | 39 ++++++++------ src/client/cli/formatter/yaml_formatter.cpp | 27 +++++----- src/daemon/daemon.cpp | 28 ++++++---- src/rpc/multipass.proto | 2 +- tests/test_cli_client.cpp | 4 +- tests/test_daemon.cpp | 48 ++++++++--------- tests/test_output_formatter.cpp | 56 ++++++++++---------- 12 files changed, 160 insertions(+), 121 deletions(-) diff --git a/src/client/cli/cmd/alias.cpp b/src/client/cli/cmd/alias.cpp index 081d407999f..a231f30d2c5 100644 --- a/src/client/cli/cmd/alias.cpp +++ b/src/client/cli/cmd/alias.cpp @@ -116,7 +116,7 @@ mp::ParseCode cmd::Alias::parse_args(mp::ArgParser* parser) auto instance = definition.left(colon_pos).toStdString(); auto working_directory = parser->isSet(no_alias_dir_mapping_option) ? "default" : "map"; - info_request.mutable_instance_names()->add_instance_name(instance); + info_request.add_instances_snapshots()->set_instance_name(instance); info_request.set_verbosity_level(0); info_request.set_no_runtime_information(true); diff --git a/src/client/cli/cmd/exec.cpp b/src/client/cli/cmd/exec.cpp index 7542e147222..4245da6dcd4 100644 --- a/src/client/cli/cmd/exec.cpp +++ b/src/client/cli/cmd/exec.cpp @@ -78,7 +78,7 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser) QStringList split_exec_dir = clean_exec_dir.split('/'); auto on_info_success = [&work_dir, &split_exec_dir](mp::InfoReply& reply) { - for (const auto& mount : reply.info(0).mount_info().mount_paths()) + for (const auto& mount : reply.details().details(0).mount_info().mount_paths()) { auto source_dir = QDir(QString::fromStdString(mount.source_path())); auto clean_source_dir = QDir::cleanPath(source_dir.absolutePath()); @@ -102,10 +102,7 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser) info_request.set_verbosity_level(parser->verbosityLevel()); - InstanceNames instance_names; - auto info_instance_name = instance_names.add_instance_name(); - info_instance_name->append(instance_name); - info_request.mutable_instance_names()->CopyFrom(instance_names); + info_request.add_instances_snapshots()->set_instance_name(instance_name); info_request.set_no_runtime_information(true); dispatch(&RpcMethod::info, info_request, on_info_success, on_info_failure); diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 5274f2b73fd..c89487f9893 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -24,6 +24,28 @@ namespace mp = multipass; namespace cmd = multipass::cmd; +namespace +{ +std::vector add_instance_and_snapshot_names(const mp::ArgParser* parser) +{ + std::vector instance_snapshot_names; + + for (const auto& arg : parser->positionalArguments()) + { + const auto tokens = arg.split('.'); + + mp::InstanceOrSnapshot inst_snap_name; + inst_snap_name.set_instance_name(tokens[0].toStdString()); + if (tokens.size() > 1) + inst_snap_name.set_snapshot_name(tokens[1].toStdString()); + + instance_snapshot_names.push_back(inst_snap_name); + } + + return instance_snapshot_names; +} +} // namespace + mp::ReturnCode cmd::Info::run(mp::ArgParser* parser) { auto ret = parse_args(parser); @@ -85,7 +107,8 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) if (parse_code != ParseCode::Ok) return parse_code; - request.mutable_instance_names()->CopyFrom(add_instance_names(parser)); + for (const auto& item : add_instance_and_snapshot_names(parser)) + request.add_instances_snapshots()->CopyFrom(item); request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); status = handle_format_option(parser, &chosen_formatter, cerr); diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 996083ffe33..f9bbfde0012 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -55,12 +55,17 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory " "total,Mounts,AllIPv4,CPU(s),Snapshots\n"); - for (const auto& info : format::sorted(reply.info())) + for (const auto& info : format::sorted(reply.details().details())) { + const auto& instance_details = info.instance_info(); + fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},{},{},{},{},{},{},{},", info.name(), - mp::format::status_string_for(info.instance_status()), info.ipv4_size() ? info.ipv4(0) : "", - info.ipv6_size() ? info.ipv6(0) : "", info.current_release(), info.id(), info.image_release(), - info.load(), info.disk_usage(), info.disk_total(), info.memory_usage(), info.memory_total()); + mp::format::status_string_for(info.instance_status()), + instance_details.ipv4_size() ? instance_details.ipv4(0) : "", + instance_details.ipv6_size() ? instance_details.ipv6(0) : "", instance_details.current_release(), + instance_details.id(), instance_details.image_release(), instance_details.load(), + instance_details.disk_usage(), info.disk_total(), instance_details.memory_usage(), + info.memory_total()); auto mount_paths = info.mount_info().mount_paths(); for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) @@ -68,8 +73,8 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); } - fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(info.ipv4(), ","), info.cpu_count(), - info.num_snapshots()); + fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), + info.cpu_count(), instance_details.num_snapshots()); } return fmt::to_string(buf); } diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 7e70b9b1849..36b68b49cc3 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -64,20 +64,22 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const info_json.insert("errors", QJsonArray()); - for (const auto& info : reply.info()) + for (const auto& info : reply.details().details()) { + const auto& instance_details = info.instance_info(); + QJsonObject instance_info; instance_info.insert("state", QString::fromStdString(mp::format::status_string_for(info.instance_status()))); - instance_info.insert("image_hash", QString::fromStdString(info.id())); - instance_info.insert("image_release", QString::fromStdString(info.image_release())); - instance_info.insert("release", QString::fromStdString(info.current_release())); + instance_info.insert("image_hash", QString::fromStdString(instance_details.id())); + instance_info.insert("image_release", QString::fromStdString(instance_details.image_release())); + instance_info.insert("release", QString::fromStdString(instance_details.current_release())); instance_info.insert("cpu_count", QString::fromStdString(info.cpu_count())); - instance_info.insert("snapshots", QString::number(info.num_snapshots())); + instance_info.insert("snapshots", QString::number(instance_details.num_snapshots())); QJsonArray load; - if (!info.load().empty()) + if (!instance_details.load().empty()) { - auto loads = mp::utils::split(info.load(), " "); + auto loads = mp::utils::split(instance_details.load(), " "); for (const auto& entry : loads) load.append(std::stod(entry)); } @@ -85,8 +87,8 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const QJsonObject disks; QJsonObject disk; - if (!info.disk_usage().empty()) - disk.insert("used", QString::fromStdString(info.disk_usage())); + if (!instance_details.disk_usage().empty()) + disk.insert("used", QString::fromStdString(instance_details.disk_usage())); if (!info.disk_total().empty()) disk.insert("total", QString::fromStdString(info.disk_total())); @@ -95,14 +97,14 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const instance_info.insert("disks", disks); QJsonObject memory; - if (!info.memory_usage().empty()) - memory.insert("used", std::stoll(info.memory_usage())); + if (!instance_details.memory_usage().empty()) + memory.insert("used", std::stoll(instance_details.memory_usage())); if (!info.memory_total().empty()) memory.insert("total", std::stoll(info.memory_total())); instance_info.insert("memory", memory); QJsonArray ipv4_addrs; - for (const auto& ip : info.ipv4()) + for (const auto& ip : instance_details.ipv4()) ipv4_addrs.append(QString::fromStdString(ip)); instance_info.insert("ipv4", ipv4_addrs); diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 60a2d3cd0f0..cd7589afe80 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -58,42 +58,47 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const { fmt::memory_buffer buf; - for (const auto& info : format::sorted(reply.info())) + for (const auto& info : format::sorted(reply.details().details())) { + const auto& instance_details = info.instance_info(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "State:", mp::format::status_string_for(info.instance_status())); - int ipv4_size = info.ipv4_size(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? info.ipv4(0) : "--"); + int ipv4_size = instance_details.ipv4_size(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); for (int i = 1; i < ipv4_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", info.ipv4(i)); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); - if (int ipv6_size = info.ipv6_size()) + if (int ipv6_size = instance_details.ipv6_size()) { - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", info.ipv6(0)); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); for (int i = 1; i < ipv6_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", info.ipv6(i)); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Release:", info.current_release().empty() ? "--" : info.current_release()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Release:", + instance_details.current_release().empty() ? "--" : instance_details.current_release()); fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); - if (info.id().empty()) + if (instance_details.id().empty()) fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); else - fmt::format_to(std::back_inserter(buf), "{}{}\n", info.id().substr(0, 12), - !info.image_release().empty() ? fmt::format(" (Ubuntu {})", info.image_release()) : ""); + fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), + !instance_details.image_release().empty() + ? fmt::format(" (Ubuntu {})", instance_details.image_release()) + : ""); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "CPU(s):", info.cpu_count().empty() ? "--" : info.cpu_count()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Load:", info.load().empty() ? "--" : info.load()); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Disk usage:", to_usage(info.disk_usage(), info.disk_total())); + "Load:", instance_details.load().empty() ? "--" : instance_details.load()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Disk usage:", to_usage(instance_details.disk_usage(), info.disk_total())); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Memory usage:", to_usage(info.memory_usage(), info.memory_total())); + "Memory usage:", to_usage(instance_details.memory_usage(), info.memory_total())); auto mount_paths = info.mount_info().mount_paths(); fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); @@ -135,13 +140,13 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const } } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", info.num_snapshots()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); fmt::format_to(std::back_inserter(buf), "\n"); } auto output = fmt::to_string(buf); - if (!reply.info().empty()) + if (!reply.details().details().empty()) output.pop_back(); else output = "\n"; diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 13910a02f60..2404d537fd1 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -65,28 +65,29 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const info_node["errors"].push_back(YAML::Null); - for (const auto& info : format::sorted(reply.info())) + for (const auto& info : format::sorted(reply.details().details())) { + const auto& instance_details = info.instance_info(); YAML::Node instance_node; instance_node["state"] = mp::format::status_string_for(info.instance_status()); - instance_node["image_hash"] = info.id(); - instance_node["image_release"] = info.image_release(); - if (info.current_release().empty()) + instance_node["image_hash"] = instance_details.id(); + instance_node["image_release"] = instance_details.image_release(); + if (instance_details.current_release().empty()) instance_node["release"] = YAML::Null; else - instance_node["release"] = info.current_release(); + instance_node["release"] = instance_details.current_release(); instance_node["cpu_count"] = YAML::Null; if (!info.cpu_count().empty()) instance_node["cpu_count"] = info.cpu_count(); - if (!info.load().empty()) + if (!instance_details.load().empty()) { // The VM returns load info in the default C locale auto current_loc = std::locale(); std::locale::global(std::locale("C")); - auto loads = mp::utils::split(info.load(), " "); + auto loads = mp::utils::split(instance_details.load(), " "); for (const auto& entry : loads) instance_node["load"].push_back(entry); std::locale::global(current_loc); @@ -95,8 +96,8 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const YAML::Node disk; disk["used"] = YAML::Null; disk["total"] = YAML::Null; - if (!info.disk_usage().empty()) - disk["used"] = info.disk_usage(); + if (!instance_details.disk_usage().empty()) + disk["used"] = instance_details.disk_usage(); if (!info.disk_total().empty()) disk["total"] = info.disk_total(); @@ -108,15 +109,15 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const YAML::Node memory; memory["usage"] = YAML::Null; memory["total"] = YAML::Null; - if (!info.memory_usage().empty()) - memory["usage"] = std::stoll(info.memory_usage()); + if (!instance_details.memory_usage().empty()) + memory["usage"] = std::stoll(instance_details.memory_usage()); if (!info.memory_total().empty()) memory["total"] = std::stoll(info.memory_total()); instance_node["memory"] = memory; instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); - for (const auto& ip : info.ipv4()) + for (const auto& ip : instance_details.ipv4()) instance_node["ipv4"].push_back(ip); YAML::Node mounts; @@ -147,7 +148,7 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const mounts[mount.target_path()] = mount_node; } instance_node["mounts"] = mounts; - instance_node["snapshots"] = info.num_snapshots(); + instance_node["snapshots"] = instance_details.num_snapshots(); info_node[info.name()].push_back(instance_node); } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9815f07f7c8..9795f7e0722 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1558,7 +1558,8 @@ try // clang-format on bool deleted = false; auto fetch_info = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; - auto info = response.add_info(); + auto info = response.mutable_details()->add_details(); + auto instance_info = info->mutable_instance_info(); auto present_state = vm.current_state(); info->set_name(name); if (deleted) @@ -1586,8 +1587,8 @@ try // clang-format on } } - info->set_image_release(original_release); - info->set_id(vm_image.id); + instance_info->set_image_release(original_release); + instance_info->set_id(vm_image.id); auto vm_specs = vm_instance_specs[name]; @@ -1630,10 +1631,11 @@ try // clang-format on { mp::SSHSession session{vm.ssh_hostname(), vm.ssh_port(), vm_specs.ssh_username, *config->ssh_key_provider}; - info->set_load(mpu::run_in_ssh_session(session, "cat /proc/loadavg | cut -d ' ' -f1-3")); - info->set_memory_usage(mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $3}'")); + instance_info->set_load(mpu::run_in_ssh_session(session, "cat /proc/loadavg | cut -d ' ' -f1-3")); + instance_info->set_memory_usage( + mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $3}'")); info->set_memory_total(mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $2}'")); - info->set_disk_usage( + instance_info->set_disk_usage( mpu::run_in_ssh_session(session, "df -t ext4 -t vfat --total -B1 --output=used | tail -n 1")); info->set_disk_total( mpu::run_in_ssh_session(session, "df -t ext4 -t vfat --total -B1 --output=size | tail -n 1")); @@ -1643,23 +1645,27 @@ try // clang-format on auto all_ipv4 = vm.get_all_ipv4(*config->ssh_key_provider); if (is_ipv4_valid(management_ip)) - info->add_ipv4(management_ip); + instance_info->add_ipv4(management_ip); else if (all_ipv4.empty()) - info->add_ipv4("N/A"); + instance_info->add_ipv4("N/A"); for (const auto& extra_ipv4 : all_ipv4) if (extra_ipv4 != management_ip) - info->add_ipv4(extra_ipv4); + instance_info->add_ipv4(extra_ipv4); auto current_release = mpu::run_in_ssh_session(session, "cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2"); - info->set_current_release(!current_release.empty() ? current_release : original_release); + instance_info->set_current_release(!current_release.empty() ? current_release : original_release); } return grpc::Status::OK; }; + mp::InstanceNames instance_names; + for (const auto& n : request->instances_snapshots()) + instance_names.add_instance_name(n.instance_name()); + auto [instance_selection, status] = - select_instances_and_react(operative_instances, deleted_instances, request->instance_names().instance_name(), + select_instances_and_react(operative_instances, deleted_instances, instance_names.instance_name(), InstanceGroup::All, require_existing_instances_reaction); if (status.ok()) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 6d2aae5ee41..a5d04ccba81 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -233,7 +233,7 @@ message SnapshotDetails { } message DetailedInfoItem { - string instance_name = 1; + string name = 1; InstanceStatus instance_status = 2; string memory_total = 3; string disk_total = 4; diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 21da093f2f7..84572eeb8e6 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -404,9 +404,9 @@ auto make_info_function(const std::string& source_path = "", const std::string& mp::InfoReply info_reply; - if (request.instance_names().instance_name(0) == "primary") + if (request.instances_snapshots(0).instance_name() == "primary") { - auto vm_info = info_reply.add_info(); + auto vm_info = info_reply.mutable_details()->add_details(); vm_info->set_name("primary"); vm_info->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 0d6c1267082..6e58f3ae306 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -2161,28 +2161,28 @@ TEST_F(Daemon, launch_fails_with_incompatible_blueprint) EXPECT_THAT(err_stream.str(), HasSubstr("The \"foo\" Blueprint is not compatible with this host.")); } -TEST_F(Daemon, info_all_returns_all_instances) -{ - const std::string good_instance_name{"good-instance"}, deleted_instance_name{"deleted-instance"}; - const auto good_instance_json = fmt::format(valid_template, good_instance_name, "10"); - const auto deleted_instance_json = fmt::format(deleted_template, deleted_instance_name, "11"); - const auto instances_json = fmt::format("{{{}, {}}}", good_instance_json, deleted_instance_json); - const auto [temp_dir, __] = plant_instance_json(instances_json); - config_builder.data_directory = temp_dir->path(); - config_builder.vault = std::make_unique>(); - - EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { - return std::make_unique(desc.vm_name); - })); - - const auto names_matcher = UnorderedElementsAre(Property(&mp::InfoReply::Info::name, good_instance_name), - Property(&mp::InfoReply::Info::name, deleted_instance_name)); - - StrictMock> mock_server{}; - EXPECT_CALL(mock_server, Write(Property(&mp::InfoReply::info, names_matcher), _)).WillOnce(Return(true)); - - mp::Daemon daemon{config_builder.build()}; - - call_daemon_slot(daemon, &mp::Daemon::info, mp::InfoRequest{}, mock_server); -} +// TEST_F(Daemon, info_all_returns_all_instances) +// { +// const std::string good_instance_name{"good-instance"}, deleted_instance_name{"deleted-instance"}; +// const auto good_instance_json = fmt::format(valid_template, good_instance_name, "10"); +// const auto deleted_instance_json = fmt::format(deleted_template, deleted_instance_name, "11"); +// const auto instances_json = fmt::format("{{{}, {}}}", good_instance_json, deleted_instance_json); +// const auto [temp_dir, __] = plant_instance_json(instances_json); +// config_builder.data_directory = temp_dir->path(); +// config_builder.vault = std::make_unique>(); + +// EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { +// return std::make_unique(desc.vm_name); +// })); + +// const auto names_matcher = UnorderedElementsAre(Property(&mp::InfoReply::Info::name, good_instance_name), +// Property(&mp::InfoReply::Info::name, deleted_instance_name)); + +// StrictMock> mock_server{}; +// EXPECT_CALL(mock_server, Write(Property(&mp::InfoReply::info, names_matcher), _)).WillOnce(Return(true)); + +// mp::Daemon daemon{config_builder.build()}; + +// call_daemon_slot(daemon, &mp::Daemon::info, mp::InfoRequest{}, mock_server); +// } } // namespace diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 070b1716adf..9c77fd5c916 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -152,11 +152,11 @@ auto construct_single_instance_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.add_info(); + auto info_entry = info_reply.mutable_details()->add_details(); info_entry->set_name("foo"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); - info_entry->set_image_release("16.04 LTS"); - info_entry->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); + info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); + info_entry->mutable_instance_info()->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); auto mount_info = info_entry->mutable_mount_info(); mount_info->set_longest_path_len(19); @@ -186,17 +186,17 @@ auto construct_single_instance_info_reply() gid_map_pair->set_instance_id(1000); info_entry->set_cpu_count("1"); - info_entry->set_load("0.45 0.51 0.15"); - info_entry->set_memory_usage("60817408"); + info_entry->mutable_instance_info()->set_load("0.45 0.51 0.15"); + info_entry->mutable_instance_info()->set_memory_usage("60817408"); info_entry->set_memory_total("1503238554"); - info_entry->set_disk_usage("1288490188"); + info_entry->mutable_instance_info()->set_disk_usage("1288490188"); info_entry->set_disk_total("5153960756"); - info_entry->set_current_release("Ubuntu 16.04.3 LTS"); - info_entry->add_ipv4("10.168.32.2"); - info_entry->add_ipv4("200.3.123.29"); - info_entry->add_ipv6("2001:67c:1562:8007::aac:423a"); - info_entry->add_ipv6("fd52:2ccf:f758:0:a342:79b5:e2ba:e05e"); - info_entry->set_num_snapshots(0); + info_entry->mutable_instance_info()->set_current_release("Ubuntu 16.04.3 LTS"); + info_entry->mutable_instance_info()->add_ipv4("10.168.32.2"); + info_entry->mutable_instance_info()->add_ipv4("200.3.123.29"); + info_entry->mutable_instance_info()->add_ipv6("2001:67c:1562:8007::aac:423a"); + info_entry->mutable_instance_info()->add_ipv6("fd52:2ccf:f758:0:a342:79b5:e2ba:e05e"); + info_entry->mutable_instance_info()->set_num_snapshots(0); return info_reply; } @@ -205,11 +205,11 @@ auto construct_multiple_instances_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.add_info(); + auto info_entry = info_reply.mutable_details()->add_details(); info_entry->set_name("bogus-instance"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); - info_entry->set_image_release("16.04 LTS"); - info_entry->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); + info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); + info_entry->mutable_instance_info()->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); auto mount_info = info_entry->mutable_mount_info(); mount_info->set_longest_path_len(17); @@ -227,32 +227,32 @@ auto construct_multiple_instances_info_reply() gid_map_pair->set_instance_id(501); info_entry->set_cpu_count("4"); - info_entry->set_load("0.03 0.10 0.15"); - info_entry->set_memory_usage("38797312"); + info_entry->mutable_instance_info()->set_load("0.03 0.10 0.15"); + info_entry->mutable_instance_info()->set_memory_usage("38797312"); info_entry->set_memory_total("1610612736"); - info_entry->set_disk_usage("1932735284"); + info_entry->mutable_instance_info()->set_disk_usage("1932735284"); info_entry->set_disk_total("6764573492"); - info_entry->set_current_release("Ubuntu 16.04.3 LTS"); - info_entry->add_ipv4("10.21.124.56"); - info_entry->set_num_snapshots(1); + info_entry->mutable_instance_info()->set_current_release("Ubuntu 16.04.3 LTS"); + info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); + info_entry->mutable_instance_info()->set_num_snapshots(1); - info_entry = info_reply.add_info(); + info_entry = info_reply.mutable_details()->add_details(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); - info_entry->set_image_release("18.04 LTS"); - info_entry->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); - info_entry->set_num_snapshots(3); + info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); + info_entry->mutable_instance_info()->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); + info_entry->mutable_instance_info()->set_num_snapshots(3); return info_reply; } auto add_petenv_to_reply(mp::InfoReply& reply) { - auto entry = reply.add_info(); + auto entry = reply.mutable_details()->add_details(); entry->set_name(petenv_name()); entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); - entry->set_image_release("18.10"); - entry->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); + entry->mutable_instance_info()->set_image_release("18.10"); + entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); } auto construct_empty_reply() From bafab280d38509888fe868f2bcd93cf7faa74863 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Apr 2023 22:05:46 -0700 Subject: [PATCH 122/627] [tests] update test in accordance with grpc changes --- tests/test_daemon.cpp | 53 +++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 6e58f3ae306..8fe540d70f7 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -2161,28 +2161,33 @@ TEST_F(Daemon, launch_fails_with_incompatible_blueprint) EXPECT_THAT(err_stream.str(), HasSubstr("The \"foo\" Blueprint is not compatible with this host.")); } -// TEST_F(Daemon, info_all_returns_all_instances) -// { -// const std::string good_instance_name{"good-instance"}, deleted_instance_name{"deleted-instance"}; -// const auto good_instance_json = fmt::format(valid_template, good_instance_name, "10"); -// const auto deleted_instance_json = fmt::format(deleted_template, deleted_instance_name, "11"); -// const auto instances_json = fmt::format("{{{}, {}}}", good_instance_json, deleted_instance_json); -// const auto [temp_dir, __] = plant_instance_json(instances_json); -// config_builder.data_directory = temp_dir->path(); -// config_builder.vault = std::make_unique>(); - -// EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { -// return std::make_unique(desc.vm_name); -// })); - -// const auto names_matcher = UnorderedElementsAre(Property(&mp::InfoReply::Info::name, good_instance_name), -// Property(&mp::InfoReply::Info::name, deleted_instance_name)); - -// StrictMock> mock_server{}; -// EXPECT_CALL(mock_server, Write(Property(&mp::InfoReply::info, names_matcher), _)).WillOnce(Return(true)); - -// mp::Daemon daemon{config_builder.build()}; - -// call_daemon_slot(daemon, &mp::Daemon::info, mp::InfoRequest{}, mock_server); -// } +TEST_F(Daemon, info_all_returns_all_instances) +{ + const std::string good_instance_name{"good-instance"}, deleted_instance_name{"deleted-instance"}; + const std::string good_instance_name2{"good-instance"}, deleted_instance_name2{"deleted-instance"}; + const auto good_instance_json = fmt::format(valid_template, good_instance_name, "10"); + const auto deleted_instance_json = fmt::format(deleted_template, deleted_instance_name, "11"); + const auto instances_json = fmt::format("{{{}, {}}}", good_instance_json, deleted_instance_json); + const auto [temp_dir, __] = plant_instance_json(instances_json); + config_builder.data_directory = temp_dir->path(); + config_builder.vault = std::make_unique>(); + + EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { + return std::make_unique(desc.vm_name); + })); + + const auto names_matcher = UnorderedElementsAre(Property(&mp::DetailedInfoItem::name, good_instance_name2), + Property(&mp::DetailedInfoItem::name, deleted_instance_name2)); + + StrictMock> mock_server{}; + mp::InfoReply info_reply; + + EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&info_reply), Return(true))); + + mp::Daemon daemon{config_builder.build()}; + + call_daemon_slot(daemon, &mp::Daemon::info, mp::InfoRequest{}, mock_server); + + EXPECT_THAT(info_reply.details().details(), names_matcher); +} } // namespace From eee02993e18d180035e8d0a04bdc5ecf9094dcee Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Apr 2023 22:45:34 -0700 Subject: [PATCH 123/627] [tests] remove debug code --- tests/test_daemon.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 8fe540d70f7..f2b61758f45 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -2164,7 +2164,6 @@ TEST_F(Daemon, launch_fails_with_incompatible_blueprint) TEST_F(Daemon, info_all_returns_all_instances) { const std::string good_instance_name{"good-instance"}, deleted_instance_name{"deleted-instance"}; - const std::string good_instance_name2{"good-instance"}, deleted_instance_name2{"deleted-instance"}; const auto good_instance_json = fmt::format(valid_template, good_instance_name, "10"); const auto deleted_instance_json = fmt::format(deleted_template, deleted_instance_name, "11"); const auto instances_json = fmt::format("{{{}, {}}}", good_instance_json, deleted_instance_json); @@ -2176,8 +2175,8 @@ TEST_F(Daemon, info_all_returns_all_instances) return std::make_unique(desc.vm_name); })); - const auto names_matcher = UnorderedElementsAre(Property(&mp::DetailedInfoItem::name, good_instance_name2), - Property(&mp::DetailedInfoItem::name, deleted_instance_name2)); + const auto names_matcher = UnorderedElementsAre(Property(&mp::DetailedInfoItem::name, good_instance_name), + Property(&mp::DetailedInfoItem::name, deleted_instance_name)); StrictMock> mock_server{}; mp::InfoReply info_reply; From 0dba20cb877a6675ff575490a8e7178e23e32e24 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 12 Apr 2023 08:20:34 -0700 Subject: [PATCH 124/627] [rebase] realign with parent branch --- src/client/cli/cmd/exec.cpp | 2 +- src/client/cli/cmd/info.cpp | 6 +++--- src/client/cli/formatter/csv_formatter.cpp | 2 +- src/client/cli/formatter/json_formatter.cpp | 2 +- src/client/cli/formatter/table_formatter.cpp | 4 ++-- src/client/cli/formatter/yaml_formatter.cpp | 2 +- src/daemon/daemon.cpp | 2 +- tests/test_cli_client.cpp | 2 +- tests/test_daemon.cpp | 2 +- tests/test_output_formatter.cpp | 8 ++++---- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/client/cli/cmd/exec.cpp b/src/client/cli/cmd/exec.cpp index 4245da6dcd4..9dc8fc9c46b 100644 --- a/src/client/cli/cmd/exec.cpp +++ b/src/client/cli/cmd/exec.cpp @@ -78,7 +78,7 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser) QStringList split_exec_dir = clean_exec_dir.split('/'); auto on_info_success = [&work_dir, &split_exec_dir](mp::InfoReply& reply) { - for (const auto& mount : reply.details().details(0).mount_info().mount_paths()) + for (const auto& mount : reply.detailed_report().details(0).mount_info().mount_paths()) { auto source_dir = QDir(QString::fromStdString(mount.source_path())); auto clean_source_dir = QDir::cleanPath(source_dir.absolutePath()); diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index c89487f9893..778d6ce5708 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -26,15 +26,15 @@ namespace cmd = multipass::cmd; namespace { -std::vector add_instance_and_snapshot_names(const mp::ArgParser* parser) +std::vector add_instance_and_snapshot_names(const mp::ArgParser* parser) { - std::vector instance_snapshot_names; + std::vector instance_snapshot_names; for (const auto& arg : parser->positionalArguments()) { const auto tokens = arg.split('.'); - mp::InstanceOrSnapshot inst_snap_name; + mp::InstanceSnapshotPair inst_snap_name; inst_snap_name.set_instance_name(tokens[0].toStdString()); if (tokens.size() > 1) inst_snap_name.set_snapshot_name(tokens[1].toStdString()); diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index f9bbfde0012..1d51a50d301 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -55,7 +55,7 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory " "total,Mounts,AllIPv4,CPU(s),Snapshots\n"); - for (const auto& info : format::sorted(reply.details().details())) + for (const auto& info : format::sorted(reply.detailed_report().details())) { const auto& instance_details = info.instance_info(); diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 36b68b49cc3..b86097c141f 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -64,7 +64,7 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const info_json.insert("errors", QJsonArray()); - for (const auto& info : reply.details().details()) + for (const auto& info : reply.detailed_report().details()) { const auto& instance_details = info.instance_info(); diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index cd7589afe80..17990e32d3f 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -58,7 +58,7 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const { fmt::memory_buffer buf; - for (const auto& info : format::sorted(reply.details().details())) + for (const auto& info : format::sorted(reply.detailed_report().details())) { const auto& instance_details = info.instance_info(); @@ -146,7 +146,7 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const } auto output = fmt::to_string(buf); - if (!reply.details().details().empty()) + if (!reply.detailed_report().details().empty()) output.pop_back(); else output = "\n"; diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 2404d537fd1..f27efc435d0 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -65,7 +65,7 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const info_node["errors"].push_back(YAML::Null); - for (const auto& info : format::sorted(reply.details().details())) + for (const auto& info : format::sorted(reply.detailed_report().details())) { const auto& instance_details = info.instance_info(); YAML::Node instance_node; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9795f7e0722..d6de33cd021 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1558,7 +1558,7 @@ try // clang-format on bool deleted = false; auto fetch_info = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; - auto info = response.mutable_details()->add_details(); + auto info = response.mutable_detailed_report()->add_details(); auto instance_info = info->mutable_instance_info(); auto present_state = vm.current_state(); info->set_name(name); diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 84572eeb8e6..225a830cfe1 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -406,7 +406,7 @@ auto make_info_function(const std::string& source_path = "", const std::string& if (request.instances_snapshots(0).instance_name() == "primary") { - auto vm_info = info_reply.mutable_details()->add_details(); + auto vm_info = info_reply.mutable_detailed_report()->add_details(); vm_info->set_name("primary"); vm_info->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index f2b61758f45..0fdd258658a 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -2187,6 +2187,6 @@ TEST_F(Daemon, info_all_returns_all_instances) call_daemon_slot(daemon, &mp::Daemon::info, mp::InfoRequest{}, mock_server); - EXPECT_THAT(info_reply.details().details(), names_matcher); + EXPECT_THAT(info_reply.detailed_report().details(), names_matcher); } } // namespace diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 9c77fd5c916..2030d485a04 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -152,7 +152,7 @@ auto construct_single_instance_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_details()->add_details(); + auto info_entry = info_reply.mutable_detailed_report()->add_details(); info_entry->set_name("foo"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); @@ -205,7 +205,7 @@ auto construct_multiple_instances_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_details()->add_details(); + auto info_entry = info_reply.mutable_detailed_report()->add_details(); info_entry->set_name("bogus-instance"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); @@ -236,7 +236,7 @@ auto construct_multiple_instances_info_reply() info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); info_entry->mutable_instance_info()->set_num_snapshots(1); - info_entry = info_reply.mutable_details()->add_details(); + info_entry = info_reply.mutable_detailed_report()->add_details(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); @@ -248,7 +248,7 @@ auto construct_multiple_instances_info_reply() auto add_petenv_to_reply(mp::InfoReply& reply) { - auto entry = reply.mutable_details()->add_details(); + auto entry = reply.mutable_detailed_report()->add_details(); entry->set_name(petenv_name()); entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); entry->mutable_instance_info()->set_image_release("18.10"); From e0edef2e5ad097cbc653b6d58e9ca713f9da8075 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 12 Apr 2023 12:15:14 -0700 Subject: [PATCH 125/627] review changes --- src/client/cli/cmd/info.cpp | 11 +++++------ src/daemon/daemon.cpp | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 778d6ce5708..b4bde185873 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -26,18 +26,17 @@ namespace cmd = multipass::cmd; namespace { +// TODO@snapshots move this to common_cli once required by other commands std::vector add_instance_and_snapshot_names(const mp::ArgParser* parser) { - std::vector instance_snapshot_names; + std::vector instance_snapshot_names(parser->positionalArguments().count()); for (const auto& arg : parser->positionalArguments()) { - const auto tokens = arg.split('.'); - mp::InstanceSnapshotPair inst_snap_name; - inst_snap_name.set_instance_name(tokens[0].toStdString()); - if (tokens.size() > 1) - inst_snap_name.set_snapshot_name(tokens[1].toStdString()); + inst_snap_name.set_instance_name(arg.left(arg.indexOf('.')).toStdString()); + if (arg.indexOf('.') >= 0) + inst_snap_name.set_snapshot_name(arg.right(arg.length() - arg.indexOf('.') - 1).toStdString()); instance_snapshot_names.push_back(inst_snap_name); } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d6de33cd021..ce44e932220 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1660,6 +1660,7 @@ try // clang-format on return grpc::Status::OK; }; + // TODO@snapshots retrieve snapshot names to gather info mp::InstanceNames instance_names; for (const auto& n : request->instances_snapshots()) instance_names.add_instance_name(n.instance_name()); From 5fd7ab9779a551e7347a2132f2a3e5e26fb55086 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 13 Apr 2023 07:30:46 -0700 Subject: [PATCH 126/627] review edits --- src/client/cli/cmd/info.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index b4bde185873..ae5b17e1c10 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -29,14 +29,16 @@ namespace // TODO@snapshots move this to common_cli once required by other commands std::vector add_instance_and_snapshot_names(const mp::ArgParser* parser) { - std::vector instance_snapshot_names(parser->positionalArguments().count()); + std::vector instance_snapshot_names; + instance_snapshot_names.reserve(parser->positionalArguments().count()); for (const auto& arg : parser->positionalArguments()) { mp::InstanceSnapshotPair inst_snap_name; - inst_snap_name.set_instance_name(arg.left(arg.indexOf('.')).toStdString()); - if (arg.indexOf('.') >= 0) - inst_snap_name.set_snapshot_name(arg.right(arg.length() - arg.indexOf('.') - 1).toStdString()); + auto index = arg.indexOf('.'); + inst_snap_name.set_instance_name(arg.left(index).toStdString()); + if (index >= 0) + inst_snap_name.set_snapshot_name(arg.right(arg.length() - index - 1).toStdString()); instance_snapshot_names.push_back(inst_snap_name); } From 0024cd2260a3850afd1a821b3ceb1fe5a42245fe Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 13 Apr 2023 09:46:49 -0700 Subject: [PATCH 127/627] [daemon] remove unneeded failing stl fcn call --- src/daemon/daemon.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index ce44e932220..9bc5b193e0e 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2427,9 +2427,6 @@ try server}; { // TODO@snapshots replace placeholder implementation - - sleep(3); - mpl::log(mpl::Level::debug, category, "Restore placeholder"); RestoreReply reply; From 51db20f4a16145104c4b50372a8ea54810883478 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 18 Apr 2023 11:03:38 -0700 Subject: [PATCH 128/627] [formatters] reposition snapshot count --- src/client/cli/formatter/table_formatter.cpp | 3 +-- src/client/cli/formatter/yaml_formatter.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 17990e32d3f..f7de9115317 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -65,6 +65,7 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "State:", mp::format::status_string_for(info.instance_status())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); int ipv4_size = instance_details.ipv4_size(); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); @@ -140,8 +141,6 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const } } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); - fmt::format_to(std::back_inserter(buf), "\n"); } diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index f27efc435d0..1d5de3d88b0 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -71,6 +71,7 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const YAML::Node instance_node; instance_node["state"] = mp::format::status_string_for(info.instance_status()); + instance_node["snapshots"] = instance_details.num_snapshots(); instance_node["image_hash"] = instance_details.id(); instance_node["image_release"] = instance_details.image_release(); if (instance_details.current_release().empty()) @@ -148,7 +149,6 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const mounts[mount.target_path()] = mount_node; } instance_node["mounts"] = mounts; - instance_node["snapshots"] = instance_details.num_snapshots(); info_node[info.name()].push_back(instance_node); } From 6052f9653ecea8454ed175d20bc9c9af2878c4ae Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 18 Apr 2023 11:04:09 -0700 Subject: [PATCH 129/627] [tests] realign tests --- tests/test_output_formatter.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 2030d485a04..713bcfd15cb 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -513,6 +513,7 @@ const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &single_instance_info_reply, "Name: foo\n" "State: Running\n" + "Snapshots: 0\n" "IPv4: 10.168.32.2\n" " 200.3.123.29\n" "IPv6: 2001:67c:1562:8007::aac:423a\n" @@ -528,12 +529,12 @@ const std::vector orderable_list_info_formatter_outputs{ " GID map: 1000:1000\n" " /home/user/test_dir => test_dir\n" " UID map: 1000:1000\n" - " GID map: 1000:1000\n" - "Snapshots: 0\n", + " GID map: 1000:1000\n", "table_info_single"}, {&table_formatter, &multiple_instances_info_reply, "Name: bogus-instance\n" "State: Running\n" + "Snapshots: 1\n" "IPv4: 10.21.124.56\n" "Release: Ubuntu 16.04.3 LTS\n" "Image hash: 1797c5c82016 (Ubuntu 16.04 LTS)\n" @@ -543,10 +544,10 @@ const std::vector orderable_list_info_formatter_outputs{ "Memory usage: 37.0MiB out of 1.5GiB\n" "Mounts: /home/user/source => source\n" " UID map: 1000:501\n" - " GID map: 1000:501\n" - "Snapshots: 1\n\n" + " GID map: 1000:501\n\n" "Name: bombastic\n" "State: Stopped\n" + "Snapshots: 3\n" "IPv4: --\n" "Release: --\n" "Image hash: ab5191cc1725 (Ubuntu 18.04 LTS)\n" @@ -554,8 +555,7 @@ const std::vector orderable_list_info_formatter_outputs{ "Load: --\n" "Disk usage: --\n" "Memory usage: --\n" - "Mounts: --\n" - "Snapshots: 3\n", + "Mounts: --\n", "table_info_multiple"}, {&csv_formatter, &empty_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n", "csv_list_empty"}, @@ -647,6 +647,7 @@ const std::vector orderable_list_info_formatter_outputs{ " - ~\n" "foo:\n" " - state: Running\n" + " snapshots: 0\n" " image_hash: 1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac\n" " image_release: 16.04 LTS\n" " release: Ubuntu 16.04.3 LTS\n" @@ -677,14 +678,14 @@ const std::vector orderable_list_info_formatter_outputs{ " - \"1000:1000\"\n" " gid_mappings:\n" " - \"1000:1000\"\n" - " source_path: /home/user/test_dir\n" - " snapshots: 0\n", + " source_path: /home/user/test_dir\n", "yaml_info_single"}, {&yaml_formatter, &multiple_instances_info_reply, "errors:\n" " - ~\n" "bogus-instance:\n" " - state: Running\n" + " snapshots: 1\n" " image_hash: 1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac\n" " image_release: 16.04 LTS\n" " release: Ubuntu 16.04.3 LTS\n" @@ -709,9 +710,9 @@ const std::vector orderable_list_info_formatter_outputs{ " gid_mappings:\n" " - \"1000:501\"\n" " source_path: /home/user/source\n" - " snapshots: 1\n" "bombastic:\n" " - state: Stopped\n" + " snapshots: 3\n" " image_hash: ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\n" " image_release: 18.04 LTS\n" " release: ~\n" @@ -725,8 +726,7 @@ const std::vector orderable_list_info_formatter_outputs{ " total: ~\n" " ipv4:\n" " []\n" - " mounts: ~\n" - " snapshots: 3\n", + " mounts: ~\n", "yaml_info_multiple"}}; const std::vector non_orderable_list_info_formatter_outputs{ From 827d943e1e68e7e3fad7b68fe71b30ee831598df Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Apr 2023 08:41:46 -0700 Subject: [PATCH 130/627] [cli] add snapshot-overview option --- src/client/cli/cmd/info.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index ae5b17e1c10..2e672526e6b 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -84,18 +84,16 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) parser->addPositionalArgument("name", "Names of instances to display information about", " [ ...]"); QCommandLineOption all_option(all_option_name, "Display info for all instances"); - parser->addOption(all_option); - QCommandLineOption noRuntimeInfoOption( "no-runtime-information", "Retrieve from the daemon only the information obtained without running commands on the instance"); noRuntimeInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); - parser->addOption(noRuntimeInfoOption); - QCommandLineOption formatOption( "format", "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", "format", "table"); - parser->addOption(formatOption); + QCommandLineOption snapshotOverviewOption("snapshot-overview", "Display info on snapshots"); + + parser->addOptions({all_option, noRuntimeInfoOption, formatOption, snapshotOverviewOption}); auto status = parser->commandParse(this); @@ -111,6 +109,7 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) for (const auto& item : add_instance_and_snapshot_names(parser)) request.add_instances_snapshots()->CopyFrom(item); request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); + request.set_snapshot_overview(parser->isSet(snapshotOverviewOption)); status = handle_format_option(parser, &chosen_formatter, cerr); From 1e55deb01298179216b6f69905f142dc152632ff Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Apr 2023 08:42:52 -0700 Subject: [PATCH 131/627] [daemon] populate info with placeholder info --- src/daemon/daemon.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9bc5b193e0e..5607d286281 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1556,7 +1556,7 @@ try // clang-format on InfoReply response; bool have_mounts = false; bool deleted = false; - auto fetch_info = [&](VirtualMachine& vm) { + auto fetch_instance_info = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; auto info = response.mutable_detailed_report()->add_details(); auto instance_info = info->mutable_instance_info(); @@ -1661,6 +1661,18 @@ try // clang-format on }; // TODO@snapshots retrieve snapshot names to gather info + auto fetch_snapshot_overview = [&](VirtualMachine& vm) { + const auto& name = vm.vm_name; + auto overview = response.mutable_snapshot_overview()->add_overview(); + auto fundamentals = overview->mutable_fundamentals(); + + overview->set_instance_name(name); + fundamentals->set_snapshot_name("snapshot1"); + fundamentals->set_comment("This is a sample comment"); + + return grpc::Status::OK; + }; + mp::InstanceNames instance_names; for (const auto& n : request->instances_snapshots()) instance_names.add_instance_name(n.instance_name()); @@ -1671,9 +1683,17 @@ try // clang-format on if (status.ok()) { - cmd_vms(instance_selection.operative_selection, fetch_info); + // TODO@snapshots change cmd logic after all info logic paths are added + auto cmd = [&](VirtualMachine& vm) { + if (request->snapshot_overview()) + return fetch_snapshot_overview(vm); + else + return fetch_instance_info(vm); + }; + + cmd_vms(instance_selection.operative_selection, cmd); deleted = true; - cmd_vms(instance_selection.deleted_selection, fetch_info); + cmd_vms(instance_selection.deleted_selection, cmd); if (have_mounts && !MP_SETTINGS.get_as(mp::mounts_key)) mpl::log(mpl::Level::error, category, "Mounts have been disabled on this instance of Multipass"); From 6852474d6cf1228bdab60ea49dbe7a12a52ebe1f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Apr 2023 08:43:37 -0700 Subject: [PATCH 132/627] [cli] add snapshot-overview table --- src/client/cli/formatter/table_formatter.cpp | 193 +++++++++++-------- 1 file changed, 117 insertions(+), 76 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index f7de9115317..340bafa5e20 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -57,98 +57,139 @@ std::string to_usage(const std::string& usage, const std::string& total) std::string mp::TableFormatter::format(const InfoReply& reply) const { fmt::memory_buffer buf; + std::string output; - for (const auto& info : format::sorted(reply.detailed_report().details())) + if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) { - const auto& instance_details = info.instance_info(); - - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "State:", mp::format::status_string_for(info.instance_status())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); - - int ipv4_size = instance_details.ipv4_size(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); - - for (int i = 1; i < ipv4_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); - - if (int ipv6_size = instance_details.ipv6_size()) + for (const auto& info : format::sorted(reply.detailed_report().details())) { - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); + const auto& instance_details = info.instance_info(); - for (int i = 1; i < ipv6_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); - } + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "State:", mp::format::status_string_for(info.instance_status())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Release:", - instance_details.current_release().empty() ? "--" : instance_details.current_release()); - fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); - if (instance_details.id().empty()) - fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); - else - fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), - !instance_details.image_release().empty() - ? fmt::format(" (Ubuntu {})", instance_details.image_release()) - : ""); - - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "CPU(s):", info.cpu_count().empty() ? "--" : info.cpu_count()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Load:", instance_details.load().empty() ? "--" : instance_details.load()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Disk usage:", to_usage(instance_details.disk_usage(), info.disk_total())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Memory usage:", to_usage(instance_details.memory_usage(), info.memory_total())); - - auto mount_paths = info.mount_info().mount_paths(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); - - for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) - { - if (mount != mount_paths.cbegin()) - fmt::format_to(std::back_inserter(buf), "{:<16}", ""); - fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), - info.mount_info().longest_path_len(), mount->target_path()); + int ipv4_size = instance_details.ipv4_size(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); - auto mount_maps = mount->mount_maps(); - auto uid_mappings_size = mount_maps.uid_mappings_size(); + for (int i = 1; i < ipv4_size; ++i) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); - for (auto i = 0; i < uid_mappings_size; ++i) + if (int ipv6_size = instance_details.ipv6_size()) { - auto uid_map_pair = mount_maps.uid_mappings(i); - auto host_uid = uid_map_pair.host_id(); - auto instance_uid = uid_map_pair.instance_id(); - - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", (i == 0) ? 29 : 0, - std::to_string(host_uid), - (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), - (i == uid_mappings_size - 1) ? "\n" : ", "); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); + + for (int i = 1; i < ipv6_size; ++i) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); } - for (auto gid_mapping = mount_maps.gid_mappings().cbegin(); gid_mapping != mount_maps.gid_mappings().cend(); - ++gid_mapping) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Release:", + instance_details.current_release().empty() ? "--" : instance_details.current_release()); + fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); + if (instance_details.id().empty()) + fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); + else + fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), + !instance_details.image_release().empty() + ? fmt::format(" (Ubuntu {})", instance_details.image_release()) + : ""); + + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "CPU(s):", info.cpu_count().empty() ? "--" : info.cpu_count()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Load:", instance_details.load().empty() ? "--" : instance_details.load()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Disk usage:", to_usage(instance_details.disk_usage(), info.disk_total())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Memory usage:", to_usage(instance_details.memory_usage(), info.memory_total())); + + auto mount_paths = info.mount_info().mount_paths(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); + + for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) { - auto host_gid = gid_mapping->host_id(); - auto instance_gid = gid_mapping->instance_id(); - - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, std::to_string(host_gid), - (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), - (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", - (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); + if (mount != mount_paths.cbegin()) + fmt::format_to(std::back_inserter(buf), "{:<16}", ""); + fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), + info.mount_info().longest_path_len(), mount->target_path()); + + auto mount_maps = mount->mount_maps(); + auto uid_mappings_size = mount_maps.uid_mappings_size(); + + for (auto i = 0; i < uid_mappings_size; ++i) + { + auto uid_map_pair = mount_maps.uid_mappings(i); + auto host_uid = uid_map_pair.host_id(); + auto instance_uid = uid_map_pair.instance_id(); + + fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", + (i == 0) ? 29 : 0, std::to_string(host_uid), + (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), + (i == uid_mappings_size - 1) ? "\n" : ", "); + } + + for (auto gid_mapping = mount_maps.gid_mappings().cbegin(); + gid_mapping != mount_maps.gid_mappings().cend(); ++gid_mapping) + { + auto host_gid = gid_mapping->host_id(); + auto instance_gid = gid_mapping->instance_id(); + + fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, + std::to_string(host_gid), + (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), + (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", + (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); + } } + + fmt::format_to(std::back_inserter(buf), "\n"); } - fmt::format_to(std::back_inserter(buf), "\n"); + output = fmt::to_string(buf); + if (!reply.detailed_report().details().empty()) + output.pop_back(); + else + output = "\n"; } + else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + { + const auto overview = reply.snapshot_overview().overview(); + const auto name_column_width = mp::format::column_width( + overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, + 24); + const auto snapshot_column_width = mp::format::column_width( + overview.begin(), overview.end(), + [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, 12); + const auto parent_column_width = mp::format::column_width( + overview.begin(), overview.end(), + [](const auto& item) -> int { return item.fundamentals().parent().length(); }, 12); + const auto comment_column_width = 50; + + const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; + + if (overview.empty()) + return "No snapshots found.\n"; + else + fmt::format_to(std::back_inserter(buf), row_format, "Instance", name_column_width, "Snapshot", + snapshot_column_width, "Parent", parent_column_width, "Comment"); - auto output = fmt::to_string(buf); - if (!reply.detailed_report().details().empty()) - output.pop_back(); - else - output = "\n"; + for (const auto& item : overview) + { + auto snapshot = item.fundamentals(); + fmt::format_to(std::back_inserter(buf), row_format, item.instance_name(), name_column_width, + snapshot.snapshot_name(), snapshot_column_width, + snapshot.parent().empty() ? "--" : snapshot.parent(), parent_column_width, + snapshot.comment().empty() ? "--" + : snapshot.comment().length() >= comment_column_width + ? fmt::format("{}…", snapshot.comment().substr(0, comment_column_width - 1)) + : snapshot.comment()); + } + + output = fmt::to_string(buf); + } return output; } From fb2dec296c2ba5113100e7db0f9e5af63f7aa9f9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Apr 2023 08:44:59 -0700 Subject: [PATCH 133/627] [utils] generalize column width util --- include/multipass/cli/format_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 38f4877f4ce..90a1fe5111c 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -45,7 +45,7 @@ void filter_aliases(google::protobuf::RepeatedPtrField Date: Tue, 4 Apr 2023 08:50:34 -0700 Subject: [PATCH 134/627] [tests] update column spacing --- tests/test_output_formatter.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 713bcfd15cb..883c46db8dd 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -904,17 +904,17 @@ const std::vector non_orderable_list_info_formatter_outputs{ const std::vector non_orderable_networks_formatter_outputs{ {&table_formatter, &empty_networks_reply, "No network interfaces found.\n", "table_networks_empty"}, {&table_formatter, &one_short_line_networks_reply, - "Name Type Description\n" - "en0 eth Ether\n", + "Name Type Description\n" + "en0 eth Ether\n", "table_networks_one_short_line"}, {&table_formatter, &one_long_line_networks_reply, - "Name Type Description\n" - "enp3s0 ethernet Amazingly fast and robust ethernet adapter\n", + "Name Type Description\n" + "enp3s0 ethernet Amazingly fast and robust ethernet adapter\n", "table_networks_one_long_line"}, {&table_formatter, &multiple_lines_networks_reply, - "Name Type Description\n" - "en0 eth Ether\n" - "wlx0123456789ab wifi Wireless\n", + "Name Type Description\n" + "en0 eth Ether\n" + "wlx0123456789ab wifi Wireless\n", "table_networks_multiple_lines"}, {&csv_formatter, &empty_networks_reply, "Name,Type,Description\n", "csv_networks_empty"}, From 7b4ee6412d45bebcd085aadfe3b57c22ef06aae1 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 4 Apr 2023 08:51:06 -0700 Subject: [PATCH 135/627] [cli] sort by timestamp --- src/client/cli/formatter/table_formatter.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 340bafa5e20..b1154f244e2 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -22,7 +22,10 @@ #include #include +#include + namespace mp = multipass; +namespace gpu = google::protobuf::util; namespace { @@ -156,7 +159,7 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const } else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) { - const auto overview = reply.snapshot_overview().overview(); + auto overview = reply.snapshot_overview().overview(); const auto name_column_width = mp::format::column_width( overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, 24); @@ -176,6 +179,11 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const fmt::format_to(std::back_inserter(buf), row_format, "Instance", name_column_width, "Snapshot", snapshot_column_width, "Parent", parent_column_width, "Comment"); + std::sort(std::begin(overview), std::end(overview), [](const auto& a, const auto& b) { + return gpu::TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < + gpu::TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); + }); + for (const auto& item : overview) { auto snapshot = item.fundamentals(); @@ -190,6 +198,10 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const output = fmt::to_string(buf); } + else + { + output = "\n"; + } return output; } From 7df51d7a1c8e4c7a6ae5b6ae4922776e9eb0b209 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 17 Apr 2023 07:27:36 -0700 Subject: [PATCH 136/627] [cli] include snapshots in cli arg desc --- src/client/cli/cmd/info.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 2e672526e6b..b4cb504d001 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -71,17 +71,18 @@ std::string cmd::Info::name() const { return "info"; } QString cmd::Info::short_help() const { - return QStringLiteral("Display information about instances"); + return QStringLiteral("Display information about instances or snapshots"); } QString cmd::Info::description() const { - return QStringLiteral("Display information about instances"); + return QStringLiteral("Display information about instances or snapshots"); } mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) { - parser->addPositionalArgument("name", "Names of instances to display information about", " [ ...]"); + parser->addPositionalArgument("instance", "Names of instances or snapshots to display information about", + "[.snapshot] [[.snapshot] ...]"); QCommandLineOption all_option(all_option_name, "Display info for all instances"); QCommandLineOption noRuntimeInfoOption( From 9996846ba7a8352a4085172a3ee0b876bf4421c2 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 5 May 2023 10:53:13 +0200 Subject: [PATCH 137/627] review changes --- src/client/cli/formatter/table_formatter.cpp | 253 ++++++++++--------- src/daemon/daemon.cpp | 8 +- 2 files changed, 131 insertions(+), 130 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index b1154f244e2..a64bc9bd908 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -25,7 +25,6 @@ #include namespace mp = multipass; -namespace gpu = google::protobuf::util; namespace { @@ -56,152 +55,158 @@ std::string to_usage(const std::string& usage, const std::string& total) return fmt::format("{} out of {}", mp::MemorySize{usage}.human_readable(), mp::MemorySize{total}.human_readable()); } -} // namespace -std::string mp::TableFormatter::format(const InfoReply& reply) const +std::string generate_instance_info_report(const mp::InfoReply& reply) { fmt::memory_buffer buf; - std::string output; - if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) + for (const auto& info : mp::format::sorted(reply.detailed_report().details())) { - for (const auto& info : format::sorted(reply.detailed_report().details())) + const auto& instance_details = info.instance_info(); + + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "State:", mp::format::status_string_for(info.instance_status())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); + + int ipv4_size = instance_details.ipv4_size(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); + + for (int i = 1; i < ipv4_size; ++i) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); + + if (int ipv6_size = instance_details.ipv6_size()) { - const auto& instance_details = info.instance_info(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "State:", mp::format::status_string_for(info.instance_status())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); + for (int i = 1; i < ipv6_size; ++i) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); + } - int ipv4_size = instance_details.ipv4_size(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Release:", + instance_details.current_release().empty() ? "--" : instance_details.current_release()); + fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); + if (instance_details.id().empty()) + fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); + else + fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), + !instance_details.image_release().empty() + ? fmt::format(" (Ubuntu {})", instance_details.image_release()) + : ""); + + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "CPU(s):", info.cpu_count().empty() ? "--" : info.cpu_count()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Load:", instance_details.load().empty() ? "--" : instance_details.load()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Disk usage:", to_usage(instance_details.disk_usage(), info.disk_total())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Memory usage:", to_usage(instance_details.memory_usage(), info.memory_total())); + + auto mount_paths = info.mount_info().mount_paths(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); + + for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) + { + if (mount != mount_paths.cbegin()) + fmt::format_to(std::back_inserter(buf), "{:<16}", ""); + fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), + info.mount_info().longest_path_len(), mount->target_path()); - for (int i = 1; i < ipv4_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); + auto mount_maps = mount->mount_maps(); + auto uid_mappings_size = mount_maps.uid_mappings_size(); - if (int ipv6_size = instance_details.ipv6_size()) + for (auto i = 0; i < uid_mappings_size; ++i) { - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); - - for (int i = 1; i < ipv6_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); + auto uid_map_pair = mount_maps.uid_mappings(i); + auto host_uid = uid_map_pair.host_id(); + auto instance_uid = uid_map_pair.instance_id(); + + fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", (i == 0) ? 29 : 0, + std::to_string(host_uid), + (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), + (i == uid_mappings_size - 1) ? "\n" : ", "); } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Release:", - instance_details.current_release().empty() ? "--" : instance_details.current_release()); - fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); - if (instance_details.id().empty()) - fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); - else - fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), - !instance_details.image_release().empty() - ? fmt::format(" (Ubuntu {})", instance_details.image_release()) - : ""); - - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "CPU(s):", info.cpu_count().empty() ? "--" : info.cpu_count()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Load:", instance_details.load().empty() ? "--" : instance_details.load()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Disk usage:", to_usage(instance_details.disk_usage(), info.disk_total())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Memory usage:", to_usage(instance_details.memory_usage(), info.memory_total())); - - auto mount_paths = info.mount_info().mount_paths(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); - - for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) + for (auto gid_mapping = mount_maps.gid_mappings().cbegin(); gid_mapping != mount_maps.gid_mappings().cend(); + ++gid_mapping) { - if (mount != mount_paths.cbegin()) - fmt::format_to(std::back_inserter(buf), "{:<16}", ""); - fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), - info.mount_info().longest_path_len(), mount->target_path()); - - auto mount_maps = mount->mount_maps(); - auto uid_mappings_size = mount_maps.uid_mappings_size(); - - for (auto i = 0; i < uid_mappings_size; ++i) - { - auto uid_map_pair = mount_maps.uid_mappings(i); - auto host_uid = uid_map_pair.host_id(); - auto instance_uid = uid_map_pair.instance_id(); - - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", - (i == 0) ? 29 : 0, std::to_string(host_uid), - (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), - (i == uid_mappings_size - 1) ? "\n" : ", "); - } - - for (auto gid_mapping = mount_maps.gid_mappings().cbegin(); - gid_mapping != mount_maps.gid_mappings().cend(); ++gid_mapping) - { - auto host_gid = gid_mapping->host_id(); - auto instance_gid = gid_mapping->instance_id(); - - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, - std::to_string(host_gid), - (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), - (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", - (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); - } + auto host_gid = gid_mapping->host_id(); + auto instance_gid = gid_mapping->instance_id(); + + fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, std::to_string(host_gid), + (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), + (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", + (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); } - - fmt::format_to(std::back_inserter(buf), "\n"); } - output = fmt::to_string(buf); - if (!reply.detailed_report().details().empty()) - output.pop_back(); - else - output = "\n"; + fmt::format_to(std::back_inserter(buf), "\n"); } - else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) - { - auto overview = reply.snapshot_overview().overview(); - const auto name_column_width = mp::format::column_width( - overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, - 24); - const auto snapshot_column_width = mp::format::column_width( - overview.begin(), overview.end(), - [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, 12); - const auto parent_column_width = mp::format::column_width( - overview.begin(), overview.end(), - [](const auto& item) -> int { return item.fundamentals().parent().length(); }, 12); - const auto comment_column_width = 50; - - const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; - - if (overview.empty()) - return "No snapshots found.\n"; - else - fmt::format_to(std::back_inserter(buf), row_format, "Instance", name_column_width, "Snapshot", - snapshot_column_width, "Parent", parent_column_width, "Comment"); - std::sort(std::begin(overview), std::end(overview), [](const auto& a, const auto& b) { - return gpu::TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < - gpu::TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); - }); + std::string output = fmt::to_string(buf); + if (!reply.detailed_report().details().empty()) + output.pop_back(); + else + output = "\n"; - for (const auto& item : overview) - { - auto snapshot = item.fundamentals(); - fmt::format_to(std::back_inserter(buf), row_format, item.instance_name(), name_column_width, - snapshot.snapshot_name(), snapshot_column_width, - snapshot.parent().empty() ? "--" : snapshot.parent(), parent_column_width, - snapshot.comment().empty() ? "--" - : snapshot.comment().length() >= comment_column_width - ? fmt::format("{}…", snapshot.comment().substr(0, comment_column_width - 1)) - : snapshot.comment()); - } + return output; +} - output = fmt::to_string(buf); +std::string generate_snapshot_overview_report(const mp::InfoReply& reply) +{ + auto overview = reply.snapshot_overview().overview(); + if (overview.empty()) + return "No snapshots found.\n"; + + fmt::memory_buffer buf; + const auto name_column_width = mp::format::column_width( + overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, 12); + const auto snapshot_column_width = mp::format::column_width( + overview.begin(), overview.end(), + [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, 12); + const auto parent_column_width = mp::format::column_width( + overview.begin(), overview.end(), [](const auto& item) -> int { return item.fundamentals().parent().length(); }, + 12); + const auto max_comment_column_width = 50; + + const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; + + fmt::format_to(std::back_inserter(buf), row_format, "Instance", name_column_width, "Snapshot", + snapshot_column_width, "Parent", parent_column_width, "Comment"); + + std::sort(std::begin(overview), std::end(overview), [](const auto& a, const auto& b) { + return google::protobuf::util::TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < + google::protobuf::util::TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); + }); + + for (const auto& item : overview) + { + auto snapshot = item.fundamentals(); + fmt::format_to(std::back_inserter(buf), row_format, item.instance_name(), name_column_width, + snapshot.snapshot_name(), snapshot_column_width, + snapshot.parent().empty() ? "--" : snapshot.parent(), parent_column_width, + snapshot.comment().empty() ? "--" + : snapshot.comment().length() > max_comment_column_width + ? fmt::format("{}…", snapshot.comment().substr(0, max_comment_column_width - 1)) + : snapshot.comment()); } + + return fmt::to_string(buf); +} +} // namespace + +std::string mp::TableFormatter::format(const InfoReply& reply) const +{ + std::string output; + + if (reply.has_detailed_report()) + output = generate_instance_info_report(reply); + else if (reply.has_snapshot_overview()) + output = generate_snapshot_overview_report(reply); else - { output = "\n"; - } return output; } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 5607d286281..a7835b3abdc 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1684,12 +1684,8 @@ try // clang-format on if (status.ok()) { // TODO@snapshots change cmd logic after all info logic paths are added - auto cmd = [&](VirtualMachine& vm) { - if (request->snapshot_overview()) - return fetch_snapshot_overview(vm); - else - return fetch_instance_info(vm); - }; + auto cmd = + request->snapshot_overview() ? std::function(fetch_snapshot_overview) : std::function(fetch_instance_info); cmd_vms(instance_selection.operative_selection, cmd); deleted = true; From 5da288163be53996429519ac76706b2a06956a5b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 5 May 2023 12:07:08 +0200 Subject: [PATCH 138/627] [formatter] add assertion and realign tests --- src/client/cli/formatter/table_formatter.cpp | 9 +++-- tests/test_cli_client.cpp | 35 +++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index a64bc9bd908..81a7423fe7d 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -202,11 +202,14 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const std::string output; if (reply.has_detailed_report()) + { output = generate_instance_info_report(reply); - else if (reply.has_snapshot_overview()) - output = generate_snapshot_overview_report(reply); + } else - output = "\n"; + { + assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); + output = generate_snapshot_overview_report(reply); + } return output; } diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 225a830cfe1..cde384a79a5 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -343,6 +343,17 @@ struct Client : public Test }; } + auto make_info_instance_details_request() + { + return [](Unused, grpc::ServerReaderWriter* server) { + mp::InfoReply reply; + reply.mutable_detailed_report(); + + server->Write(reply); + return grpc::Status{}; + }; + } + std::string negate_flag_string(const std::string& orig) { auto flag = QVariant{QString::fromStdString(orig)}.toBool(); @@ -350,14 +361,16 @@ struct Client : public Test } template - auto check_request_and_return(const Matcher& matcher, const grpc::Status& status) + auto check_request_and_return(const Matcher& matcher, const grpc::Status& status, + const ReplyType& reply = ReplyType{}) { - return [&matcher, &status](grpc::ServerReaderWriter* server) { + return [&matcher, &status, reply = std::move(reply)](grpc::ServerReaderWriter* server) { RequestType request; server->Read(&request); EXPECT_THAT(request, matcher); + server->Write(std::move(reply)); return status; }; } @@ -1720,13 +1733,13 @@ TEST_F(Client, info_cmd_fails_no_args) TEST_F(Client, info_cmd_ok_with_one_arg) { - EXPECT_CALL(mock_daemon, info(_, _)); + EXPECT_CALL(mock_daemon, info(_, _)).WillOnce(make_info_instance_details_request()); EXPECT_THAT(send_command({"info", "foo"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, info_cmd_succeeds_with_multiple_args) { - EXPECT_CALL(mock_daemon, info(_, _)); + EXPECT_CALL(mock_daemon, info(_, _)).WillOnce(make_info_instance_details_request()); EXPECT_THAT(send_command({"info", "foo", "bar"}), Eq(mp::ReturnCode::Ok)); } @@ -1737,7 +1750,7 @@ TEST_F(Client, info_cmd_help_ok) TEST_F(Client, info_cmd_succeeds_with_all) { - EXPECT_CALL(mock_daemon, info(_, _)); + EXPECT_CALL(mock_daemon, info(_, _)).WillOnce(make_info_instance_details_request()); EXPECT_THAT(send_command({"info", "--all"}), Eq(mp::ReturnCode::Ok)); } @@ -1749,27 +1762,33 @@ TEST_F(Client, info_cmd_fails_with_names_and_all) TEST_F(Client, infoCmdDoesNotDefaultToNoRuntimeInformationAndSucceeds) { const auto info_matcher = Property(&mp::InfoRequest::no_runtime_information, IsFalse()); + mp::InfoReply reply; + reply.mutable_detailed_report(); EXPECT_CALL(mock_daemon, info) - .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok))); + .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok, reply))); EXPECT_THAT(send_command({"info", "name1", "name2"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, infoCmdSucceedsWithInstanceNamesAndNoRuntimeInformation) { const auto info_matcher = Property(&mp::InfoRequest::no_runtime_information, IsTrue()); + mp::InfoReply reply; + reply.mutable_detailed_report(); EXPECT_CALL(mock_daemon, info) - .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok))); + .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok, reply))); EXPECT_THAT(send_command({"info", "name3", "name4", "--no-runtime-information"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, infoCmdSucceedsWithAllAndNoRuntimeInformation) { const auto info_matcher = Property(&mp::InfoRequest::no_runtime_information, IsTrue()); + mp::InfoReply reply; + reply.mutable_detailed_report(); EXPECT_CALL(mock_daemon, info) - .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok))); + .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok, reply))); EXPECT_THAT(send_command({"info", "name5", "--no-runtime-information"}), Eq(mp::ReturnCode::Ok)); } From f68eae431b8412bc6004f2b0b84a6748c43f3ed3 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 15 May 2023 07:46:56 -0700 Subject: [PATCH 139/627] review changes --- src/client/cli/formatter/table_formatter.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 81a7423fe7d..638a853e9bb 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -161,14 +161,16 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) return "No snapshots found.\n"; fmt::memory_buffer buf; + const std::string::size_type name_columns_width = 12; const auto name_column_width = mp::format::column_width( - overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, 12); + overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, + name_columns_width); const auto snapshot_column_width = mp::format::column_width( overview.begin(), overview.end(), - [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, 12); + [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, name_columns_width); const auto parent_column_width = mp::format::column_width( overview.begin(), overview.end(), [](const auto& item) -> int { return item.fundamentals().parent().length(); }, - 12); + name_columns_width); const auto max_comment_column_width = 50; const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; @@ -176,9 +178,10 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) fmt::format_to(std::back_inserter(buf), row_format, "Instance", name_column_width, "Snapshot", snapshot_column_width, "Parent", parent_column_width, "Comment"); + using google::protobuf::util::TimeUtil; std::sort(std::begin(overview), std::end(overview), [](const auto& a, const auto& b) { - return google::protobuf::util::TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < - google::protobuf::util::TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); + return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); }); for (const auto& item : overview) From b3304919a72df0ed57d4425456dbfebf2e7bf96f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 15 May 2023 08:49:17 -0700 Subject: [PATCH 140/627] realign tests --- tests/test_daemon.cpp | 11 +++++++++-- tests/test_output_formatter.cpp | 9 ++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 0fdd258658a..08d017e52e6 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -157,8 +157,15 @@ TEST_F(Daemon, receives_commands_and_calls_corresponding_slot) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); EXPECT_CALL(daemon, ssh_info(_, _, _)) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); - EXPECT_CALL(daemon, info(_, _, _)) - .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); + EXPECT_CALL(daemon, info(_, _, _)).WillOnce([](auto, auto server, auto status_promise) { + mp::InfoReply reply; + reply.mutable_detailed_report(); + + server->Write(reply); + status_promise->set_value(grpc::Status::OK); + + return grpc::Status{}; + }); EXPECT_CALL(daemon, list(_, _, _)) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); EXPECT_CALL(daemon, recover(_, _, _)) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 883c46db8dd..1aadcd2a27f 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -148,6 +148,13 @@ auto construct_multiple_lines_networks_reply() return networks_reply; } +auto construct_empty_info_reply() +{ + mp::InfoReply info_reply; + info_reply.mutable_detailed_report(); + return info_reply; +} + auto construct_single_instance_info_reply() { mp::InfoReply info_reply; @@ -483,7 +490,7 @@ const auto one_short_line_networks_reply = construct_one_short_line_networks_rep const auto one_long_line_networks_reply = construct_one_long_line_networks_reply(); const auto multiple_lines_networks_reply = construct_multiple_lines_networks_reply(); -const auto empty_info_reply = mp::InfoReply(); +const auto empty_info_reply = construct_empty_info_reply(); const auto single_instance_info_reply = construct_single_instance_info_reply(); const auto multiple_instances_info_reply = construct_multiple_instances_info_reply(); From 7b31a112d1bb16eae10d77d103439537f0fce9e2 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 16 May 2023 13:15:37 -0700 Subject: [PATCH 141/627] [formatters] adjust column widths --- include/multipass/cli/format_utils.h | 10 +-- src/client/cli/formatter/table_formatter.cpp | 69 +++++++++++------- tests/test_alias_dict.cpp | 74 +++++++++++++------- tests/test_format_utils.cpp | 11 +-- tests/test_output_formatter.cpp | 4 +- 5 files changed, 105 insertions(+), 63 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 90a1fe5111c..9c8521184f9 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -33,6 +33,8 @@ class Formatter; namespace format { +static constexpr int col_buffer = 3; + std::string status_string_for(const InstanceStatus& status); std::string image_string_for(const multipass::FindReply_AliasInfo& alias); Formatter* formatter_for(const std::string& format); @@ -44,14 +46,14 @@ void filter_aliases(google::protobuf::RepeatedPtrField int { return item.instance_name().length(); }, - name_columns_width); + name_col_header.length()); const auto snapshot_column_width = mp::format::column_width( overview.begin(), overview.end(), - [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, name_columns_width); + [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, + snapshot_col_header.length()); const auto parent_column_width = mp::format::column_width( overview.begin(), overview.end(), [](const auto& item) -> int { return item.fundamentals().parent().length(); }, - name_columns_width); + parent_col_header.length()); const auto max_comment_column_width = 50; const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, "Instance", name_column_width, "Snapshot", - snapshot_column_width, "Parent", parent_column_width, "Comment"); + fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, snapshot_col_header, + snapshot_column_width, parent_col_header, parent_column_width, comment_col_header); using google::protobuf::util::TimeUtil; std::sort(std::begin(overview), std::end(overview), [](const auto& a, const auto& b) { @@ -226,14 +228,16 @@ std::string mp::TableFormatter::format(const ListReply& reply) const if (instances.empty()) return "No instances found.\n"; + const std::string name_col_header = "Name"; const auto name_column_width = mp::format::column_width( - instances.begin(), instances.end(), [](const auto& instance) -> int { return instance.name().length(); }, 24); + instances.begin(), instances.end(), [](const auto& instance) -> int { return instance.name().length(); }, + name_col_header.length(), 24); const std::string::size_type state_column_width = 18; const std::string::size_type ip_column_width = 17; const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, "Name", name_column_width, "State", state_column_width, "IPv4", - ip_column_width, "Image"); + fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, "State", state_column_width, + "IPv4", ip_column_width, "Image"); for (const auto& instance : format::sorted(reply.instances())) { @@ -264,17 +268,17 @@ std::string mp::TableFormatter::format(const NetworksReply& reply) const if (interfaces.empty()) return "No network interfaces found.\n"; + const std::string name_col_header = "Name", type_col_header = "Type", desc_col_header = "Description"; const auto name_column_width = mp::format::column_width( interfaces.begin(), interfaces.end(), [](const auto& interface) -> int { return interface.name().length(); }, - 5); - + name_col_header.length()); const auto type_column_width = mp::format::column_width( interfaces.begin(), interfaces.end(), [](const auto& interface) -> int { return interface.type().length(); }, - 5); + type_col_header.length()); const auto row_format = "{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, "Name", name_column_width, "Type", type_column_width, - "Description"); + fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, type_col_header, + type_column_width, desc_col_header); for (const auto& interface : format::sorted(reply.interfaces())) { @@ -346,31 +350,44 @@ std::string mp::TableFormatter::format(const mp::AliasDict& aliases) const if (aliases.empty()) return "No aliases defined.\n"; - auto width = [&aliases](const auto get_width, int minimum_width) -> int { + auto width = [&aliases](const auto get_width, int header_width) -> int { return mp::format::column_width( aliases.cbegin(), aliases.cend(), - [&, get_width, minimum_width](const auto& ctx) -> int { - return mp::format::column_width( + [&, get_width](const auto& ctx) -> int { + return get_width(*std::max_element( ctx.second.cbegin(), ctx.second.cend(), - [&get_width](const auto& alias) -> int { return get_width(alias); }, minimum_width, 2); + [&get_width](const auto& lhs, const auto& rhs) { return get_width(lhs) < get_width(rhs); })); }, - minimum_width, 0); + header_width); }; - const auto alias_width = width([](const auto& alias) -> int { return alias.first.length(); }, 7); - const auto instance_width = width([](const auto& alias) -> int { return alias.second.instance.length(); }, 10); - const auto command_width = width([](const auto& alias) -> int { return alias.second.command.length(); }, 9); + const std::string alias_col_header = "Alias", instance_col_header = "Instance", command_col_header = "Command", + context_col_header = "Context", dir_col_header = "Working directory"; + const std::string active_context = "*"; + const auto alias_width = + width([](const auto& alias) -> int { return alias.first.length(); }, alias_col_header.length()); + const auto instance_width = + width([](const auto& alias) -> int { return alias.second.instance.length(); }, instance_col_header.length()); + const auto command_width = + width([](const auto& alias) -> int { return alias.second.command.length(); }, command_col_header.length()); const auto context_width = mp::format::column_width( - aliases.cbegin(), aliases.cend(), [](const auto& alias) -> int { return alias.first.length(); }, 10); + aliases.cbegin(), aliases.cend(), + [&aliases, &active_context](const auto& alias) -> int { + return alias.first == aliases.active_context_name() ? alias.first.length() + active_context.length() + : alias.first.length(); + }, + context_col_header.length()); const auto row_format = "{:<{}}{:<{}}{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, "Alias", alias_width, "Instance", instance_width, "Command", - command_width, "Context", context_width, "Working directory"); + fmt::format_to(std::back_inserter(buf), row_format, alias_col_header, alias_width, instance_col_header, + instance_width, command_col_header, command_width, context_col_header, context_width, + dir_col_header); for (const auto& [context_name, context_contents] : sort_dict(aliases)) { - std::string shown_context = context_name == aliases.active_context_name() ? context_name + "*" : context_name; + std::string shown_context = + context_name == aliases.active_context_name() ? context_name + active_context : context_name; for (const auto& [name, def] : sort_dict(context_contents)) { diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index 30bfb5883ec..a6dab1b360e 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -470,32 +470,54 @@ TEST_P(FormatterTestsuite, table) const std::string csv_head{"Alias,Instance,Command,Working directory,Context\n"}; -INSTANTIATE_TEST_SUITE_P( - AliasDictionary, FormatterTestsuite, - Values(std::make_tuple(AliasesVector{}, csv_head, - "{\n \"active-context\": \"default\",\n \"contexts\": {\n \"default\": {\n" - " }\n }\n}\n", - "No aliases defined.\n", "active_context: default\naliases:\n default: ~\n"), - std::make_tuple( - AliasesVector{{"lsp", {"primary", "ls", "map"}}, {"llp", {"primary", "ls", "map"}}}, - csv_head + "llp,primary,ls,map,default*\nlsp,primary,ls,map,default*\n", - "{\n \"active-context\": \"default\",\n \"contexts\": {\n" - " \"default\": {\n" - " \"llp\": {\n" - " \"command\": \"ls\",\n" - " \"instance\": \"primary\",\n" - " \"working-directory\": \"map\"\n" - " },\n" - " \"lsp\": {\n" - " \"command\": \"ls\",\n" - " \"instance\": \"primary\",\n" - " \"working-directory\": \"map\"\n" - " }\n }\n }\n}\n", - "Alias Instance Command Context Working directory\n" - "llp primary ls default* map\nlsp primary ls default* map\n", - "active_context: default\naliases:\n default:\n" - " - alias: llp\n command: ls\n instance: primary\n working-directory: map\n" - " - alias: lsp\n command: ls\n instance: primary\n working-directory: map\n"))); +INSTANTIATE_TEST_SUITE_P(AliasDictionary, FormatterTestsuite, + Values(std::make_tuple(AliasesVector{}, csv_head, + "{\n" + " \"active-context\": \"default\",\n" + " \"contexts\": {\n" + " \"default\": {\n" + " }\n" + " }\n" + "}\n", + "No aliases defined.\n", + "active_context: default\n" + "aliases:\n" + " default: ~\n"), + std::make_tuple(AliasesVector{{"lsp", {"primary", "ls", "map"}}, + {"llp", {"primary", "ls", "map"}}}, + csv_head + "llp,primary,ls,map,default*\n" + "lsp,primary,ls,map,default*\n", + "{\n" + " \"active-context\": \"default\",\n" + " \"contexts\": {\n" + " \"default\": {\n" + " \"llp\": {\n" + " \"command\": \"ls\",\n" + " \"instance\": \"primary\",\n" + " \"working-directory\": \"map\"\n" + " },\n" + " \"lsp\": {\n" + " \"command\": \"ls\",\n" + " \"instance\": \"primary\",\n" + " \"working-directory\": \"map\"\n" + " }\n" + " }\n" + " }\n" + "}\n", + "Alias Instance Command Context Working directory\n" + "llp primary ls default* map\n" + "lsp primary ls default* map\n", + "active_context: default\n" + "aliases:\n" + " default:\n" + " - alias: llp\n" + " command: ls\n" + " instance: primary\n" + " working-directory: map\n" + " - alias: lsp\n" + " command: ls\n" + " instance: primary\n" + " working-directory: map\n"))); struct RemoveInstanceTestsuite : public AliasDictionary, public WithParamInterface>> diff --git a/tests/test_format_utils.cpp b/tests/test_format_utils.cpp index 247ea835e52..80252d380ab 100644 --- a/tests/test_format_utils.cpp +++ b/tests/test_format_utils.cpp @@ -210,7 +210,8 @@ TEST(StaticFormatFunctions, columnWidthOnEmptyInputWorks) const auto get_width = [](const auto& str) -> int { return str.length(); }; int min_w = 3; - ASSERT_EQ(mp::format::column_width(empty_vector.begin(), empty_vector.end(), get_width, min_w), min_w); + ASSERT_EQ(mp::format::column_width(empty_vector.begin(), empty_vector.end(), get_width, 0, min_w), + mp::format::col_buffer); } TEST(StaticFormatFunctions, columnWidthOnWideInputWorks) @@ -222,16 +223,16 @@ TEST(StaticFormatFunctions, columnWidthOnWideInputWorks) int min_w = 3; int space = 1; - ASSERT_EQ(mp::format::column_width(str_vector.begin(), str_vector.end(), get_width, min_w, space), - wider_str.length() + space); + ASSERT_EQ(mp::format::column_width(str_vector.begin(), str_vector.end(), get_width, space, min_w), + wider_str.length() + mp::format::col_buffer); } TEST(StaticFormatFunctions, columnWidthOnNarrowInputWorks) { const auto str_vector = std::vector{"n", "n2"}; const auto get_width = [](const auto& str) -> int { return str.length(); }; - int min_w = 3; + int min_w = 7; int space = 2; - ASSERT_EQ(mp::format::column_width(str_vector.begin(), str_vector.end(), get_width, min_w, space), 4); + ASSERT_EQ(mp::format::column_width(str_vector.begin(), str_vector.end(), get_width, space, min_w), min_w); } diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 1aadcd2a27f..6ffd284d1f4 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -911,8 +911,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ const std::vector non_orderable_networks_formatter_outputs{ {&table_formatter, &empty_networks_reply, "No network interfaces found.\n", "table_networks_empty"}, {&table_formatter, &one_short_line_networks_reply, - "Name Type Description\n" - "en0 eth Ether\n", + "Name Type Description\n" + "en0 eth Ether\n", "table_networks_one_short_line"}, {&table_formatter, &one_long_line_networks_reply, "Name Type Description\n" From e5572fb03eb26d86f6c1fe84ca4ccd0d97902fd9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 18 Apr 2023 07:39:46 -0700 Subject: [PATCH 142/627] [vm] add name generation method --- .../backends/shared/base_virtual_machine.cpp | 18 +++++++++++++----- .../backends/shared/base_virtual_machine.h | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 8b29a3b5f54..70f83350953 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -113,13 +113,15 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) { - // TODO@snapshots generate name + std::string snapshot_name; + { std::unique_lock lock{snapshot_mutex}; if (snapshot_count > max_snapshots) throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded", max_snapshots)}; + snapshot_name = name.empty() ? generate_snapshot_name() : name; - const auto [it, success] = snapshots.try_emplace(name, nullptr); + const auto [it, success] = snapshots.try_emplace(snapshot_name, nullptr); if (success) { auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() noexcept { @@ -134,7 +136,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn }); // TODO@snapshots - generate implementation-specific snapshot instead - auto ret = head_snapshot = it->second = std::make_shared(name, comment, head_snapshot, specs); + auto ret = head_snapshot = it->second = + std::make_shared(snapshot_name, comment, head_snapshot, specs); ++snapshot_count; persist_head_snapshot(snapshot_dir); @@ -146,8 +149,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } - mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); - throw SnapshotNameTaken{vm_name, name}; + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", snapshot_name)); + throw SnapshotNameTaken{vm_name, snapshot_name}; } void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) @@ -268,4 +271,9 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const rollback_head_file.dismiss(); } +std::string BaseVirtualMachine::generate_snapshot_name() const +{ + return fmt::format("snapshot{}", snapshot_count + 1); +} + } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 2444f93cf41..2ea28776d2f 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -69,6 +69,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& snapshot_dir) const; + std::string generate_snapshot_name() const; private: using SnapshotMap = std::unordered_map>; From 76481f4d9c9d48d73c0367c2bf7da8a63eef7b6e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 18 Apr 2023 07:44:31 -0700 Subject: [PATCH 143/627] [daemon] dont throw on empty name --- src/daemon/daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a7835b3abdc..4ef239731f7 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2404,7 +2404,7 @@ try grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only take snapshots of stopped instances."}); auto snapshot_name = request->snapshot(); - if (!mp::utils::valid_hostname(snapshot_name)) + if (!snapshot_name.empty() && !mp::utils::valid_hostname(snapshot_name)) return status_promise->set_value( grpc::Status{grpc::INVALID_ARGUMENT, fmt::format(R"(Invalid snapshot name: "{}".)", snapshot_name)}); From dbd1ce60b2cc6b77f5bbfa049059ce5bf2133c9c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 12:26:47 +0100 Subject: [PATCH 144/627] [vm] Fix move from const lambda capture --- .../backends/shared/base_virtual_machine.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 70f83350953..666461c16bb 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -124,16 +124,17 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn const auto [it, success] = snapshots.try_emplace(snapshot_name, nullptr); if (success) { - auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() noexcept { - if (it->second) // snapshot was created - { - --snapshot_count; - head_snapshot = std::move(old_head); - mp::top_catch_all(vm_name, [it] { it->second->delet(); }); - } - - snapshots.erase(it); - }); + auto rollback_on_failure = + sg::make_scope_guard([this, it = it, old_head = head_snapshot]() mutable noexcept { + if (it->second) // snapshot was created + { + --snapshot_count; + head_snapshot = std::move(old_head); + mp::top_catch_all(vm_name, [it] { it->second->delet(); }); + } + + snapshots.erase(it); + }); // TODO@snapshots - generate implementation-specific snapshot instead auto ret = head_snapshot = it->second = From ee6ddd923d48e2aa3d6f76c69a07dadb008ed15b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 12:28:27 +0100 Subject: [PATCH 145/627] [vm] Invert if logic To ease indentation. --- .../backends/shared/base_virtual_machine.cpp | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 666461c16bb..215e1f12ef0 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -122,36 +122,35 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn snapshot_name = name.empty() ? generate_snapshot_name() : name; const auto [it, success] = snapshots.try_emplace(snapshot_name, nullptr); - if (success) + if (!success) { - auto rollback_on_failure = - sg::make_scope_guard([this, it = it, old_head = head_snapshot]() mutable noexcept { - if (it->second) // snapshot was created - { - --snapshot_count; - head_snapshot = std::move(old_head); - mp::top_catch_all(vm_name, [it] { it->second->delet(); }); - } - - snapshots.erase(it); - }); + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", snapshot_name)); + throw SnapshotNameTaken{vm_name, snapshot_name}; + } - // TODO@snapshots - generate implementation-specific snapshot instead - auto ret = head_snapshot = it->second = - std::make_shared(snapshot_name, comment, head_snapshot, specs); + auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() mutable noexcept { + if (it->second) // snapshot was created + { + --snapshot_count; + head_snapshot = std::move(old_head); + mp::top_catch_all(vm_name, [it] { it->second->delet(); }); + } - ++snapshot_count; - persist_head_snapshot(snapshot_dir); - rollback_on_failure.dismiss(); + snapshots.erase(it); + }); - log_latest_snapshot(std::move(lock)); + // TODO@snapshots - generate implementation-specific snapshot instead + auto ret = head_snapshot = it->second = + std::make_shared(snapshot_name, comment, head_snapshot, specs); - return ret; - } - } + ++snapshot_count; + persist_head_snapshot(snapshot_dir); + rollback_on_failure.dismiss(); - mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", snapshot_name)); - throw SnapshotNameTaken{vm_name, snapshot_name}; + log_latest_snapshot(std::move(lock)); + + return ret; + } } void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) From c1ebda7893a24c9161d42d58c902e2df608f65c3 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Apr 2023 16:10:11 -0700 Subject: [PATCH 146/627] [cli] add csv formatter --- src/client/cli/formatter/csv_formatter.cpp | 56 ++++++++++++++-------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 1d51a50d301..15a9035018a 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -50,32 +50,48 @@ std::string format_images(const google::protobuf::RepeatedPtrField {};", mount->source_path(), mount->target_path()); + const auto& instance_details = info.instance_info(); + + fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},{},{},{},{},{},{},{},", info.name(), + mp::format::status_string_for(info.instance_status()), + instance_details.ipv4_size() ? instance_details.ipv4(0) : "", + instance_details.ipv6_size() ? instance_details.ipv6(0) : "", + instance_details.current_release(), instance_details.id(), instance_details.image_release(), + instance_details.load(), instance_details.disk_usage(), info.disk_total(), + instance_details.memory_usage(), info.memory_total()); + + auto mount_paths = info.mount_info().mount_paths(); + for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) + { + fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); + } + + fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), + info.cpu_count(), instance_details.num_snapshots()); } + } + else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + { + fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n"); - fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), - info.cpu_count(), instance_details.num_snapshots()); + for (const auto& item : format::sort_snapshots(reply.snapshot_overview().overview())) + { + const auto snapshot = item.fundamentals(); + fmt::format_to(std::back_inserter(buf), "{},{},{},{}\n", item.instance_name(), snapshot.snapshot_name(), + snapshot.parent(), snapshot.comment()); + } } + return fmt::to_string(buf); } From fde4f3747ce3e066c84bb93730424c0212fbf6b7 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Apr 2023 16:10:34 -0700 Subject: [PATCH 147/627] [cli] add json formatter --- src/client/cli/formatter/json_formatter.cpp | 177 +++++++++++--------- 1 file changed, 102 insertions(+), 75 deletions(-) diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index b86097c141f..fe27e23efe2 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -64,90 +64,117 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const info_json.insert("errors", QJsonArray()); - for (const auto& info : reply.detailed_report().details()) + if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) { - const auto& instance_details = info.instance_info(); - - QJsonObject instance_info; - instance_info.insert("state", QString::fromStdString(mp::format::status_string_for(info.instance_status()))); - instance_info.insert("image_hash", QString::fromStdString(instance_details.id())); - instance_info.insert("image_release", QString::fromStdString(instance_details.image_release())); - instance_info.insert("release", QString::fromStdString(instance_details.current_release())); - instance_info.insert("cpu_count", QString::fromStdString(info.cpu_count())); - instance_info.insert("snapshots", QString::number(instance_details.num_snapshots())); - - QJsonArray load; - if (!instance_details.load().empty()) + for (const auto& info : reply.detailed_report().details()) { - auto loads = mp::utils::split(instance_details.load(), " "); - for (const auto& entry : loads) - load.append(std::stod(entry)); - } - instance_info.insert("load", load); - - QJsonObject disks; - QJsonObject disk; - if (!instance_details.disk_usage().empty()) - disk.insert("used", QString::fromStdString(instance_details.disk_usage())); - if (!info.disk_total().empty()) - disk.insert("total", QString::fromStdString(info.disk_total())); - - // TODO: disk name should come from daemon - disks.insert("sda1", disk); - instance_info.insert("disks", disks); - - QJsonObject memory; - if (!instance_details.memory_usage().empty()) - memory.insert("used", std::stoll(instance_details.memory_usage())); - if (!info.memory_total().empty()) - memory.insert("total", std::stoll(info.memory_total())); - instance_info.insert("memory", memory); - - QJsonArray ipv4_addrs; - for (const auto& ip : instance_details.ipv4()) - ipv4_addrs.append(QString::fromStdString(ip)); - instance_info.insert("ipv4", ipv4_addrs); - - QJsonObject mounts; - for (const auto& mount : info.mount_info().mount_paths()) - { - QJsonObject entry; - QJsonArray mount_uids; - QJsonArray mount_gids; - - auto mount_maps = mount.mount_maps(); - - for (auto i = 0; i < mount_maps.uid_mappings_size(); ++i) + const auto& instance_details = info.instance_info(); + + QJsonObject instance_info; + instance_info.insert("state", + QString::fromStdString(mp::format::status_string_for(info.instance_status()))); + instance_info.insert("image_hash", QString::fromStdString(instance_details.id())); + instance_info.insert("image_release", QString::fromStdString(instance_details.image_release())); + instance_info.insert("release", QString::fromStdString(instance_details.current_release())); + instance_info.insert("cpu_count", QString::fromStdString(info.cpu_count())); + instance_info.insert("snapshots", QString::number(instance_details.num_snapshots())); + + QJsonArray load; + if (!instance_details.load().empty()) { - auto uid_map_pair = mount_maps.uid_mappings(i); - auto host_uid = uid_map_pair.host_id(); - auto instance_uid = uid_map_pair.instance_id(); - - mount_uids.append( - QString("%1:%2") - .arg(QString::number(host_uid)) - .arg((instance_uid == mp::default_id) ? "default" : QString::number(instance_uid))); + auto loads = mp::utils::split(instance_details.load(), " "); + for (const auto& entry : loads) + load.append(std::stod(entry)); } - for (auto i = 0; i < mount_maps.gid_mappings_size(); ++i) + instance_info.insert("load", load); + + QJsonObject disks; + QJsonObject disk; + if (!instance_details.disk_usage().empty()) + disk.insert("used", QString::fromStdString(instance_details.disk_usage())); + if (!info.disk_total().empty()) + disk.insert("total", QString::fromStdString(info.disk_total())); + + // TODO: disk name should come from daemon + disks.insert("sda1", disk); + instance_info.insert("disks", disks); + + QJsonObject memory; + if (!instance_details.memory_usage().empty()) + memory.insert("used", std::stoll(instance_details.memory_usage())); + if (!info.memory_total().empty()) + memory.insert("total", std::stoll(info.memory_total())); + instance_info.insert("memory", memory); + + QJsonArray ipv4_addrs; + for (const auto& ip : instance_details.ipv4()) + ipv4_addrs.append(QString::fromStdString(ip)); + instance_info.insert("ipv4", ipv4_addrs); + + QJsonObject mounts; + for (const auto& mount : info.mount_info().mount_paths()) { - auto gid_map_pair = mount_maps.gid_mappings(i); - auto host_gid = gid_map_pair.host_id(); - auto instance_gid = gid_map_pair.instance_id(); - - mount_gids.append( - QString("%1:%2") - .arg(QString::number(host_gid)) - .arg((instance_gid == mp::default_id) ? "default" : QString::number(instance_gid))); + QJsonObject entry; + QJsonArray mount_uids; + QJsonArray mount_gids; + + auto mount_maps = mount.mount_maps(); + + for (auto i = 0; i < mount_maps.uid_mappings_size(); ++i) + { + auto uid_map_pair = mount_maps.uid_mappings(i); + auto host_uid = uid_map_pair.host_id(); + auto instance_uid = uid_map_pair.instance_id(); + + mount_uids.append( + QString("%1:%2") + .arg(QString::number(host_uid)) + .arg((instance_uid == mp::default_id) ? "default" : QString::number(instance_uid))); + } + for (auto i = 0; i < mount_maps.gid_mappings_size(); ++i) + { + auto gid_map_pair = mount_maps.gid_mappings(i); + auto host_gid = gid_map_pair.host_id(); + auto instance_gid = gid_map_pair.instance_id(); + + mount_gids.append( + QString("%1:%2") + .arg(QString::number(host_gid)) + .arg((instance_gid == mp::default_id) ? "default" : QString::number(instance_gid))); + } + entry.insert("uid_mappings", mount_uids); + entry.insert("gid_mappings", mount_gids); + entry.insert("source_path", QString::fromStdString(mount.source_path())); + + mounts.insert(QString::fromStdString(mount.target_path()), entry); } - entry.insert("uid_mappings", mount_uids); - entry.insert("gid_mappings", mount_gids); - entry.insert("source_path", QString::fromStdString(mount.source_path())); + instance_info.insert("mounts", mounts); + + info_obj.insert(QString::fromStdString(info.name()), instance_info); + } + } + else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + { + std::unordered_map instance_map; - mounts.insert(QString::fromStdString(mount.target_path()), entry); + for (const auto& item : reply.snapshot_overview().overview()) + { + const auto snapshot = item.fundamentals(); + QJsonObject snapshot_obj; + + snapshot_obj.insert("parent", QString::fromStdString(snapshot.parent())); + snapshot_obj.insert("comment", QString::fromStdString(snapshot.comment())); + + auto it = instance_map.find(item.instance_name()); + if (it == instance_map.end()) + instance_map.emplace(item.instance_name(), + QJsonObject{{QString::fromStdString(snapshot.snapshot_name()), snapshot_obj}}); + else + it->second.insert(QString::fromStdString(snapshot.snapshot_name()), snapshot_obj); } - instance_info.insert("mounts", mounts); - info_obj.insert(QString::fromStdString(info.name()), instance_info); + for (const auto& [instance_name, instance_obj] : instance_map) + info_obj.insert(QString::fromStdString(instance_name), instance_obj); } info_json.insert("info", info_obj); From 62b1e28b09d15947b2a37b8cbedbaf190c500290 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Apr 2023 16:10:58 -0700 Subject: [PATCH 148/627] [cli] add yaml formatter --- src/client/cli/formatter/yaml_formatter.cpp | 179 +++++++++++--------- 1 file changed, 101 insertions(+), 78 deletions(-) diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 1d5de3d88b0..bc60274c3c3 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -65,93 +65,116 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const info_node["errors"].push_back(YAML::Null); - for (const auto& info : format::sorted(reply.detailed_report().details())) + if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) { - const auto& instance_details = info.instance_info(); - YAML::Node instance_node; - - instance_node["state"] = mp::format::status_string_for(info.instance_status()); - instance_node["snapshots"] = instance_details.num_snapshots(); - instance_node["image_hash"] = instance_details.id(); - instance_node["image_release"] = instance_details.image_release(); - if (instance_details.current_release().empty()) - instance_node["release"] = YAML::Null; - else - instance_node["release"] = instance_details.current_release(); - - instance_node["cpu_count"] = YAML::Null; - if (!info.cpu_count().empty()) - instance_node["cpu_count"] = info.cpu_count(); - - if (!instance_details.load().empty()) - { - // The VM returns load info in the default C locale - auto current_loc = std::locale(); - std::locale::global(std::locale("C")); - auto loads = mp::utils::split(instance_details.load(), " "); - for (const auto& entry : loads) - instance_node["load"].push_back(entry); - std::locale::global(current_loc); - } - - YAML::Node disk; - disk["used"] = YAML::Null; - disk["total"] = YAML::Null; - if (!instance_details.disk_usage().empty()) - disk["used"] = instance_details.disk_usage(); - if (!info.disk_total().empty()) - disk["total"] = info.disk_total(); - - // TODO: disk name should come from daemon - YAML::Node disk_node; - disk_node["sda1"] = disk; - instance_node["disks"].push_back(disk_node); - - YAML::Node memory; - memory["usage"] = YAML::Null; - memory["total"] = YAML::Null; - if (!instance_details.memory_usage().empty()) - memory["usage"] = std::stoll(instance_details.memory_usage()); - if (!info.memory_total().empty()) - memory["total"] = std::stoll(info.memory_total()); - - instance_node["memory"] = memory; - - instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); - for (const auto& ip : instance_details.ipv4()) - instance_node["ipv4"].push_back(ip); - - YAML::Node mounts; - for (const auto& mount : info.mount_info().mount_paths()) + for (const auto& info : format::sorted(reply.detailed_report().details())) { - YAML::Node mount_node; - - for (const auto& uid_mapping : mount.mount_maps().uid_mappings()) + const auto& instance_details = info.instance_info(); + YAML::Node instance_node; + + instance_node["state"] = mp::format::status_string_for(info.instance_status()); + instance_node["snapshots"] = instance_details.num_snapshots(); + instance_node["image_hash"] = instance_details.id(); + instance_node["image_release"] = instance_details.image_release(); + if (instance_details.current_release().empty()) + instance_node["release"] = YAML::Null; + else + instance_node["release"] = instance_details.current_release(); + + instance_node["cpu_count"] = YAML::Null; + if (!info.cpu_count().empty()) + instance_node["cpu_count"] = info.cpu_count(); + + if (!instance_details.load().empty()) { - auto host_uid = uid_mapping.host_id(); - auto instance_uid = uid_mapping.instance_id(); - - mount_node["uid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_uid), - (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); + // The VM returns load info in the default C locale + auto current_loc = std::locale(); + std::locale::global(std::locale("C")); + auto loads = mp::utils::split(instance_details.load(), " "); + for (const auto& entry : loads) + instance_node["load"].push_back(entry); + std::locale::global(current_loc); } - for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) - { - auto host_gid = gid_mapping.host_id(); - auto instance_gid = gid_mapping.instance_id(); - mount_node["gid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_gid), - (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); + YAML::Node disk; + disk["used"] = YAML::Null; + disk["total"] = YAML::Null; + if (!instance_details.disk_usage().empty()) + disk["used"] = instance_details.disk_usage(); + if (!info.disk_total().empty()) + disk["total"] = info.disk_total(); + + // TODO: disk name should come from daemon + YAML::Node disk_node; + disk_node["sda1"] = disk; + instance_node["disks"].push_back(disk_node); + + YAML::Node memory; + memory["usage"] = YAML::Null; + memory["total"] = YAML::Null; + if (!instance_details.memory_usage().empty()) + memory["usage"] = std::stoll(instance_details.memory_usage()); + if (!info.memory_total().empty()) + memory["total"] = std::stoll(info.memory_total()); + + instance_node["memory"] = memory; + + instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); + for (const auto& ip : instance_details.ipv4()) + instance_node["ipv4"].push_back(ip); + + YAML::Node mounts; + for (const auto& mount : info.mount_info().mount_paths()) + { + YAML::Node mount_node; + + for (const auto& uid_mapping : mount.mount_maps().uid_mappings()) + { + auto host_uid = uid_mapping.host_id(); + auto instance_uid = uid_mapping.instance_id(); + + mount_node["uid_mappings"].push_back( + fmt::format("{}:{}", std::to_string(host_uid), + (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); + } + for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) + { + auto host_gid = gid_mapping.host_id(); + auto instance_gid = gid_mapping.instance_id(); + + mount_node["gid_mappings"].push_back( + fmt::format("{}:{}", std::to_string(host_gid), + (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); + } + + mount_node["source_path"] = mount.source_path(); + mounts[mount.target_path()] = mount_node; } + instance_node["mounts"] = mounts; - mount_node["source_path"] = mount.source_path(); - mounts[mount.target_path()] = mount_node; + info_node[info.name()].push_back(instance_node); } - instance_node["mounts"] = mounts; - - info_node[info.name()].push_back(instance_node); } + else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + { + for (const auto& item : format::sort_snapshots(reply.snapshot_overview().overview())) + { + const auto snapshot = item.fundamentals(); + YAML::Node instance_node; + YAML::Node snapshot_node; + + snapshot_node["parent"] = YAML::Null; + snapshot_node["comment"] = YAML::Null; + if (!snapshot.parent().empty()) + snapshot_node["parent"] = snapshot.parent(); + if (!snapshot.comment().empty()) + snapshot_node["comment"] = snapshot.comment(); + + instance_node[snapshot.snapshot_name()].push_back(snapshot_node); + info_node[item.instance_name()].push_back(instance_node); + } + } + return mpu::emit_yaml(info_node); } From ae00cd887567b62cb47ed02788b46745ab756186 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Apr 2023 16:11:44 -0700 Subject: [PATCH 149/627] [cli] fix bug in table formatter --- src/client/cli/formatter/table_formatter.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index aa5a759ae6a..dbdc2daf4ae 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -22,8 +22,6 @@ #include #include -#include - namespace mp = multipass; namespace From 2f6f11377ca322112461ed00f182e73ddfe9a295 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Apr 2023 16:12:13 -0700 Subject: [PATCH 150/627] [util] factor out common code --- include/multipass/cli/format_utils.h | 31 ++++++++++++++++++++ src/client/cli/formatter/table_formatter.cpp | 8 +---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 9c8521184f9..bf74f4b923c 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -42,6 +43,9 @@ Formatter* formatter_for(const std::string& format); template Instances sorted(const Instances& instances); +template +Snapshots sort_snapshots(const Snapshots& snapshots); + void filter_aliases(google::protobuf::RepeatedPtrField& aliases); // Computes the column width needed to display all the elements of a range [begin, end). get_width is a function @@ -78,6 +82,33 @@ Instances multipass::format::sorted(const Instances& instances) return ret; } +template +Snapshots multipass::format::sort_snapshots(const Snapshots& snapshots) +{ + using google::protobuf::util::TimeUtil; + if (snapshots.empty()) + return snapshots; + + auto ret = snapshots; + const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString(); + std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) { + if (a.instance_name() == petenv_name && b.instance_name() != petenv_name) + return true; + else if (a.instance_name() != petenv_name && b.instance_name() == petenv_name) + return false; + + if (a.instance_name() < b.instance_name()) + return true; + else if (a.instance_name() > b.instance_name()) + return false; + + return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); + }); + + return ret; +} + namespace fmt { template <> diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index dbdc2daf4ae..db57e5254d4 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -178,13 +178,7 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, snapshot_col_header, snapshot_column_width, parent_col_header, parent_column_width, comment_col_header); - using google::protobuf::util::TimeUtil; - std::sort(std::begin(overview), std::end(overview), [](const auto& a, const auto& b) { - return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < - TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); - }); - - for (const auto& item : overview) + for (const auto& item : mp::format::sort_snapshots(overview)) { auto snapshot = item.fundamentals(); fmt::format_to(std::back_inserter(buf), row_format, item.instance_name(), name_column_width, From 4b55b77093d7db4e85c5a1a082e90a89875f7dc9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Apr 2023 16:12:35 -0700 Subject: [PATCH 151/627] [tests] add snapshot overview tests --- tests/test_output_formatter.cpp | 221 ++++++++++++++++++++++++++++++-- 1 file changed, 212 insertions(+), 9 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 6ffd284d1f4..c23d20ed94c 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -155,6 +155,13 @@ auto construct_empty_info_reply() return info_reply; } +auto construct_empty_snapshot_overview_reply() +{ + mp::InfoReply info_reply; + info_reply.mutable_snapshot_overview(); + return info_reply; +} + auto construct_single_instance_info_reply() { mp::InfoReply info_reply; @@ -253,13 +260,93 @@ auto construct_multiple_instances_info_reply() return info_reply; } +auto construct_single_snapshot_overview_info_reply() +{ + mp::InfoReply info_reply; + + auto snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); + snapshot_entry->set_instance_name("foo"); + + auto fundamentals = snapshot_entry->mutable_fundamentals(); + fundamentals->set_snapshot_name("snapshot1"); + fundamentals->set_comment("This is a sample comment"); + + google::protobuf::Timestamp timestamp; + timestamp.set_seconds(time(nullptr)); + timestamp.set_nanos(0); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return info_reply; +} + +auto construct_multiple_snapshot_overview_info_reply() +{ + mp::InfoReply info_reply; + + auto snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); + auto fundamentals = snapshot_entry->mutable_fundamentals(); + google::protobuf::Timestamp timestamp; + + snapshot_entry->set_instance_name("prosperous-spadefish"); + fundamentals->set_snapshot_name("snapshot10"); + fundamentals->set_parent("snapshot2"); + timestamp.set_seconds(1672531200); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); + fundamentals = snapshot_entry->mutable_fundamentals(); + snapshot_entry->set_instance_name("hale-roller"); + fundamentals->set_snapshot_name("rolling"); + fundamentals->set_parent("pristine"); + fundamentals->set_comment("Loaded with stuff"); + timestamp.set_seconds(25425952800); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); + fundamentals = snapshot_entry->mutable_fundamentals(); + snapshot_entry->set_instance_name("hale-roller"); + fundamentals->set_snapshot_name("rocking"); + fundamentals->set_parent("pristine"); + fundamentals->set_comment("A very long comment that should be truncated by the table formatter"); + timestamp.set_seconds(2209234259); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); + fundamentals = snapshot_entry->mutable_fundamentals(); + snapshot_entry->set_instance_name("hale-roller"); + fundamentals->set_snapshot_name("pristine"); + fundamentals->set_comment("A first snapshot"); + timestamp.set_seconds(409298914); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); + fundamentals = snapshot_entry->mutable_fundamentals(); + snapshot_entry->set_instance_name("prosperous-spadefish"); + fundamentals->set_snapshot_name("snapshot2"); + fundamentals->set_comment("Before restoring snap1"); + timestamp.set_seconds(1671840000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return info_reply; +} + auto add_petenv_to_reply(mp::InfoReply& reply) { - auto entry = reply.mutable_detailed_report()->add_details(); - entry->set_name(petenv_name()); - entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); - entry->mutable_instance_info()->set_image_release("18.10"); - entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); + if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) + { + auto entry = reply.mutable_detailed_report()->add_details(); + entry->set_name(petenv_name()); + entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); + entry->mutable_instance_info()->set_image_release("18.10"); + entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); + } + else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + { + auto entry = reply.mutable_snapshot_overview()->add_overview(); + entry->set_instance_name(petenv_name()); + entry->mutable_fundamentals()->set_snapshot_name("snapshot1"); + entry->mutable_fundamentals()->set_comment("An examplary comment"); + } } auto construct_empty_reply() @@ -491,8 +578,11 @@ const auto one_long_line_networks_reply = construct_one_long_line_networks_reply const auto multiple_lines_networks_reply = construct_multiple_lines_networks_reply(); const auto empty_info_reply = construct_empty_info_reply(); +const auto empty_snapshot_overview_reply = construct_empty_snapshot_overview_reply(); const auto single_instance_info_reply = construct_single_instance_info_reply(); const auto multiple_instances_info_reply = construct_multiple_instances_info_reply(); +const auto single_snapshot_overview_info_reply = construct_single_snapshot_overview_info_reply(); +const auto multiple_snapshot_overview_info_reply = construct_multiple_snapshot_overview_info_reply(); const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &empty_list_reply, "No instances found.\n", "table_list_empty"}, @@ -517,6 +607,7 @@ const std::vector orderable_list_info_formatter_outputs{ "table_list_unsorted"}, {&table_formatter, &empty_info_reply, "\n", "table_info_empty"}, + {&table_formatter, &empty_snapshot_overview_reply, "No snapshots found.\n", "table_snapshot_overview_empty"}, {&table_formatter, &single_instance_info_reply, "Name: foo\n" "State: Running\n" @@ -564,6 +655,18 @@ const std::vector orderable_list_info_formatter_outputs{ "Memory usage: --\n" "Mounts: --\n", "table_info_multiple"}, + {&table_formatter, &single_snapshot_overview_info_reply, + "Instance Snapshot Parent Comment\n" + "foo snapshot1 -- This is a sample comment\n", + "table_snapshot_overview_single"}, + {&table_formatter, &multiple_snapshot_overview_info_reply, + "Instance Snapshot Parent Comment\n" + "hale-roller pristine -- A first snapshot\n" + "hale-roller rocking pristine A very long comment that should be truncated by t…\n" + "hale-roller rolling pristine Loaded with stuff\n" + "prosperous-spadefish snapshot2 -- Before restoring snap1\n" + "prosperous-spadefish snapshot10 snapshot2 --\n", + "table_snapshot_overview_multiple"}, {&csv_formatter, &empty_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n", "csv_list_empty"}, {&csv_formatter, &single_instance_list_reply, @@ -587,6 +690,8 @@ const std::vector orderable_list_info_formatter_outputs{ "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\n", "csv_info_empty"}, + {&csv_formatter, &empty_snapshot_overview_reply, "Instance,Snapshot,Parent,Comment\n", + "csv_snapshot_overview_empty"}, {&csv_formatter, &single_instance_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nfoo,Running,10.168.32.2,2001:67c:1562:8007::aac:423a,Ubuntu " @@ -603,6 +708,14 @@ const std::vector orderable_list_info_formatter_outputs{ "source;,\"10.21.124.56\";,4,1\nbombastic,Stopped,,,," "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,\"\";,,3\n", "csv_info_multiple"}, + {&csv_formatter, &single_snapshot_overview_info_reply, + "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,This is a sample comment\n", "csv_snapshot_overview_single"}, + {&csv_formatter, &multiple_snapshot_overview_info_reply, + "Instance,Snapshot,Parent,Comment\nhale-roller,pristine,,A first " + "snapshot\nhale-roller,rocking,pristine,A very long comment that should be truncated by the table " + "formatter\nhale-roller,rolling,pristine,Loaded with stuff\nprosperous-spadefish,snapshot2,,Before restoring " + "snap1\nprosperous-spadefish,snapshot10,snapshot2,\n", + "csv_snapshot_overview_multiple"}, {&yaml_formatter, &empty_list_reply, "\n", "yaml_list_empty"}, {&yaml_formatter, &single_instance_list_reply, @@ -648,6 +761,7 @@ const std::vector orderable_list_info_formatter_outputs{ " release: Ubuntu N/A\n", "yaml_list_unsorted"}, {&yaml_formatter, &empty_info_reply, "errors:\n - ~\n", "yaml_info_empty"}, + {&yaml_formatter, &empty_snapshot_overview_reply, "errors:\n - ~\n", "yaml_snapshot_overview_empty"}, {&yaml_formatter, &single_instance_info_reply, "errors:\n" @@ -734,7 +848,36 @@ const std::vector orderable_list_info_formatter_outputs{ " ipv4:\n" " []\n" " mounts: ~\n", - "yaml_info_multiple"}}; + "yaml_info_multiple"}, + {&yaml_formatter, &single_snapshot_overview_info_reply, + "errors:\n" + " - ~\n" + "foo:\n" + " - snapshot1:\n" + " - parent: ~\n" + " comment: This is a sample comment\n", + "yaml_snapshot_overview_single"}, + {&yaml_formatter, &multiple_snapshot_overview_info_reply, + "errors:\n" + " - ~\n" + "hale-roller:\n" + " - pristine:\n" + " - parent: ~\n" + " comment: A first snapshot\n" + " - rocking:\n" + " - parent: pristine\n" + " comment: A very long comment that should be truncated by the table formatter\n" + " - rolling:\n" + " - parent: pristine\n" + " comment: Loaded with stuff\n" + "prosperous-spadefish:\n" + " - snapshot2:\n" + " - parent: ~\n" + " comment: Before restoring snap1\n" + " - snapshot10:\n" + " - parent: snapshot2\n" + " comment: ~\n", + "yaml_snapshot_overview_multiple"}}; const std::vector non_orderable_list_info_formatter_outputs{ {&json_formatter, &empty_list_reply, @@ -787,6 +930,14 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_empty"}, + {&json_formatter, &empty_snapshot_overview_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " }\n" + "}\n", + "json_snapshot_overview_empty"}, {&json_formatter, &single_instance_info_reply, "{\n" " \"errors\": [\n" @@ -906,7 +1057,53 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " }\n" "}\n", - "json_info_multiple"}}; + "json_info_multiple"}, + {&json_formatter, &single_snapshot_overview_info_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"foo\": {\n" + " \"snapshot1\": {\n" + " \"comment\": \"This is a sample comment\",\n" + " \"parent\": \"\"\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_snapshot_overview_single"}, + {&json_formatter, &multiple_snapshot_overview_info_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"hale-roller\": {\n" + " \"pristine\": {\n" + " \"comment\": \"A first snapshot\",\n" + " \"parent\": \"\"\n" + " },\n" + " \"rocking\": {\n" + " \"comment\": \"A very long comment that should be truncated by the table formatter\",\n" + " \"parent\": \"pristine\"\n" + " },\n" + " \"rolling\": {\n" + " \"comment\": \"Loaded with stuff\",\n" + " \"parent\": \"pristine\"\n" + " }\n" + " },\n" + " \"prosperous-spadefish\": {\n" + " \"snapshot10\": {\n" + " \"comment\": \"\",\n" + " \"parent\": \"snapshot2\"\n" + " },\n" + " \"snapshot2\": {\n" + " \"comment\": \"Before restoring snap1\",\n" + " \"parent\": \"\"\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_snapshot_overview_multiple"}}; const std::vector non_orderable_networks_formatter_outputs{ {&table_formatter, &empty_networks_reply, "No network interfaces found.\n", "table_networks_empty"}, @@ -1410,6 +1607,12 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) else if (auto input = dynamic_cast(reply)) { mp::InfoReply reply_copy; + + if (input->info_contents_case() == mp::InfoReply::kDetailedReport) + reply_copy.mutable_detailed_report(); + else if (input->info_contents_case() == mp::InfoReply::kSnapshotOverview) + reply_copy.mutable_snapshot_overview(); + if (prepend) { add_petenv_to_reply(reply_copy); @@ -1423,9 +1626,9 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) output = formatter->format(reply_copy); if (dynamic_cast(formatter)) - regex = fmt::format("Name:[[:space:]]+{}.+", petenv_name()); + regex = fmt::format("(Name:[[:space:]]+{0}.+|Instance[[:print:]]*\n{0}.+)", petenv_name()); else if (dynamic_cast(formatter)) - regex = fmt::format("Name[[:print:]]*\n{},.*", petenv_name()); + regex = fmt::format("(Name[[:print:]]*\n{0},.*|Instance[[:print:]]*\n{0},.*)", petenv_name()); else if (dynamic_cast(formatter)) regex = fmt::format("(errors:[[:space:]]+-[[:space:]]+~[[:space:]]+)?{}:.*", petenv_name()); else From 085bd4ddffac15bfb42dda41cd603fd49cddfffd Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 18 May 2023 13:19:15 -0700 Subject: [PATCH 152/627] [formatters] add helper methods --- src/client/cli/formatter/csv_formatter.cpp | 91 ++++---- src/client/cli/formatter/json_formatter.cpp | 227 +++++++++++--------- src/client/cli/formatter/yaml_formatter.cpp | 221 ++++++++++--------- tests/test_output_formatter.cpp | 10 +- 4 files changed, 307 insertions(+), 242 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 15a9035018a..53841f722fa 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -45,55 +45,74 @@ std::string format_images(const google::protobuf::RepeatedPtrField {};", mount->source_path(), mount->target_path()); - } - - fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), - info.cpu_count(), instance_details.num_snapshots()); + fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); } + + fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), + info.cpu_count(), instance_details.num_snapshots()); } - else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) - { - fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n"); - for (const auto& item : format::sort_snapshots(reply.snapshot_overview().overview())) - { - const auto snapshot = item.fundamentals(); - fmt::format_to(std::back_inserter(buf), "{},{},{},{}\n", item.instance_name(), snapshot.snapshot_name(), - snapshot.parent(), snapshot.comment()); - } + return fmt::to_string(buf); +} + +std::string generate_snapshot_overview_report(const mp::InfoReply& reply) +{ + fmt::memory_buffer buf; + + fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n"); + + for (const auto& item : mp::format::sort_snapshots(reply.snapshot_overview().overview())) + { + const auto& snapshot = item.fundamentals(); + fmt::format_to(std::back_inserter(buf), "{},{},{},{}\n", item.instance_name(), snapshot.snapshot_name(), + snapshot.parent(), snapshot.comment()); } return fmt::to_string(buf); } +} // namespace + +std::string mp::CSVFormatter::format(const InfoReply& reply) const +{ + std::string output; + + if (reply.has_detailed_report()) + { + output = generate_instance_info_report(reply); + } + else + { + assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); + output = generate_snapshot_overview_report(reply); + } + + return output; +} std::string mp::CSVFormatter::format(const ListReply& reply) const { diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index fe27e23efe2..8205664a37e 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -55,130 +55,155 @@ QJsonObject format_images(const google::protobuf::RepeatedPtrField instance_map; + const auto& snapshot = item.fundamentals(); + QJsonObject snapshot_obj; - for (const auto& item : reply.snapshot_overview().overview()) + snapshot_obj.insert("parent", QString::fromStdString(snapshot.parent())); + snapshot_obj.insert("comment", QString::fromStdString(snapshot.comment())); + + auto it = info_obj.find(QString::fromStdString(item.instance_name())); + if (it == info_obj.end()) { - const auto snapshot = item.fundamentals(); - QJsonObject snapshot_obj; - - snapshot_obj.insert("parent", QString::fromStdString(snapshot.parent())); - snapshot_obj.insert("comment", QString::fromStdString(snapshot.comment())); - - auto it = instance_map.find(item.instance_name()); - if (it == instance_map.end()) - instance_map.emplace(item.instance_name(), - QJsonObject{{QString::fromStdString(snapshot.snapshot_name()), snapshot_obj}}); - else - it->second.insert(QString::fromStdString(snapshot.snapshot_name()), snapshot_obj); + info_obj.insert(QString::fromStdString(item.instance_name()), + QJsonObject{{QString::fromStdString(snapshot.snapshot_name()), snapshot_obj}}); + } + else + { + QJsonObject obj = it.value().toObject(); + obj.insert(QString::fromStdString(snapshot.snapshot_name()), snapshot_obj); + it.value() = obj; } - - for (const auto& [instance_name, instance_obj] : instance_map) - info_obj.insert(QString::fromStdString(instance_name), instance_obj); } + info_json.insert("info", info_obj); - return mp::json_to_string(info_json); + return QString(QJsonDocument(info_json).toJson()).toStdString(); +} +} // namespace + +std::string mp::JsonFormatter::format(const InfoReply& reply) const +{ + std::string output; + + if (reply.has_detailed_report()) + { + output = generate_instance_info_report(reply); + } + else + { + assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); + output = generate_snapshot_overview_report(reply); + } + + return output; } std::string mp::JsonFormatter::format(const ListReply& reply) const diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index bc60274c3c3..d02a39896a0 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -57,126 +57,147 @@ format_images(const google::protobuf::RepeatedPtrField& return images_node; } -} // namespace -std::string mp::YamlFormatter::format(const InfoReply& reply) const +std::string generate_instance_info_report(const mp::InfoReply& reply) { YAML::Node info_node; info_node["errors"].push_back(YAML::Null); - if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) + for (const auto& info : mp::format::sorted(reply.detailed_report().details())) { - for (const auto& info : format::sorted(reply.detailed_report().details())) + const auto& instance_details = info.instance_info(); + YAML::Node instance_node; + + instance_node["state"] = mp::format::status_string_for(info.instance_status()); + instance_node["snapshots"] = instance_details.num_snapshots(); + instance_node["image_hash"] = instance_details.id(); + instance_node["image_release"] = instance_details.image_release(); + if (instance_details.current_release().empty()) + instance_node["release"] = YAML::Null; + else + instance_node["release"] = instance_details.current_release(); + + instance_node["cpu_count"] = YAML::Null; + if (!info.cpu_count().empty()) + instance_node["cpu_count"] = info.cpu_count(); + + if (!instance_details.load().empty()) + { + // The VM returns load info in the default C locale + auto current_loc = std::locale(); + std::locale::global(std::locale("C")); + auto loads = mp::utils::split(instance_details.load(), " "); + for (const auto& entry : loads) + instance_node["load"].push_back(entry); + std::locale::global(current_loc); + } + + YAML::Node disk; + disk["used"] = YAML::Null; + disk["total"] = YAML::Null; + if (!instance_details.disk_usage().empty()) + disk["used"] = instance_details.disk_usage(); + if (!info.disk_total().empty()) + disk["total"] = info.disk_total(); + + // TODO: disk name should come from daemon + YAML::Node disk_node; + disk_node["sda1"] = disk; + instance_node["disks"].push_back(disk_node); + + YAML::Node memory; + memory["usage"] = YAML::Null; + memory["total"] = YAML::Null; + if (!instance_details.memory_usage().empty()) + memory["usage"] = std::stoll(instance_details.memory_usage()); + if (!info.memory_total().empty()) + memory["total"] = std::stoll(info.memory_total()); + + instance_node["memory"] = memory; + + instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); + for (const auto& ip : instance_details.ipv4()) + instance_node["ipv4"].push_back(ip); + + YAML::Node mounts; + for (const auto& mount : info.mount_info().mount_paths()) { - const auto& instance_details = info.instance_info(); - YAML::Node instance_node; - - instance_node["state"] = mp::format::status_string_for(info.instance_status()); - instance_node["snapshots"] = instance_details.num_snapshots(); - instance_node["image_hash"] = instance_details.id(); - instance_node["image_release"] = instance_details.image_release(); - if (instance_details.current_release().empty()) - instance_node["release"] = YAML::Null; - else - instance_node["release"] = instance_details.current_release(); - - instance_node["cpu_count"] = YAML::Null; - if (!info.cpu_count().empty()) - instance_node["cpu_count"] = info.cpu_count(); - - if (!instance_details.load().empty()) + YAML::Node mount_node; + + for (const auto& uid_mapping : mount.mount_maps().uid_mappings()) { - // The VM returns load info in the default C locale - auto current_loc = std::locale(); - std::locale::global(std::locale("C")); - auto loads = mp::utils::split(instance_details.load(), " "); - for (const auto& entry : loads) - instance_node["load"].push_back(entry); - std::locale::global(current_loc); - } + auto host_uid = uid_mapping.host_id(); + auto instance_uid = uid_mapping.instance_id(); - YAML::Node disk; - disk["used"] = YAML::Null; - disk["total"] = YAML::Null; - if (!instance_details.disk_usage().empty()) - disk["used"] = instance_details.disk_usage(); - if (!info.disk_total().empty()) - disk["total"] = info.disk_total(); - - // TODO: disk name should come from daemon - YAML::Node disk_node; - disk_node["sda1"] = disk; - instance_node["disks"].push_back(disk_node); - - YAML::Node memory; - memory["usage"] = YAML::Null; - memory["total"] = YAML::Null; - if (!instance_details.memory_usage().empty()) - memory["usage"] = std::stoll(instance_details.memory_usage()); - if (!info.memory_total().empty()) - memory["total"] = std::stoll(info.memory_total()); - - instance_node["memory"] = memory; - - instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); - for (const auto& ip : instance_details.ipv4()) - instance_node["ipv4"].push_back(ip); - - YAML::Node mounts; - for (const auto& mount : info.mount_info().mount_paths()) + mount_node["uid_mappings"].push_back( + fmt::format("{}:{}", std::to_string(host_uid), + (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); + } + for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) { - YAML::Node mount_node; - - for (const auto& uid_mapping : mount.mount_maps().uid_mappings()) - { - auto host_uid = uid_mapping.host_id(); - auto instance_uid = uid_mapping.instance_id(); - - mount_node["uid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_uid), - (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); - } - for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) - { - auto host_gid = gid_mapping.host_id(); - auto instance_gid = gid_mapping.instance_id(); - - mount_node["gid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_gid), - (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); - } - - mount_node["source_path"] = mount.source_path(); - mounts[mount.target_path()] = mount_node; + auto host_gid = gid_mapping.host_id(); + auto instance_gid = gid_mapping.instance_id(); + + mount_node["gid_mappings"].push_back( + fmt::format("{}:{}", std::to_string(host_gid), + (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); } - instance_node["mounts"] = mounts; - info_node[info.name()].push_back(instance_node); + mount_node["source_path"] = mount.source_path(); + mounts[mount.target_path()] = mount_node; } + instance_node["mounts"] = mounts; + + info_node[info.name()].push_back(instance_node); } - else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + + return mpu::emit_yaml(info_node); +} + +std::string generate_snapshot_overview_report(const mp::InfoReply& reply) +{ + YAML::Node info_node; + + info_node["errors"].push_back(YAML::Null); + + for (const auto& item : mp::format::sort_snapshots(reply.snapshot_overview().overview())) { - for (const auto& item : format::sort_snapshots(reply.snapshot_overview().overview())) - { - const auto snapshot = item.fundamentals(); - YAML::Node instance_node; - YAML::Node snapshot_node; - - snapshot_node["parent"] = YAML::Null; - snapshot_node["comment"] = YAML::Null; - if (!snapshot.parent().empty()) - snapshot_node["parent"] = snapshot.parent(); - if (!snapshot.comment().empty()) - snapshot_node["comment"] = snapshot.comment(); - - instance_node[snapshot.snapshot_name()].push_back(snapshot_node); - info_node[item.instance_name()].push_back(instance_node); - } + const auto& snapshot = item.fundamentals(); + YAML::Node instance_node; + YAML::Node snapshot_node; + + snapshot_node["parent"] = YAML::Null; + snapshot_node["comment"] = YAML::Null; + if (!snapshot.parent().empty()) + snapshot_node["parent"] = snapshot.parent(); + if (!snapshot.comment().empty()) + snapshot_node["comment"] = snapshot.comment(); + + instance_node[snapshot.snapshot_name()].push_back(snapshot_node); + info_node[item.instance_name()].push_back(instance_node); } return mpu::emit_yaml(info_node); } +} // namespace + +std::string mp::YamlFormatter::format(const InfoReply& reply) const +{ + std::string output; + + if (reply.has_detailed_report()) + { + output = generate_instance_info_report(reply); + } + else + { + assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); + output = generate_snapshot_overview_report(reply); + } + + return output; +} std::string mp::YamlFormatter::format(const ListReply& reply) const { diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index c23d20ed94c..34c68612cb4 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -332,7 +332,7 @@ auto construct_multiple_snapshot_overview_info_reply() auto add_petenv_to_reply(mp::InfoReply& reply) { - if (reply.info_contents_case() == mp::InfoReply::kDetailedReport) + if (reply.has_detailed_report()) { auto entry = reply.mutable_detailed_report()->add_details(); entry->set_name(petenv_name()); @@ -340,12 +340,12 @@ auto add_petenv_to_reply(mp::InfoReply& reply) entry->mutable_instance_info()->set_image_release("18.10"); entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); } - else if (reply.info_contents_case() == mp::InfoReply::kSnapshotOverview) + else { auto entry = reply.mutable_snapshot_overview()->add_overview(); entry->set_instance_name(petenv_name()); entry->mutable_fundamentals()->set_snapshot_name("snapshot1"); - entry->mutable_fundamentals()->set_comment("An examplary comment"); + entry->mutable_fundamentals()->set_comment("An exemplary comment"); } } @@ -1608,9 +1608,9 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) { mp::InfoReply reply_copy; - if (input->info_contents_case() == mp::InfoReply::kDetailedReport) + if (input->has_detailed_report()) reply_copy.mutable_detailed_report(); - else if (input->info_contents_case() == mp::InfoReply::kSnapshotOverview) + else reply_copy.mutable_snapshot_overview(); if (prepend) From 586bbb96adf863798bf6cac8164b1a0c6894410e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 19 May 2023 09:16:32 -0700 Subject: [PATCH 153/627] [formatters] clean up if statement with ternary ops --- src/client/cli/formatter/yaml_formatter.cpp | 44 ++++++++------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index d02a39896a0..e75b1822f5c 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -73,14 +73,11 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) instance_node["snapshots"] = instance_details.num_snapshots(); instance_node["image_hash"] = instance_details.id(); instance_node["image_release"] = instance_details.image_release(); - if (instance_details.current_release().empty()) - instance_node["release"] = YAML::Null; - else - instance_node["release"] = instance_details.current_release(); - - instance_node["cpu_count"] = YAML::Null; - if (!info.cpu_count().empty()) - instance_node["cpu_count"] = info.cpu_count(); + instance_node["release"] = instance_details.current_release().empty() + ? YAML::Node(YAML::NodeType::Null) + : YAML::Node(instance_details.current_release()); + instance_node["cpu_count"] = + info.cpu_count().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(info.cpu_count()); if (!instance_details.load().empty()) { @@ -94,12 +91,9 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) } YAML::Node disk; - disk["used"] = YAML::Null; - disk["total"] = YAML::Null; - if (!instance_details.disk_usage().empty()) - disk["used"] = instance_details.disk_usage(); - if (!info.disk_total().empty()) - disk["total"] = info.disk_total(); + disk["used"] = instance_details.disk_usage().empty() ? YAML::Node(YAML::NodeType::Null) + : YAML::Node(instance_details.disk_usage()); + disk["total"] = info.disk_total().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(info.disk_total()); // TODO: disk name should come from daemon YAML::Node disk_node; @@ -107,13 +101,11 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) instance_node["disks"].push_back(disk_node); YAML::Node memory; - memory["usage"] = YAML::Null; - memory["total"] = YAML::Null; - if (!instance_details.memory_usage().empty()) - memory["usage"] = std::stoll(instance_details.memory_usage()); - if (!info.memory_total().empty()) - memory["total"] = std::stoll(info.memory_total()); - + memory["usage"] = instance_details.memory_usage().empty() + ? YAML::Node(YAML::NodeType::Null) + : YAML::Node(std::stoll(instance_details.memory_usage())); + memory["total"] = info.memory_total().empty() ? YAML::Node(YAML::NodeType::Null) + : YAML::Node(std::stoll(info.memory_total())); instance_node["memory"] = memory; instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); @@ -167,12 +159,10 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) YAML::Node instance_node; YAML::Node snapshot_node; - snapshot_node["parent"] = YAML::Null; - snapshot_node["comment"] = YAML::Null; - if (!snapshot.parent().empty()) - snapshot_node["parent"] = snapshot.parent(); - if (!snapshot.comment().empty()) - snapshot_node["comment"] = snapshot.comment(); + snapshot_node["parent"] = + snapshot.parent().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(snapshot.parent()); + snapshot_node["comment"] = + snapshot.comment().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(snapshot.comment()); instance_node[snapshot.snapshot_name()].push_back(snapshot_node); info_node[item.instance_name()].push_back(instance_node); From 8f54f207f46c9b3fa1a88a8f695aa6abff257bff Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 23 May 2023 00:02:39 -0700 Subject: [PATCH 154/627] [formatters] clean up null node creation --- src/client/cli/formatter/yaml_formatter.cpp | 28 ++++++++------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index e75b1822f5c..0b3b011e265 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -31,8 +31,8 @@ namespace mpu = multipass::utils; namespace { -std::map -format_images(const google::protobuf::RepeatedPtrField& images_info) +template +std::map format_images(const ImageInfo& images_info) { std::map images_node; @@ -73,11 +73,9 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) instance_node["snapshots"] = instance_details.num_snapshots(); instance_node["image_hash"] = instance_details.id(); instance_node["image_release"] = instance_details.image_release(); - instance_node["release"] = instance_details.current_release().empty() - ? YAML::Node(YAML::NodeType::Null) - : YAML::Node(instance_details.current_release()); - instance_node["cpu_count"] = - info.cpu_count().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(info.cpu_count()); + instance_node["release"] = + instance_details.current_release().empty() ? YAML::Node() : YAML::Node(instance_details.current_release()); + instance_node["cpu_count"] = info.cpu_count().empty() ? YAML::Node() : YAML::Node(info.cpu_count()); if (!instance_details.load().empty()) { @@ -91,9 +89,8 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) } YAML::Node disk; - disk["used"] = instance_details.disk_usage().empty() ? YAML::Node(YAML::NodeType::Null) - : YAML::Node(instance_details.disk_usage()); - disk["total"] = info.disk_total().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(info.disk_total()); + disk["used"] = instance_details.disk_usage().empty() ? YAML::Node() : YAML::Node(instance_details.disk_usage()); + disk["total"] = info.disk_total().empty() ? YAML::Node() : YAML::Node(info.disk_total()); // TODO: disk name should come from daemon YAML::Node disk_node; @@ -102,10 +99,9 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) YAML::Node memory; memory["usage"] = instance_details.memory_usage().empty() - ? YAML::Node(YAML::NodeType::Null) + ? YAML::Node() : YAML::Node(std::stoll(instance_details.memory_usage())); - memory["total"] = info.memory_total().empty() ? YAML::Node(YAML::NodeType::Null) - : YAML::Node(std::stoll(info.memory_total())); + memory["total"] = info.memory_total().empty() ? YAML::Node() : YAML::Node(std::stoll(info.memory_total())); instance_node["memory"] = memory; instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); @@ -159,10 +155,8 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) YAML::Node instance_node; YAML::Node snapshot_node; - snapshot_node["parent"] = - snapshot.parent().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(snapshot.parent()); - snapshot_node["comment"] = - snapshot.comment().empty() ? YAML::Node(YAML::NodeType::Null) : YAML::Node(snapshot.comment()); + snapshot_node["parent"] = snapshot.parent().empty() ? YAML::Node() : YAML::Node(snapshot.parent()); + snapshot_node["comment"] = snapshot.comment().empty() ? YAML::Node() : YAML::Node(snapshot.comment()); instance_node[snapshot.snapshot_name()].push_back(snapshot_node); info_node[item.instance_name()].push_back(instance_node); From 75a254d33e0c24d12b8d510e719b524089f2e0a0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 12:34:23 +0100 Subject: [PATCH 155/627] [vm] Add snapshot factory method to VM Unimplemented for the time being. --- src/platform/backends/libvirt/libvirt_virtual_machine.cpp | 8 ++++++++ src/platform/backends/libvirt/libvirt_virtual_machine.h | 5 +++++ src/platform/backends/lxd/lxd_virtual_machine.cpp | 7 +++++++ src/platform/backends/lxd/lxd_virtual_machine.h | 5 +++++ src/platform/backends/qemu/qemu_virtual_machine.cpp | 7 +++++++ src/platform/backends/qemu/qemu_virtual_machine.h | 4 ++++ src/platform/backends/shared/base_virtual_machine.cpp | 4 +--- src/platform/backends/shared/base_virtual_machine.h | 5 +++++ tests/test_base_virtual_machine.cpp | 8 ++++++++ 9 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 705b53778ef..ed44e1be749 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -553,3 +553,11 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) mp::backend::resize_instance_image(new_size, desc.image.image_path); desc.disk_space = new_size; } + +auto multipass::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const multipass::VMSpecs& specs) + -> std::shared_ptr +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots +} diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index bf717fc3dd3..4a9dc1324f0 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -58,6 +58,11 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine static ConnectionUPtr open_libvirt_connection(const LibvirtWrapper::UPtr& libvirt_wrapper); +protected: + std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const VMSpecs& specs) override; + private: DomainUPtr initialize_domain_info(virConnectPtr connection); DomainUPtr checked_vm_domain() const; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 80249b8f0cb..111265c19ed 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -482,3 +482,10 @@ mp::LXDVirtualMachine::make_native_mount_handler(const SSHKeyProvider* ssh_key_p return std::make_unique(manager, this, ssh_key_provider, target, mount); } + +auto multipass::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, + std::shared_ptr parent, + const multipass::VMSpecs& specs) -> std::shared_ptr +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots +} diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index 11569c62a96..2da184bd9b1 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -56,6 +56,11 @@ class LXDVirtualMachine : public BaseVirtualMachine std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount) override; +protected: + std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const VMSpecs& specs) override; + private: const QString name; const std::string username; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 35b5dd6a285..656d264e5a3 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -637,3 +637,10 @@ mp::QemuVirtualMachine::MountArgs& mp::QemuVirtualMachine::modifiable_mount_args { return mount_args; } + +auto multipass::QemuVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const multipass::VMSpecs& specs) -> std::shared_ptr +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots +} diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index f0bd31ce66f..45b248dcb91 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -73,6 +73,10 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine { } + std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const VMSpecs& specs) override; + private: void on_started(); void on_error(); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 215e1f12ef0..841776d4e47 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -139,9 +139,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn snapshots.erase(it); }); - // TODO@snapshots - generate implementation-specific snapshot instead - auto ret = head_snapshot = it->second = - std::make_shared(snapshot_name, comment, head_snapshot, specs); + auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); ++snapshot_count; persist_head_snapshot(snapshot_dir); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 2ea28776d2f..3cc01edfe7e 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -62,6 +62,11 @@ class BaseVirtualMachine : public VirtualMachine const std::string& name, const std::string& comment) override; void load_snapshots(const QDir& snapshot_dir) override; +protected: + virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const VMSpecs& specs) = 0; + private: template void log_latest_snapshot(LockT lock) const; diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index aa49695ea3d..67b77c69b0f 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -112,6 +112,14 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine void resize_disk(const mp::MemorySize&) override { } + +protected: + std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + const mp::VMSpecs& specs) override + { + return nullptr; + } }; struct BaseVM : public Test From d3f2582fcaba13dc077ef150d56140e77cd1e28c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 15:28:05 +0100 Subject: [PATCH 156/627] [qemu] Make specific QemuSnapshot in QEMU VM --- src/platform/backends/qemu/CMakeLists.txt | 1 + src/platform/backends/qemu/qemu_snapshot.cpp | 29 +++++++++++++++ src/platform/backends/qemu/qemu_snapshot.h | 35 +++++++++++++++++++ .../backends/qemu/qemu_virtual_machine.cpp | 3 +- 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/platform/backends/qemu/qemu_snapshot.cpp create mode 100644 src/platform/backends/qemu/qemu_snapshot.h diff --git a/src/platform/backends/qemu/CMakeLists.txt b/src/platform/backends/qemu/CMakeLists.txt index 653c1d1e768..8ba5e722ea3 100644 --- a/src/platform/backends/qemu/CMakeLists.txt +++ b/src/platform/backends/qemu/CMakeLists.txt @@ -25,6 +25,7 @@ add_definitions(-DHOST_ARCH="${HOST_ARCH}") add_library(qemu_backend STATIC qemu_base_process_spec.cpp qemu_mount_handler.cpp + qemu_snapshot.cpp qemu_vm_process_spec.cpp qemu_vmstate_process_spec.cpp qemu_virtual_machine_factory.cpp diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp new file mode 100644 index 00000000000..0ea041c7e4f --- /dev/null +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -0,0 +1,29 @@ +/* + * 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 "qemu_snapshot.h" + +namespace multipass +{ + +QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, + const VMSpecs& specs) + : BaseSnapshot(name, comment, std::move(parent), specs) +{ +} + +} // namespace multipass diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h new file mode 100644 index 00000000000..a6018887905 --- /dev/null +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -0,0 +1,35 @@ +/* + * 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_QEMU_SNAPSHOT_H +#define MULTIPASS_QEMU_SNAPSHOT_H + +#include + +namespace multipass +{ + +class QemuSnapshot : public BaseSnapshot +{ +public: + QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, + const VMSpecs& specs); +}; + +} // namespace multipass + +#endif // MULTIPASS_QEMU_SNAPSHOT_H diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 656d264e5a3..b774250aab9 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -17,6 +17,7 @@ #include "qemu_virtual_machine.h" #include "qemu_mount_handler.h" +#include "qemu_snapshot.h" #include "qemu_vm_process_spec.h" #include "qemu_vmstate_process_spec.h" @@ -642,5 +643,5 @@ auto multipass::QemuVirtualMachine::make_specific_snapshot(const std::string& na std::shared_ptr parent, const multipass::VMSpecs& specs) -> std::shared_ptr { - throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots + return std::make_shared(name, comment, std::move(parent), specs); } From e3aea35876d6027ac9b5c0eca3fad03bd5074680 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 20:25:42 +0100 Subject: [PATCH 157/627] [qemu] Implement creating a snapshot with qemu-img --- src/platform/backends/qemu/qemu_snapshot.cpp | 32 +++++++++++++++---- src/platform/backends/qemu/qemu_snapshot.h | 2 +- .../backends/qemu/qemu_virtual_machine.cpp | 3 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 0ea041c7e4f..921e8dc30c7 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -16,14 +16,34 @@ */ #include "qemu_snapshot.h" +#include +#include -namespace multipass -{ +namespace mp = multipass; +namespace mpp = mp::platform; -QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, - const VMSpecs& specs) - : BaseSnapshot(name, comment, std::move(parent), specs) +namespace { +const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char + that can't be part of the name, to avoid confusion */ } -} // namespace multipass +mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, const VMSpecs& specs, const QString& image_path) + : BaseSnapshot(name, comment, std::move(parent), specs), image_path{image_path} +{ + /* TODO@snapshots verify no snapshot with the same tag exists yet (otherwise creation would succeed and then we'd be + unable to identify the snapshot by name) */ + auto process = mpp::make_process( + mp::simple_process_spec("qemu-img", // TODO@ricab extract making spec + QStringList{"snapshot", "-c", snapshot_template.arg(get_name().c_str()), image_path})); + + auto process_state = process->execute(); + if (!process_state.completed_successfully()) + { + throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", + process_state.failure_message(), process->read_all_standard_error())); + } + + // TODO@ricab consider a dedicated method to shoot the snapshot on the backend +} diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index a6018887905..8d191257255 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -27,7 +27,7 @@ class QemuSnapshot : public BaseSnapshot { public: QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, - const VMSpecs& specs); + const VMSpecs& specs, const QString& image_path); }; } // namespace multipass diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index b774250aab9..fad28f4e212 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -643,5 +643,6 @@ auto multipass::QemuVirtualMachine::make_specific_snapshot(const std::string& na std::shared_ptr parent, const multipass::VMSpecs& specs) -> std::shared_ptr { - return std::make_shared(name, comment, std::move(parent), specs); + assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise + return std::make_shared(name, comment, std::move(parent), specs, desc.image.image_path); } From 3d20ef600ef1a4bc48cbb9b94f7a68a1e4fd622e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 20:27:39 +0100 Subject: [PATCH 158/627] [snapshot] Add a dedicated `shoot()` method --- include/multipass/snapshot.h | 1 + src/platform/backends/qemu/qemu_snapshot.cpp | 5 +++++ src/platform/backends/qemu/qemu_snapshot.h | 5 +++++ src/platform/backends/shared/base_snapshot.h | 7 +++++++ tests/stub_snapshot.h | 4 ++++ 5 files changed, 22 insertions(+) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 9872e83339f..528fc22c231 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -57,6 +57,7 @@ class Snapshot : private DisabledCopyMove virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; + virtual void shoot() = 0; // not using the constructor, we need snapshot objects for existing snapshots too virtual void delet() = 0; // not using the destructor, we want snapshots to stick around when daemon quits }; } // namespace multipass diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 921e8dc30c7..258dc70a652 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -47,3 +47,8 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme // TODO@ricab consider a dedicated method to shoot the snapshot on the backend } + +void multipass::QemuSnapshot::shoot() +{ + // TODO@snapshots lock +} diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 8d191257255..d98c82dd42f 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -28,6 +28,11 @@ class QemuSnapshot : public BaseSnapshot public: QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, const QString& image_path); + + void shoot() override; + +private: + QString image_path; }; } // namespace multipass diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 2cf598fd046..2500c66fb45 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -58,6 +58,7 @@ class BaseSnapshot : public Snapshot void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; + void shoot() override; void delet() override; private: @@ -161,6 +162,12 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr parent = std::move(p); } +inline void multipass::BaseSnapshot::shoot() +{ + // TODO@snapshots this is meant to be implemented by descendants + // placeholder implementation to avoid making this class abstract for now +} + inline void multipass::BaseSnapshot::delet() { // TODO@snapshots this is meant to be implemented by descendants diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 25a1b25ce3e..3640346d6cf 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -94,6 +94,10 @@ struct StubSnapshot : public Snapshot { } + void shoot() override + { + } + void delet() override { } From 4f43ff21ddf4a71af2688cad29078dad87561351 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 18 May 2023 20:28:49 +0100 Subject: [PATCH 159/627] [qemu] Move snapshot creation to `shoot()` --- src/platform/backends/qemu/qemu_snapshot.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 258dc70a652..1e02f906038 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -32,6 +32,11 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme std::shared_ptr parent, const VMSpecs& specs, const QString& image_path) : BaseSnapshot(name, comment, std::move(parent), specs), image_path{image_path} { +} + +void multipass::QemuSnapshot::shoot() +{ + // TODO@snapshots lock /* TODO@snapshots verify no snapshot with the same tag exists yet (otherwise creation would succeed and then we'd be unable to identify the snapshot by name) */ auto process = mpp::make_process( @@ -44,11 +49,4 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", process_state.failure_message(), process->read_all_standard_error())); } - - // TODO@ricab consider a dedicated method to shoot the snapshot on the backend -} - -void multipass::QemuSnapshot::shoot() -{ - // TODO@snapshots lock } From 03f8a5f4b832852fecbdd13e3f41c318b36ba52f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 14:59:10 +0100 Subject: [PATCH 160/627] [vm] Call Snapshot::shoot when taking the snapshot --- src/platform/backends/shared/base_virtual_machine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 841776d4e47..70bfc67799f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -140,6 +140,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn }); auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); + ret->shoot(); ++snapshot_count; persist_head_snapshot(snapshot_dir); From 601699f98f324eb41abfd018d70ca57b920cdec4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 16:30:20 +0100 Subject: [PATCH 161/627] [vm] Account for new shoot call in rollback guard --- .../backends/shared/base_virtual_machine.cpp | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 70bfc67799f..6ad4ade87d4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -128,24 +128,32 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn throw SnapshotNameTaken{vm_name, snapshot_name}; } - auto rollback_on_failure = sg::make_scope_guard([this, it = it, old_head = head_snapshot]() mutable noexcept { - if (it->second) // snapshot was created - { - --snapshot_count; - head_snapshot = std::move(old_head); - mp::top_catch_all(vm_name, [it] { it->second->delet(); }); - } + auto rollback_on_failure = sg::make_scope_guard( + [this, it = it, old_head = head_snapshot, old_count = snapshot_count]() mutable noexcept { + if (old_head != head_snapshot) + { + assert(it->second && "snapshot not created despite modified head"); + if (snapshot_count > old_count) // snapshot was created + { + assert(snapshot_count - old_count == 1); + --snapshot_count; - snapshots.erase(it); - }); + mp::top_catch_all(vm_name, [it] { it->second->delet(); }); + } + + head_snapshot = std::move(old_head); + } + + snapshots.erase(it); + }); auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); ret->shoot(); ++snapshot_count; persist_head_snapshot(snapshot_dir); - rollback_on_failure.dismiss(); + rollback_on_failure.dismiss(); log_latest_snapshot(std::move(lock)); return ret; From 840c19ac5ec8b28b1b1ef8d3aabbe19a6cc7d6ab Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 11:23:15 +0100 Subject: [PATCH 162/627] [qemu] Extract querying for existing snapshots --- .../backends/qemu/qemu_virtual_machine.cpp | 27 ++----------------- .../shared/qemu_img_utils/qemu_img_utils.cpp | 25 +++++++++++++++++ .../shared/qemu_img_utils/qemu_img_utils.h | 2 ++ 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index fad28f4e212..6d4a9f7461e 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -149,30 +149,6 @@ auto hmc_to_qmp_json(const QString& command_line) return QJsonDocument(qmp).toJson(); } -bool instance_image_has_snapshot(const mp::Path& image_path) -{ - auto process = - mp::platform::make_process(mp::simple_process_spec("qemu-img", QStringList{"snapshot", "-l", image_path})); - auto process_state = process->execute(); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), process->read_all_standard_error())); - } - - auto output = process->read_all_standard_output().split('\n'); - - for (const auto& line : output) - { - if (line.contains(suspend_tag)) - { - return true; - } - } - - return false; -} - auto get_qemu_machine_type(const QStringList& platform_args) { QTemporaryFile dump_file; @@ -223,7 +199,8 @@ auto generate_metadata(const QStringList& platform_args, const QStringList& proc mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, VMStatusMonitor& monitor) - : BaseVirtualMachine{instance_image_has_snapshot(desc.image.image_path) ? State::suspended : State::off, + : BaseVirtualMachine{mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag) ? State::suspended + : State::off, desc.vm_name}, desc{desc}, mac_addr{desc.default_mac_address}, diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index b9d53df50c8..e2315fe0a43 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -22,6 +22,7 @@ #include #include #include +#include // TODO@ricab can we use the above? #include #include @@ -87,3 +88,27 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) return image_path; } } + +bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, const char* snapshot_tag) +{ + auto process = + mp::platform::make_process(mp::simple_process_spec("qemu-img", QStringList{"snapshot", "-l", image_path})); + auto process_state = process->execute(); + if (!process_state.completed_successfully()) + { + throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", + process_state.failure_message(), process->read_all_standard_error())); + } + + auto output = process->read_all_standard_output().split('\n'); + + for (const auto& line : output) + { + if (line.contains(snapshot_tag)) + { + return true; + } + } + + return false; +} diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index 8872ba7f5d5..4dfff18dad9 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -28,6 +28,8 @@ namespace backend { void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); +bool instance_image_has_snapshot(const Path& image_path, const char* snapshot_tag); + } // namespace backend } // namespace multipass #endif // MULTIPASS_QEMU_IMG_UTILS_H From e5235e852c7b74a85929c8f4d3f46412431fff30 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 16:48:10 +0100 Subject: [PATCH 163/627] [qemu] Protect against repeated snapshot tags --- src/platform/backends/qemu/qemu_snapshot.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 1e02f906038..8e3bb6b5cc3 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -16,6 +16,7 @@ */ #include "qemu_snapshot.h" +#include "shared/qemu_img_utils/qemu_img_utils.h" #include #include @@ -37,11 +38,16 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme void multipass::QemuSnapshot::shoot() { // TODO@snapshots lock - /* TODO@snapshots verify no snapshot with the same tag exists yet (otherwise creation would succeed and then we'd be - unable to identify the snapshot by name) */ - auto process = mpp::make_process( - mp::simple_process_spec("qemu-img", // TODO@ricab extract making spec - QStringList{"snapshot", "-c", snapshot_template.arg(get_name().c_str()), image_path})); + auto tag = snapshot_template.arg(get_name().c_str()); + + // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to + // identify the snapshot by tag) + if (backend::instance_image_has_snapshot(image_path, tag.toUtf8())) + throw std::runtime_error{fmt::format( + "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; + + auto process = mpp::make_process(mp::simple_process_spec("qemu-img", // TODO@ricab extract making spec + QStringList{"snapshot", "-c", tag, image_path})); auto process_state = process->execute(); if (!process_state.completed_successfully()) From 2f086d9bfc33b5eb7a0fb82ec1879d1db0aff7e6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 16:55:41 +0100 Subject: [PATCH 164/627] [snapshot] Rename a couple of snapshot methods --- include/multipass/snapshot.h | 4 ++-- src/platform/backends/qemu/qemu_snapshot.cpp | 2 +- src/platform/backends/qemu/qemu_snapshot.h | 2 +- src/platform/backends/shared/base_snapshot.h | 8 ++++---- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- tests/stub_snapshot.h | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 528fc22c231..57c76ee1928 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -57,8 +57,8 @@ class Snapshot : private DisabledCopyMove virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; - virtual void shoot() = 0; // not using the constructor, we need snapshot objects for existing snapshots too - virtual void delet() = 0; // not using the destructor, we want snapshots to stick around when daemon quits + virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too + virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits }; } // namespace multipass diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 8e3bb6b5cc3..c93957b6888 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -35,7 +35,7 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme { } -void multipass::QemuSnapshot::shoot() +void multipass::QemuSnapshot::capture() { // TODO@snapshots lock auto tag = snapshot_template.arg(get_name().c_str()); diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index d98c82dd42f..b294ba4c36d 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -29,7 +29,7 @@ class QemuSnapshot : public BaseSnapshot QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, const QString& image_path); - void shoot() override; + void capture() override; private: QString image_path; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 2500c66fb45..74a5eb3a370 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -58,8 +58,8 @@ class BaseSnapshot : public Snapshot void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; - void shoot() override; - void delet() override; + void capture() override; + void erase() override; private: struct InnerJsonTag @@ -162,13 +162,13 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr parent = std::move(p); } -inline void multipass::BaseSnapshot::shoot() +inline void multipass::BaseSnapshot::capture() { // TODO@snapshots this is meant to be implemented by descendants // placeholder implementation to avoid making this class abstract for now } -inline void multipass::BaseSnapshot::delet() +inline void multipass::BaseSnapshot::erase() { // TODO@snapshots this is meant to be implemented by descendants // placeholder implementation to avoid making this class abstract for now diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6ad4ade87d4..6476458e85e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -138,7 +138,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn assert(snapshot_count - old_count == 1); --snapshot_count; - mp::top_catch_all(vm_name, [it] { it->second->delet(); }); + mp::top_catch_all(vm_name, [it] { it->second->erase(); }); } head_snapshot = std::move(old_head); @@ -148,7 +148,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn }); auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); - ret->shoot(); + ret->capture(); ++snapshot_count; persist_head_snapshot(snapshot_dir); diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 3640346d6cf..46bd7199ae5 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -94,11 +94,11 @@ struct StubSnapshot : public Snapshot { } - void shoot() override + void capture() override { } - void delet() override + void erase() override { } From 010d752e8165f75c0d11898f87fd60e4047af503 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:11:47 +0100 Subject: [PATCH 165/627] [qemu] Use `QemuImgProcessSpec` to list snapshots --- .../backends/shared/qemu_img_utils/qemu_img_utils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index e2315fe0a43..b21da0bb1ee 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -22,7 +22,6 @@ #include #include #include -#include // TODO@ricab can we use the above? #include #include @@ -91,8 +90,9 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, const char* snapshot_tag) { - auto process = - mp::platform::make_process(mp::simple_process_spec("qemu-img", QStringList{"snapshot", "-l", image_path})); + auto process = mp::platform::make_process( + std::make_unique(QStringList{"snapshot", "-l", image_path}, image_path)); + auto process_state = process->execute(); if (!process_state.completed_successfully()) { From 3de52b2ef09f1480eda8f6ec0def00212607452f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 17:34:20 +0100 Subject: [PATCH 166/627] [qemu] Simplify search for snapshot Simplify search for existing snapshot in the output of `qemu-img`. --- .../shared/qemu_img_utils/qemu_img_utils.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index b21da0bb1ee..2bc6b9dce0f 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -100,15 +100,5 @@ bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, const process_state.failure_message(), process->read_all_standard_error())); } - auto output = process->read_all_standard_output().split('\n'); - - for (const auto& line : output) - { - if (line.contains(snapshot_tag)) - { - return true; - } - } - - return false; + return process->read_all_standard_output().contains(snapshot_tag); } From 108b81b256c34048ad7f4c2c06cdcc8c2b209198 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 17:57:09 +0100 Subject: [PATCH 167/627] [qemu] Use `QemuImgProcessSpec` to create snapshot --- src/platform/backends/qemu/qemu_snapshot.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index c93957b6888..d11147cd9e5 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -18,7 +18,7 @@ #include "qemu_snapshot.h" #include "shared/qemu_img_utils/qemu_img_utils.h" #include -#include +#include namespace mp = multipass; namespace mpp = mp::platform; @@ -46,8 +46,8 @@ void multipass::QemuSnapshot::capture() throw std::runtime_error{fmt::format( "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; - auto process = mpp::make_process(mp::simple_process_spec("qemu-img", // TODO@ricab extract making spec - QStringList{"snapshot", "-c", tag, image_path})); + auto process = mpp::make_process(std::make_unique( // TODO@ricab extract making spec + QStringList{"snapshot", "-c", tag, image_path}, image_path)); auto process_state = process->execute(); if (!process_state.completed_successfully()) From d0e5eb7d35f400c022bc1b8d6665a7ff81504e58 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:10:36 +0100 Subject: [PATCH 168/627] [qemu] Extract making spec for snapshot capture --- src/platform/backends/qemu/qemu_snapshot.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index d11147cd9e5..30c47b3df55 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -20,6 +20,8 @@ #include #include +#include + namespace mp = multipass; namespace mpp = mp::platform; @@ -27,6 +29,11 @@ namespace { const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char that can't be part of the name, to avoid confusion */ + +std::unique_ptr make_capture_spec(const QString& tag, const QString& image_path) +{ + return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, image_path); +} } mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, @@ -46,8 +53,7 @@ void multipass::QemuSnapshot::capture() throw std::runtime_error{fmt::format( "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; - auto process = mpp::make_process(std::make_unique( // TODO@ricab extract making spec - QStringList{"snapshot", "-c", tag, image_path}, image_path)); + auto process = mpp::make_process(make_capture_spec(tag, image_path)); auto process_state = process->execute(); if (!process_state.completed_successfully()) From 9380f04c9f0afbac5eef7a45b6ad69dd1bac2c45 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:12:04 +0100 Subject: [PATCH 169/627] [qemu] Remove leftover include --- src/platform/backends/qemu/qemu_virtual_machine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 6d4a9f7461e..7d6882dcd50 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include From e1bf474306ba740a5845d700547411e8945fffd3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:14:46 +0100 Subject: [PATCH 170/627] [qemu] Extract making of snapshot tag --- src/platform/backends/qemu/qemu_snapshot.cpp | 7 ++++++- src/platform/backends/qemu/qemu_snapshot.h | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 30c47b3df55..9e80b6d8d8e 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -45,7 +45,7 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme void multipass::QemuSnapshot::capture() { // TODO@snapshots lock - auto tag = snapshot_template.arg(get_name().c_str()); + auto tag = make_tag(); // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) @@ -62,3 +62,8 @@ void multipass::QemuSnapshot::capture() process_state.failure_message(), process->read_all_standard_error())); } } + +QString multipass::QemuSnapshot::make_tag() const +{ + return snapshot_template.arg(get_name().c_str()); +} diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index b294ba4c36d..13d191b5416 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -31,6 +31,9 @@ class QemuSnapshot : public BaseSnapshot void capture() override; +private: + QString make_tag() const; + private: QString image_path; }; From 0e7292ea9e85094fcb8c02262827d4a1a6452e5e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:45:09 +0100 Subject: [PATCH 171/627] [qemu] Implement constructing snapshot from JSON Throwing implementations for now. --- src/platform/backends/qemu/qemu_snapshot.cpp | 9 ++++++++- src/platform/backends/qemu/qemu_snapshot.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 9e80b6d8d8e..4745e1fb3f2 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -16,7 +16,9 @@ */ #include "qemu_snapshot.h" +#include "qemu_virtual_machine.h" #include "shared/qemu_img_utils/qemu_img_utils.h" + #include #include @@ -34,7 +36,7 @@ std::unique_ptr make_capture_spec(const QString& tag, co { return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, image_path); } -} +} // namespace mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, const QString& image_path) @@ -42,6 +44,11 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme { } +mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const multipass::QemuVirtualMachine& vm) + : BaseSnapshot(json, vm) +{ +} + void multipass::QemuSnapshot::capture() { // TODO@snapshots lock diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 13d191b5416..7820a40cdf3 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -22,12 +22,14 @@ namespace multipass { +class QemuVirtualMachine; class QemuSnapshot : public BaseSnapshot { public: QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, const QString& image_path); + QemuSnapshot(const QJsonObject& json, const QemuVirtualMachine& vm); void capture() override; From 8277d7f16e21817810d6ba27e876553ce5dd9e12 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:48:22 +0100 Subject: [PATCH 172/627] [vm] Add method to make specific snapshot w/ JSON --- src/platform/backends/libvirt/libvirt_virtual_machine.cpp | 5 +++++ src/platform/backends/libvirt/libvirt_virtual_machine.h | 1 + src/platform/backends/lxd/lxd_virtual_machine.cpp | 5 +++++ src/platform/backends/lxd/lxd_virtual_machine.h | 1 + src/platform/backends/qemu/qemu_virtual_machine.cpp | 5 +++++ src/platform/backends/qemu/qemu_virtual_machine.h | 1 + src/platform/backends/shared/base_virtual_machine.h | 1 + tests/test_base_virtual_machine.cpp | 5 +++++ 8 files changed, 24 insertions(+) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index ed44e1be749..96118e172a0 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -561,3 +561,8 @@ auto multipass::LibVirtVirtualMachine::make_specific_snapshot(const std::string& { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } + +auto multipass::LibVirtVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots +} diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index 4a9dc1324f0..cab7f38e91f 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -59,6 +59,7 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine static ConnectionUPtr open_libvirt_connection(const LibvirtWrapper::UPtr& libvirt_wrapper); protected: + std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs) override; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 111265c19ed..5e76e54aa89 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -489,3 +489,8 @@ auto multipass::LXDVirtualMachine::make_specific_snapshot(const std::string& sna { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } + +std::shared_ptr multipass::LXDVirtualMachine::make_specific_snapshot(const QJsonObject& json) +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots +} diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index 2da184bd9b1..965b33072be 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -57,6 +57,7 @@ class LXDVirtualMachine : public BaseVirtualMachine const std::string& target, const VMMount& mount) override; protected: + std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs) override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 7d6882dcd50..5fff59b8906 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -622,3 +622,8 @@ auto multipass::QemuVirtualMachine::make_specific_snapshot(const std::string& na assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise return std::make_shared(name, comment, std::move(parent), specs, desc.image.image_path); } + +auto multipass::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots +} diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 45b248dcb91..127a4a1fa81 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -73,6 +73,7 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine { } + std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs) override; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 3cc01edfe7e..0f5381d6a82 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -63,6 +63,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshots(const QDir& snapshot_dir) override; protected: + virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs) = 0; diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 67b77c69b0f..a3ccabc0fe9 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -120,6 +120,11 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine { return nullptr; } + + virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) override + { + return nullptr; + } }; struct BaseVM : public Test From 29200a04cc9b5425456a9955322481a8711623ae Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:48:47 +0100 Subject: [PATCH 173/627] [qemu] Implement making specific snapshot w/ JSON --- src/platform/backends/qemu/qemu_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 5fff59b8906..e2f3a316a29 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -625,5 +625,5 @@ auto multipass::QemuVirtualMachine::make_specific_snapshot(const std::string& na auto multipass::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr { - throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots + return std::make_shared(json, *this); } From 4064a18156db90aaba60d6b949f2fa2046e1754c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 19:00:52 +0100 Subject: [PATCH 174/627] [vm] Make specific snapshot when loading from JSON --- src/platform/backends/shared/base_virtual_machine.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6476458e85e..d037b850c23 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -16,7 +16,6 @@ */ #include "base_virtual_machine.h" -#include "base_snapshot.h" // TODO@snapshots may be able to remove this #include #include @@ -226,8 +225,7 @@ void BaseVirtualMachine::load_snapshot_from_file(const QString& filename) void BaseVirtualMachine::load_snapshot(const QJsonObject& json) { - // TODO@snapshots move to specific VM implementations and make specific snapshot from there - auto snapshot = std::make_shared(json, *this); + auto snapshot = make_specific_snapshot(json); const auto& name = snapshot->get_name(); auto [it, success] = snapshots.try_emplace(name, snapshot); From 57f9ddaf5369824e833a2d4a2a4d7fe8a0e28f6c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 19 May 2023 18:51:09 +0100 Subject: [PATCH 175/627] [snapshot] Implement locking of capture and erase --- src/platform/backends/qemu/qemu_snapshot.cpp | 10 +++++++--- src/platform/backends/qemu/qemu_snapshot.h | 4 +++- src/platform/backends/shared/base_snapshot.h | 16 ++++++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 4745e1fb3f2..71f5f644111 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -49,9 +49,8 @@ mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const multipass::QemuVir { } -void multipass::QemuSnapshot::capture() +void mp::QemuSnapshot::capture_impl() { - // TODO@snapshots lock auto tag = make_tag(); // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to @@ -70,7 +69,12 @@ void multipass::QemuSnapshot::capture() } } -QString multipass::QemuSnapshot::make_tag() const +void mp::QemuSnapshot::erase_impl() // TODO@snapshots +{ + throw NotImplementedOnThisBackendException{"Snapshot erasing"}; +} + +QString mp::QemuSnapshot::make_tag() const { return snapshot_template.arg(get_name().c_str()); } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 7820a40cdf3..1de53cd0ae0 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -31,7 +31,9 @@ class QemuSnapshot : public BaseSnapshot const VMSpecs& specs, const QString& image_path); QemuSnapshot(const QJsonObject& json, const QemuVirtualMachine& vm); - void capture() override; +protected: + void capture_impl() override; + void erase_impl() override; private: QString make_tag() const; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 74a5eb3a370..f5d680a75ff 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -58,8 +58,12 @@ class BaseSnapshot : public Snapshot void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; - void capture() override; - void erase() override; + void capture() final; + void erase() final; + +protected: + virtual void capture_impl() = 0; + virtual void erase_impl() = 0; private: struct InnerJsonTag @@ -164,14 +168,14 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr inline void multipass::BaseSnapshot::capture() { - // TODO@snapshots this is meant to be implemented by descendants - // placeholder implementation to avoid making this class abstract for now + const std::unique_lock lock{mutex}; + capture_impl(); } inline void multipass::BaseSnapshot::erase() { - // TODO@snapshots this is meant to be implemented by descendants - // placeholder implementation to avoid making this class abstract for now + const std::unique_lock lock{mutex}; + erase_impl(); } #endif // MULTIPASS_BASE_SNAPSHOT_H From ebcfececa4f56eee281307478433ab85231ea527 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 23 May 2023 19:49:24 +0100 Subject: [PATCH 176/627] [qemu] Fix use of QemuImgProcessSpec Pass image as target rather than source image, to get write access in the AppArmor profile. --- src/platform/backends/qemu/qemu_snapshot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 71f5f644111..fdbb1700ad5 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -34,7 +34,8 @@ const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with su std::unique_ptr make_capture_spec(const QString& tag, const QString& image_path) { - return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, image_path); + return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, + /* src_img = */ "", image_path); } } // namespace From d2422942a25c589be6ebbcd4343379693334aa1b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 23 May 2023 19:50:36 +0100 Subject: [PATCH 177/627] [qemu] Add lock capability to qemu-img AppArmor --- src/process/qemuimg_process_spec.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/process/qemuimg_process_spec.cpp b/src/process/qemuimg_process_spec.cpp index 131e77cc24a..03ae5757e48 100644 --- a/src/process/qemuimg_process_spec.cpp +++ b/src/process/qemuimg_process_spec.cpp @@ -45,6 +45,7 @@ QString mp::QemuImgProcessSpec::apparmor_profile() const profile %1 flags=(attach_disconnected) { #include + capability ipc_lock, capability dac_read_search, %2 From 5d68525f242431147ddc46a3381676593addea3b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 25 May 2023 16:11:25 +0100 Subject: [PATCH 178/627] [format] Use `mp::` instead of `multipass::` --- .../backends/libvirt/libvirt_virtual_machine.cpp | 7 +++---- src/platform/backends/lxd/lxd_virtual_machine.cpp | 10 +++++----- src/platform/backends/qemu/qemu_snapshot.cpp | 3 +-- src/platform/backends/qemu/qemu_virtual_machine.cpp | 8 ++++---- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 96118e172a0..99cc32adfc2 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -554,15 +554,14 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) desc.disk_space = new_size; } -auto multipass::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const multipass::VMSpecs& specs) +auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, const mp::VMSpecs& specs) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } -auto multipass::LibVirtVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr +auto mp::LibVirtVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 5e76e54aa89..cb1464daf09 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -421,7 +421,7 @@ void mp::LXDVirtualMachine::request_state(const QString& new_state) } } -void multipass::LXDVirtualMachine::update_cpus(int num_cores) +void mp::LXDVirtualMachine::update_cpus(int num_cores) { assert(num_cores > 0); assert(manager); @@ -483,14 +483,14 @@ mp::LXDVirtualMachine::make_native_mount_handler(const SSHKeyProvider* ssh_key_p return std::make_unique(manager, this, ssh_key_provider, target, mount); } -auto multipass::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, - std::shared_ptr parent, - const multipass::VMSpecs& specs) -> std::shared_ptr +auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, + std::shared_ptr parent, const mp::VMSpecs& specs) + -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } -std::shared_ptr multipass::LXDVirtualMachine::make_specific_snapshot(const QJsonObject& json) +std::shared_ptr mp::LXDVirtualMachine::make_specific_snapshot(const QJsonObject& json) { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index fdbb1700ad5..1b5d200b0bd 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -45,8 +45,7 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme { } -mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const multipass::QemuVirtualMachine& vm) - : BaseSnapshot(json, vm) +mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const mp::QemuVirtualMachine& vm) : BaseSnapshot(json, vm) { } diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index e2f3a316a29..5d2a666090e 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -615,15 +615,15 @@ mp::QemuVirtualMachine::MountArgs& mp::QemuVirtualMachine::modifiable_mount_args return mount_args; } -auto multipass::QemuVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const multipass::VMSpecs& specs) -> std::shared_ptr +auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, const mp::VMSpecs& specs) + -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise return std::make_shared(name, comment, std::move(parent), specs, desc.image.image_path); } -auto multipass::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr +auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr { return std::make_shared(json, *this); } From 0106d292aa83e8181593f479129cdb362b57886e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 4 Apr 2023 10:29:57 -0700 Subject: [PATCH 179/627] [cli] call mp snapshot on restore --- src/client/cli/cmd/restore.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index f293bfdac06..ec77d2128d7 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -40,6 +40,13 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) if (auto ret = parse_args(parser); ret != ParseCode::Ok) return parser->returnCodeFrom(ret); + if (!request.destructive()) + { + auto ret = run_cmd({"multipass", "snapshot", QString::fromStdString(request.instance())}, parser, cout, cerr); + if (ret != ReturnCode::Ok) + return ReturnCode::CommandFail; + } + AnimatedSpinner spinner{cout}; auto on_success = [this, &spinner](mp::RestoreReply& reply) { From bea1c1393e6923d0b585d9f6e4e485264d647cf3 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 19 Apr 2023 12:25:39 -0700 Subject: [PATCH 180/627] [cli] remove extra call from cli --- src/client/cli/cmd/restore.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index ec77d2128d7..f293bfdac06 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -40,13 +40,6 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) if (auto ret = parse_args(parser); ret != ParseCode::Ok) return parser->returnCodeFrom(ret); - if (!request.destructive()) - { - auto ret = run_cmd({"multipass", "snapshot", QString::fromStdString(request.instance())}, parser, cout, cerr); - if (ret != ReturnCode::Ok) - return ReturnCode::CommandFail; - } - AnimatedSpinner spinner{cout}; auto on_success = [this, &spinner](mp::RestoreReply& reply) { From b776f5f41d536282ad0edef37ddcca497b2460ef Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 20 Apr 2023 07:48:50 -0700 Subject: [PATCH 181/627] [rpc] add reply field --- src/rpc/multipass.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index a5d04ccba81..ed092a60c73 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -512,4 +512,5 @@ message RestoreRequest { message RestoreReply { string log_line = 1; + string reply_message = 2; } From 1064b8c4770145460bc58e1d88a0dab095f8aaad Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 20 Apr 2023 07:49:31 -0700 Subject: [PATCH 182/627] [client] factor out common code --- src/client/cli/cmd/common_callbacks.h | 17 ++++++++++++++--- src/client/cli/cmd/remote_settings_handler.cpp | 16 +++------------- src/client/cli/cmd/restore.cpp | 3 +-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/client/cli/cmd/common_callbacks.h b/src/client/cli/cmd/common_callbacks.h index f0f3090086d..02f24858d60 100644 --- a/src/client/cli/cmd/common_callbacks.h +++ b/src/client/cli/cmd/common_callbacks.h @@ -31,8 +31,21 @@ auto make_logging_spinner_callback(AnimatedSpinner& spinner, std::ostream& strea { return [&spinner, &stream](const Reply& reply, grpc::ClientReaderWriterInterface*) { if (!reply.log_line().empty()) - { spinner.print(stream, reply.log_line()); + }; +} + +template +auto make_reply_spinner_callback(AnimatedSpinner& spinner, std::ostream& stream) +{ + return [&spinner, &stream](const Reply& reply, grpc::ClientReaderWriterInterface*) { + if (!reply.log_line().empty()) + spinner.print(stream, reply.log_line()); + + if (const auto& msg = reply.reply_message(); !msg.empty()) + { + spinner.stop(); + spinner.start(msg); } }; } @@ -42,9 +55,7 @@ auto make_iterative_spinner_callback(AnimatedSpinner& spinner, Terminal& term) { return [&spinner, &term](const Reply& reply, grpc::ClientReaderWriterInterface* client) { if (!reply.log_line().empty()) - { spinner.print(term.cerr(), reply.log_line()); - } if (reply.password_requested()) { diff --git a/src/client/cli/cmd/remote_settings_handler.cpp b/src/client/cli/cmd/remote_settings_handler.cpp index 77158e34012..319126c6d56 100644 --- a/src/client/cli/cmd/remote_settings_handler.cpp +++ b/src/client/cli/cmd/remote_settings_handler.cpp @@ -17,6 +17,7 @@ #include "remote_settings_handler.h" #include "animated_spinner.h" +#include "common_callbacks.h" #include #include @@ -119,21 +120,10 @@ class RemoteSet : public RemoteSettingsCmd set_request.set_val(val.toStdString()); mp::AnimatedSpinner spinner{cout}; - auto streaming_callback = [this, - &spinner](mp::SetReply& reply, - grpc::ClientReaderWriterInterface* client) { - if (const auto& msg = reply.log_line(); !msg.empty()) - spinner.print(cerr, reply.log_line()); - - if (const auto& msg = reply.reply_message(); !msg.empty()) - { - spinner.stop(); - spinner.start(msg); - } - }; [[maybe_unused]] auto ret = - dispatch(&RpcMethod::set, set_request, on_success, on_failure, streaming_callback); + dispatch(&RpcMethod::set, set_request, on_success, on_failure, + mp::make_reply_spinner_callback(spinner, cerr)); assert(ret == mp::ReturnCode::Ok && "should have thrown otherwise"); } }; diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index f293bfdac06..8581fcdad1b 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -53,9 +53,8 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) return standard_failure_handler_for(name(), cerr, status); }; - spinner.start("Restoring snapshot "); return dispatch(&RpcMethod::restore, request, on_success, on_failure, - make_logging_spinner_callback(spinner, cerr)); + make_reply_spinner_callback(spinner, cerr)); } std::string cmd::Restore::name() const From 7089661f26d186058dc3dbf4c970a81e50aa08ff Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 20 Apr 2023 07:50:54 -0700 Subject: [PATCH 183/627] [daemon] take auto snapshot and return progress messages --- src/daemon/daemon.cpp | 46 +++++++++++++++++++++++++++++++++++++++++-- src/daemon/daemon.h | 3 +++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 4ef239731f7..8265672f7bd 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2442,10 +2442,40 @@ try mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; - { // TODO@snapshots replace placeholder implementation + RestoreReply reply; + const auto& instance_name = request->instance(); + auto [instance_trail, status] = find_instance_and_react(operative_instances, deleted_instances, instance_name, + require_operative_instances_reaction); + + if (status.ok()) + { + assert(instance_trail.index() == 0); + auto* vm_ptr = std::get<0>(instance_trail)->second.get(); + assert(vm_ptr); + + using St = VirtualMachine::State; + if (auto state = vm_ptr->current_state(); state != St::off && state != St::stopped) + return status_promise->set_value( + grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only restore snapshots to stopped instances."}); + + const auto spec_it = vm_instance_specs.find(instance_name); + assert(spec_it != vm_instance_specs.end() && "missing instance specs"); + + if (!request->destructive()) + { + reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); + + const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, "", + fmt::format("Before restoring {}", request->snapshot())); + + reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), + /* sticky = */ true); + } + + // TODO@snapshots replace placeholder implementation + reply_msg(server, "Restoring snapshot"); mpl::log(mpl::Level::debug, category, "Restore placeholder"); - RestoreReply reply; auto snapshot_name = request->snapshot(); server->Write(reply); @@ -3155,3 +3185,15 @@ void mp::Daemon::wait_update_manifests_all_and_optionally_applied_force(const bo update_manifests_all_task.start_timer(); } } + +template +void mp::Daemon::reply_msg(grpc::ServerReaderWriterInterface* server, std::string&& msg, bool sticky) +{ + Reply reply{}; + if (sticky) + reply.set_reply_message(fmt::format("{}\n", std::forward(msg))); + else + reply.set_reply_message(std::forward(msg)); + + server->Write(reply); +} diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 61a46ab9fe1..75ec63ec9da 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -177,6 +177,9 @@ public slots: // it is applied in Daemon::find wherever the image info fetching is involved, aka non-only-blueprints case void wait_update_manifests_all_and_optionally_applied_force(const bool force_manifest_network_download); + template + void reply_msg(grpc::ServerReaderWriterInterface* server, std::string&& msg, bool sticky = false); + std::unique_ptr config; std::unordered_map vm_instance_specs; std::unordered_map operative_instances; From 8565738714417cc711e0442ac542f2fe181b834c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 24 May 2023 08:56:49 -0700 Subject: [PATCH 184/627] [daemon] write status to server reply --- src/daemon/daemon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 8265672f7bd..a27d9397687 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2456,7 +2456,7 @@ try using St = VirtualMachine::State; if (auto state = vm_ptr->current_state(); state != St::off && state != St::stopped) return status_promise->set_value( - grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only restore snapshots to stopped instances."}); + grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only restore snapshots of stopped instances."}); const auto spec_it = vm_instance_specs.find(instance_name); assert(spec_it != vm_instance_specs.end() && "missing instance specs"); @@ -2481,7 +2481,7 @@ try server->Write(reply); } - status_promise->set_value(grpc::Status::OK); + status_promise->set_value(status); } catch (const std::exception& e) { From 18e0d2495ae5eb1343d0cf7cc068502a611f92e4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 25 May 2023 19:32:30 +0100 Subject: [PATCH 185/627] [vm] Default to yes on auto snapshot question Accept empty answer and default to yes on the snapshot question asking whether an automatic snapshot should be taken on restore. --- src/client/cli/cmd/restore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 8581fcdad1b..5f2da5b70b8 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -141,11 +141,11 @@ bool cmd::Restore::confirm_destruction(const QString& instance_name) { static constexpr auto prompt_text = "Do you want to take a snapshot of {} before discarding its current state? (Yes/no)"; - static constexpr auto invalid_input = "Please answer yes/no"; + static constexpr auto invalid_input = "Please answer Yes/no"; mp::PlainPrompter prompter(term); auto answer = prompter.prompt(fmt::format(prompt_text, instance_name)); - while (!std::regex_match(answer, yes) && !std::regex_match(answer, no)) + while (!answer.empty() && !std::regex_match(answer, yes) && !std::regex_match(answer, no)) answer = prompter.prompt(invalid_input); return std::regex_match(answer, no); From 837cc63e61ca43bc8ef057384c4bcaa45b2ea3eb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 17:53:44 +0100 Subject: [PATCH 186/627] [vm] Add method to restore a snapshot --- include/multipass/virtual_machine.h | 1 + src/platform/backends/shared/base_virtual_machine.cpp | 6 ++++++ src/platform/backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 4 ++++ 5 files changed, 13 insertions(+) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index f5e6c136698..b45f2008b72 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -91,6 +91,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; + virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; VirtualMachine::State state; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index d037b850c23..d7dab3f95f0 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -16,6 +16,7 @@ */ #include "base_virtual_machine.h" +#include "daemon/vm_specs.h" // TODO@snapshots move this #include #include @@ -281,4 +282,9 @@ std::string BaseVirtualMachine::generate_snapshot_name() const return fmt::format("snapshot{}", snapshot_count + 1); } +void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& specs) +{ + // TODO@ricab implement +} + } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 0f5381d6a82..c1258296456 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -60,6 +60,7 @@ class BaseVirtualMachine : public VirtualMachine // derived classes is a big refactor std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; + void restore_snapshot(const std::string& name, VMSpecs& specs) override; void load_snapshots(const QDir& snapshot_dir) override; protected: diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 38d3504fa02..3ed73f65b32 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -70,6 +70,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 9f9af45f683..e2245c8388e 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -134,6 +134,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + void restore_snapshot(const std::string& name, VMSpecs& specs) override + { + } + void load_snapshots(const QDir&) override { } From 10f792aa25b09bd2db4806a46f5093d7d917588f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 18:44:13 +0100 Subject: [PATCH 187/627] [vm] Implement generic part of snapshot restoring --- .../backends/shared/base_virtual_machine.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index d7dab3f95f0..48d04e857e4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -284,7 +284,23 @@ std::string BaseVirtualMachine::generate_snapshot_name() const void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& specs) { - // TODO@ricab implement + using St = VirtualMachine::State; + + std::unique_lock lock{snapshot_mutex}; + assert(state == St::off || state == St::stopped); + + auto snapshot = snapshots.at(name); // TODO@snapshots convert out_of_range exception, here and `get_snapshot` + + assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); + assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); + + specs.state = snapshot->get_state(); + specs.num_cores = snapshot->get_num_cores(); + specs.mem_size = snapshot->get_mem_size(); + specs.mounts = snapshot->get_mounts(); + specs.metadata = snapshot->get_metadata(); + + head_snapshot = snapshot; } } // namespace multipass From 3c1b38ee074516e677ee54d14e112a94da29bfd8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 18:50:06 +0100 Subject: [PATCH 188/627] [vm] Extract constant to indicate file overwriting --- .../backends/shared/base_virtual_machine.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 48d04e857e4..e77ca4ad1e9 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -45,6 +45,7 @@ constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; constexpr auto index_digits = 4; // these two go together constexpr auto max_snapshots = 1000ull; // replace suffix with uz for size_t in C++23 +constexpr auto yes_overwrite = true; } // namespace namespace multipass @@ -255,23 +256,22 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const mp::write_json(head_snapshot->serialize(), snapshot_path); - auto overwrite = true; QFile head_file{head_path}; auto rollback_head_file = - sg::make_scope_guard([this, &head_path, &head_file, overwrite, old_head = head_snapshot->get_parent_name(), + sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), existed = head_file.exists()]() noexcept { // best effort, ignore returns if (!existed) head_file.remove(); else - mp::top_catch_all(vm_name, [&head_path, &old_head, overwrite] { - MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, overwrite); + mp::top_catch_all(vm_name, [&head_path, &old_head] { + MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, yes_overwrite); }); }); - MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), overwrite); - MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), overwrite); + MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); + MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), yes_overwrite); rollback_snapshot_file.dismiss(); rollback_head_file.dismiss(); @@ -294,6 +294,9 @@ void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& spec assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); + // TODO@ricab attempt rollback on failure + // snapshot->apply(); + specs.state = snapshot->get_state(); specs.num_cores = snapshot->get_num_cores(); specs.mem_size = snapshot->get_mem_size(); From 5af0b8d75e9293ba7c62423de09a9a94b0640a78 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 18:52:54 +0100 Subject: [PATCH 189/627] [vm] Extract method to persist head snapshot name --- src/platform/backends/shared/base_virtual_machine.cpp | 7 ++++++- src/platform/backends/shared/base_virtual_machine.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e77ca4ad1e9..6611cb64b0a 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -270,13 +270,18 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const }); }); - MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); + persist_head_snapshot_name(head_path); MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), yes_overwrite); rollback_snapshot_file.dismiss(); rollback_head_file.dismiss(); } +void BaseVirtualMachine::persist_head_snapshot_name(const QString& head_path) const +{ + MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); +} + std::string BaseVirtualMachine::generate_snapshot_name() const { return fmt::format("snapshot{}", snapshot_count + 1); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index c1258296456..ceca30c12db 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -76,6 +76,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& snapshot_dir) const; + void persist_head_snapshot_name(const QString& head_path) const; std::string generate_snapshot_name() const; private: From aff67576da83fa49e23c621fc4f437fd4f0a923b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:33:03 +0100 Subject: [PATCH 190/627] [vm] Extract method to derive head path Extract method to derive the path to save the name of the head snapshot at. --- src/platform/backends/shared/base_virtual_machine.cpp | 8 +++++++- src/platform/backends/shared/base_virtual_machine.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6611cb64b0a..c55a28df54e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -247,7 +247,7 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const .arg(QString::fromStdString(head_snapshot->get_name()), snapshot_extension); auto snapshot_path = snapshot_dir.filePath(snapshot_filename); - auto head_path = snapshot_dir.filePath(head_filename); + auto head_path = derive_head_path(snapshot_dir); auto count_path = snapshot_dir.filePath(count_filename); auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filename]() noexcept { @@ -277,6 +277,11 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const rollback_head_file.dismiss(); } +QString BaseVirtualMachine::derive_head_path(const QDir& snapshot_dir) const +{ + return snapshot_dir.filePath(head_filename); +} + void BaseVirtualMachine::persist_head_snapshot_name(const QString& head_path) const { MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); @@ -296,6 +301,7 @@ void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& spec auto snapshot = snapshots.at(name); // TODO@snapshots convert out_of_range exception, here and `get_snapshot` + // TODO@snapshots convert into runtime_errors (persisted info could have been tampered with) assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index ceca30c12db..a1934a05217 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -77,6 +77,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& snapshot_dir) const; void persist_head_snapshot_name(const QString& head_path) const; + QString derive_head_path(const QDir& snapshot_dir) const; std::string generate_snapshot_name() const; private: From ee728a09d4a05a54e8bfad88b3cb81b6f64b9af5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 19:04:33 +0100 Subject: [PATCH 191/627] [vm] Persist head snapshot name on restore --- include/multipass/virtual_machine.h | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 3 ++- src/platform/backends/shared/base_virtual_machine.h | 2 +- tests/mock_virtual_machine.h | 2 +- tests/stub_virtual_machine.h | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index b45f2008b72..35fc7ced3cf 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -91,7 +91,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; - virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; + virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; VirtualMachine::State state; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c55a28df54e..2e1ae9bcea0 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -292,7 +292,7 @@ std::string BaseVirtualMachine::generate_snapshot_name() const return fmt::format("snapshot{}", snapshot_count + 1); } -void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& specs) +void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) { using St = VirtualMachine::State; @@ -315,6 +315,7 @@ void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& spec specs.metadata = snapshot->get_metadata(); head_snapshot = snapshot; + persist_head_snapshot_name(derive_head_path(snapshot_dir)); } } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a1934a05217..79b144bd9f1 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -60,7 +60,7 @@ class BaseVirtualMachine : public VirtualMachine // derived classes is a big refactor std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; - void restore_snapshot(const std::string& name, VMSpecs& specs) override; + void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; void load_snapshots(const QDir& snapshot_dir) override; protected: diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 3ed73f65b32..7ea687ad173 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -70,7 +70,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); - MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); + MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index e2245c8388e..a46827bf36b 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -134,7 +134,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - void restore_snapshot(const std::string& name, VMSpecs& specs) override + void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override { } From e085f989b4df4d9cb0fd6ab726e8b2ed10a50f10 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:56:12 +0100 Subject: [PATCH 192/627] [vm] Add and call backend specific snapshot apply With placeholder implementation for now. --- include/multipass/snapshot.h | 1 + src/platform/backends/qemu/qemu_snapshot.cpp | 5 +++++ src/platform/backends/qemu/qemu_snapshot.h | 1 + src/platform/backends/shared/base_snapshot.h | 8 ++++++++ src/platform/backends/shared/base_virtual_machine.cpp | 2 +- tests/stub_snapshot.h | 4 ++++ 6 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 57c76ee1928..c859f78ad79 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -59,6 +59,7 @@ class Snapshot : private DisabledCopyMove virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits + virtual void apply() = 0; }; } // namespace multipass diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 1b5d200b0bd..2e07aa1408a 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -74,6 +74,11 @@ void mp::QemuSnapshot::erase_impl() // TODO@snapshots throw NotImplementedOnThisBackendException{"Snapshot erasing"}; } +void mp::QemuSnapshot::apply_impl() // TODO@snapshots +{ + // TODO@snapshots implement +} + QString mp::QemuSnapshot::make_tag() const { return snapshot_template.arg(get_name().c_str()); diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 1de53cd0ae0..e8f03553cad 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -34,6 +34,7 @@ class QemuSnapshot : public BaseSnapshot protected: void capture_impl() override; void erase_impl() override; + void apply_impl() override; private: QString make_tag() const; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index f5d680a75ff..4655b4ec5c8 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -60,10 +60,12 @@ class BaseSnapshot : public Snapshot void capture() final; void erase() final; + void apply() final; protected: virtual void capture_impl() = 0; virtual void erase_impl() = 0; + virtual void apply_impl() = 0; private: struct InnerJsonTag @@ -178,4 +180,10 @@ inline void multipass::BaseSnapshot::erase() erase_impl(); } +inline void multipass::BaseSnapshot::apply() +{ + const std::unique_lock lock{mutex}; + apply_impl(); +} + #endif // MULTIPASS_BASE_SNAPSHOT_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2e1ae9bcea0..925e4be5e95 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -306,7 +306,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); // TODO@ricab attempt rollback on failure - // snapshot->apply(); + snapshot->apply(); specs.state = snapshot->get_state(); specs.num_cores = snapshot->get_num_cores(); diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 46bd7199ae5..4c2ad7d17b3 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -102,6 +102,10 @@ struct StubSnapshot : public Snapshot { } + void apply() override + { + } + std::unordered_map mounts; QJsonObject metadata; }; From 2f98a069585fb8acbecfc159d4985c8ed0d27f6f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 19:27:27 +0100 Subject: [PATCH 193/627] [vm] Avoid head-snapshot update when restoring it --- src/platform/backends/shared/base_virtual_machine.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 925e4be5e95..1a638c3186b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -314,8 +314,11 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s specs.mounts = snapshot->get_mounts(); specs.metadata = snapshot->get_metadata(); - head_snapshot = snapshot; - persist_head_snapshot_name(derive_head_path(snapshot_dir)); + if (head_snapshot != snapshot) + { + head_snapshot = snapshot; + persist_head_snapshot_name(derive_head_path(snapshot_dir)); + } } } // namespace multipass From a43f6b4421bf36082a0712bed621e35e54f57ba4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 21:38:21 +0100 Subject: [PATCH 194/627] [vm] Implement best-effort rollback for restore --- .../backends/shared/base_virtual_machine.cpp | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1a638c3186b..85342e32868 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -129,7 +129,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn throw SnapshotNameTaken{vm_name, snapshot_name}; } - auto rollback_on_failure = sg::make_scope_guard( + auto rollback_on_failure = sg::make_scope_guard( // best effort to rollback [this, it = it, old_head = head_snapshot, old_count = snapshot_count]() mutable noexcept { if (old_head != head_snapshot) { @@ -305,9 +305,22 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); - // TODO@ricab attempt rollback on failure snapshot->apply(); + const auto head_path = derive_head_path(snapshot_dir); + auto rollback = // best effort to rollback + sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { + mp::top_catch_all(vm_name, [this, &head_path, &old_head, &old_specs, &specs] { + old_head->apply(); + specs = old_specs; + if (old_head != head_snapshot) + { + head_snapshot = old_head; + persist_head_snapshot_name(head_path); + } + }); + }); + specs.state = snapshot->get_state(); specs.num_cores = snapshot->get_num_cores(); specs.mem_size = snapshot->get_mem_size(); @@ -317,8 +330,10 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s if (head_snapshot != snapshot) { head_snapshot = snapshot; - persist_head_snapshot_name(derive_head_path(snapshot_dir)); + persist_head_snapshot_name(head_path); } + + rollback.dismiss(); } } // namespace multipass From 4e2465afc6e7d596c5eb497002afa3e8d748bd96 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 25 May 2023 13:10:30 +0100 Subject: [PATCH 195/627] [vm] Acknowledge unused type to make clang happy --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 85342e32868..5b71164e6e2 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -294,7 +294,7 @@ std::string BaseVirtualMachine::generate_snapshot_name() const void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) { - using St = VirtualMachine::State; + using St [[maybe_unused]] = VirtualMachine::State; std::unique_lock lock{snapshot_mutex}; assert(state == St::off || state == St::stopped); From 5a8c181409856223346b884728c15bb4a8f707e2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 25 May 2023 21:00:28 +0100 Subject: [PATCH 196/627] [daemon] Delegate snapshot restoring on VM --- src/daemon/daemon.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a27d9397687..791f8fa903a 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2458,25 +2458,23 @@ try return status_promise->set_value( grpc::Status{grpc::INVALID_ARGUMENT, "Multipass can only restore snapshots of stopped instances."}); - const auto spec_it = vm_instance_specs.find(instance_name); + auto spec_it = vm_instance_specs.find(instance_name); assert(spec_it != vm_instance_specs.end() && "missing instance specs"); + const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); - const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, "", + const auto snapshot = vm_ptr->take_snapshot(vm_dir, spec_it->second, "", fmt::format("Before restoring {}", request->snapshot())); reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), /* sticky = */ true); } - // TODO@snapshots replace placeholder implementation reply_msg(server, "Restoring snapshot"); - mpl::log(mpl::Level::debug, category, "Restore placeholder"); - - auto snapshot_name = request->snapshot(); + vm_ptr->restore_snapshot(vm_dir, request->snapshot(), spec_it->second); server->Write(reply); } From d78960597f0e4b623d1efb1e9b737f9a603bee72 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 25 May 2023 21:24:18 +0100 Subject: [PATCH 197/627] [daemon] Persist VMs after restoring snapshots --- src/daemon/daemon.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 791f8fa903a..1a719f2d36e 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2475,6 +2475,7 @@ try reply_msg(server, "Restoring snapshot"); vm_ptr->restore_snapshot(vm_dir, request->snapshot(), spec_it->second); + persist_instances(); server->Write(reply); } From 5f1310ee5a3a50581d6b6528d75dad1f0f74ced2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 21:53:03 +0100 Subject: [PATCH 198/627] [vm] Extract restore rollback's guts --- .../backends/shared/base_virtual_machine.cpp | 23 +++++++++++-------- .../backends/shared/base_virtual_machine.h | 6 +++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 5b71164e6e2..ec6b15ffa93 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -292,6 +292,18 @@ std::string BaseVirtualMachine::generate_snapshot_name() const return fmt::format("snapshot{}", snapshot_count + 1); } +void BaseVirtualMachine::restore_rollback_guts(const QString& head_path, const std::shared_ptr& old_head, + const VMSpecs& old_specs, VMSpecs& specs) +{ + old_head->apply(); + specs = old_specs; + if (old_head != head_snapshot) + { + head_snapshot = old_head; + persist_head_snapshot_name(head_path); + } +} + void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) { using St [[maybe_unused]] = VirtualMachine::State; @@ -310,15 +322,8 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s const auto head_path = derive_head_path(snapshot_dir); auto rollback = // best effort to rollback sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { - mp::top_catch_all(vm_name, [this, &head_path, &old_head, &old_specs, &specs] { - old_head->apply(); - specs = old_specs; - if (old_head != head_snapshot) - { - head_snapshot = old_head; - persist_head_snapshot_name(head_path); - } - }); + mp::top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_guts, this, head_path, old_head, old_specs, + specs); }); specs.state = snapshot->get_state(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 79b144bd9f1..dbfcdbe7075 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -72,14 +72,20 @@ class BaseVirtualMachine : public VirtualMachine private: template void log_latest_snapshot(LockT lock) const; + void load_generic_snapshot_info(const QDir& snapshot_dir); void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); + void persist_head_snapshot(const QDir& snapshot_dir) const; void persist_head_snapshot_name(const QString& head_path) const; + QString derive_head_path(const QDir& snapshot_dir) const; std::string generate_snapshot_name() const; + void restore_rollback_guts(const QString& head_path, const std::shared_ptr& old_head, + const VMSpecs& old_specs, VMSpecs& specs); + private: using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; From 9ef172b293fec888daf57c0005721ec76fdb303f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:32:13 +0100 Subject: [PATCH 199/627] [vm] Extract making the rollback guard for restore --- .../backends/shared/base_virtual_machine.cpp | 14 +++++++++----- .../backends/shared/base_virtual_machine.h | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index ec6b15ffa93..a3618f01bff 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -292,9 +292,17 @@ std::string BaseVirtualMachine::generate_snapshot_name() const return fmt::format("snapshot{}", snapshot_count + 1); } +auto BaseVirtualMachine::make_restore_rollback(const QString& head_path, VMSpecs& specs) +{ + return sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { + top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_guts, this, head_path, old_head, old_specs, specs); + }); +} + void BaseVirtualMachine::restore_rollback_guts(const QString& head_path, const std::shared_ptr& old_head, const VMSpecs& old_specs, VMSpecs& specs) { + // best effort only old_head->apply(); specs = old_specs; if (old_head != head_snapshot) @@ -320,11 +328,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s snapshot->apply(); const auto head_path = derive_head_path(snapshot_dir); - auto rollback = // best effort to rollback - sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { - mp::top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_guts, this, head_path, old_head, old_specs, - specs); - }); + auto rollback = make_restore_rollback(head_path, specs); specs.state = snapshot->get_state(); specs.num_cores = snapshot->get_num_cores(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index dbfcdbe7075..b91bd89a6b7 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -83,6 +83,7 @@ class BaseVirtualMachine : public VirtualMachine QString derive_head_path(const QDir& snapshot_dir) const; std::string generate_snapshot_name() const; + auto make_restore_rollback(const QString& head_path, VMSpecs& specs); void restore_rollback_guts(const QString& head_path, const std::shared_ptr& old_head, const VMSpecs& old_specs, VMSpecs& specs); From b5a0449f4ee10451f762813fa471def85a5a4de4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:18:56 +0100 Subject: [PATCH 200/627] [vm] Extract persist-head rollback guts --- .../backends/shared/base_virtual_machine.cpp | 20 ++++++++++++------- .../backends/shared/base_virtual_machine.h | 3 +++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index a3618f01bff..2e086d37c65 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -261,13 +261,7 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const auto rollback_head_file = sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), existed = head_file.exists()]() noexcept { - // best effort, ignore returns - if (!existed) - head_file.remove(); - else - mp::top_catch_all(vm_name, [&head_path, &old_head] { - MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, yes_overwrite); - }); + persist_head_rollback_guts(head_path, head_file, old_head, existed); }); persist_head_snapshot_name(head_path); @@ -277,6 +271,18 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const rollback_head_file.dismiss(); } +void BaseVirtualMachine::persist_head_rollback_guts(const QString& head_path, QFile& head_file, + const std::string& old_head, bool existed) const +{ + // best effort, ignore returns + if (!existed) + head_file.remove(); + else + top_catch_all(vm_name, [&head_path, &old_head] { + MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, yes_overwrite); + }); +} + QString BaseVirtualMachine::derive_head_path(const QDir& snapshot_dir) const { return snapshot_dir.filePath(head_filename); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index b91bd89a6b7..08443b66ef7 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -78,6 +78,9 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot(const QJsonObject& json); void persist_head_snapshot(const QDir& snapshot_dir) const; + void persist_head_rollback_guts(const QString& head_path, QFile& head_file, const std::string& old_head, + bool existed) const; + void persist_head_snapshot_name(const QString& head_path) const; QString derive_head_path(const QDir& snapshot_dir) const; From 03a7d86ed77413bfe69a1fb0d2e2739a79dd71a6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:26:02 +0100 Subject: [PATCH 201/627] [vm] Extract making rollback for head persist --- .../backends/shared/base_virtual_machine.cpp | 40 ++++++++++--------- .../backends/shared/base_virtual_machine.h | 5 ++- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2e086d37c65..7a41736b60e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -238,6 +238,26 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) } } +auto BaseVirtualMachine::make_head_file_rollback(const QString& head_path, QFile& head_file) const +{ + return sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), + existed = head_file.exists()]() noexcept { + head_file_rollback_guts(head_path, head_file, old_head, existed); + }); +} + +void BaseVirtualMachine::head_file_rollback_guts(const QString& head_path, QFile& head_file, + const std::string& old_head, bool existed) const +{ + // best effort, ignore returns + if (!existed) + head_file.remove(); + else + top_catch_all(vm_name, [&head_path, &old_head] { + MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, yes_overwrite); + }); +} + void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const { assert(head_snapshot); @@ -258,29 +278,13 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const QFile head_file{head_path}; - auto rollback_head_file = - sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), - existed = head_file.exists()]() noexcept { - persist_head_rollback_guts(head_path, head_file, old_head, existed); - }); + auto head_file_rollback = make_head_file_rollback(head_path, head_file); persist_head_snapshot_name(head_path); MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), yes_overwrite); rollback_snapshot_file.dismiss(); - rollback_head_file.dismiss(); -} - -void BaseVirtualMachine::persist_head_rollback_guts(const QString& head_path, QFile& head_file, - const std::string& old_head, bool existed) const -{ - // best effort, ignore returns - if (!existed) - head_file.remove(); - else - top_catch_all(vm_name, [&head_path, &old_head] { - MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, yes_overwrite); - }); + head_file_rollback.dismiss(); } QString BaseVirtualMachine::derive_head_path(const QDir& snapshot_dir) const diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 08443b66ef7..7197858cdc4 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -77,9 +77,10 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); + auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; + void head_file_rollback_guts(const QString& head_path, QFile& head_file, const std::string& old_head, + bool existed) const; void persist_head_snapshot(const QDir& snapshot_dir) const; - void persist_head_rollback_guts(const QString& head_path, QFile& head_file, const std::string& old_head, - bool existed) const; void persist_head_snapshot_name(const QString& head_path) const; From 5f5885ad6e5a30815c26d64a7a192605baed50d8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:48:17 +0100 Subject: [PATCH 202/627] [vm] Extract take-snapshot-rollback guts --- .../backends/shared/base_virtual_machine.cpp | 36 +++++++++++-------- .../backends/shared/base_virtual_machine.h | 5 ++- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7a41736b60e..24cadf5b710 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -111,6 +111,26 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri return snapshots.at(name); } +void BaseVirtualMachine::take_snapshot_rollback_guts(SnapshotMap::iterator it, std::shared_ptr& old_head, + size_t old_count) +{ + if (old_head != head_snapshot) + { + assert(it->second && "snapshot not created despite modified head"); + if (snapshot_count > old_count) // snapshot was created + { + assert(snapshot_count - old_count == 1); + --snapshot_count; + + mp::top_catch_all(vm_name, [it] { it->second->erase(); }); + } + + head_snapshot = std::move(old_head); + } + + snapshots.erase(it); +} + std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) { @@ -131,21 +151,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn auto rollback_on_failure = sg::make_scope_guard( // best effort to rollback [this, it = it, old_head = head_snapshot, old_count = snapshot_count]() mutable noexcept { - if (old_head != head_snapshot) - { - assert(it->second && "snapshot not created despite modified head"); - if (snapshot_count > old_count) // snapshot was created - { - assert(snapshot_count - old_count == 1); - --snapshot_count; - - mp::top_catch_all(vm_name, [it] { it->second->erase(); }); - } - - head_snapshot = std::move(old_head); - } - - snapshots.erase(it); + take_snapshot_rollback_guts(it, old_head, old_count); }); auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 7197858cdc4..99456965d01 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -70,6 +70,8 @@ class BaseVirtualMachine : public VirtualMachine const VMSpecs& specs) = 0; private: + using SnapshotMap = std::unordered_map>; + template void log_latest_snapshot(LockT lock) const; @@ -77,6 +79,8 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); + void take_snapshot_rollback_guts(SnapshotMap::iterator it, std::shared_ptr& old_head, size_t old_count); + auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; void head_file_rollback_guts(const QString& head_path, QFile& head_file, const std::string& old_head, bool existed) const; @@ -92,7 +96,6 @@ class BaseVirtualMachine : public VirtualMachine const VMSpecs& old_specs, VMSpecs& specs); private: - using SnapshotMap = std::unordered_map>; SnapshotMap snapshots; std::shared_ptr head_snapshot = nullptr; size_t snapshot_count = 0; // tracks the number of snapshots ever taken (regardless or deletes) From d460ac0d66e61be31930c85fd37e4bcfda941646 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 24 May 2023 22:52:39 +0100 Subject: [PATCH 203/627] [vm] Extract making take_snapshot rollback --- .../backends/shared/base_virtual_machine.cpp | 13 +++++++++---- src/platform/backends/shared/base_virtual_machine.h | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 24cadf5b710..7660d5db2ad 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -131,6 +131,14 @@ void BaseVirtualMachine::take_snapshot_rollback_guts(SnapshotMap::iterator it, s snapshots.erase(it); } +auto BaseVirtualMachine::make_take_snapshot_rollback(SnapshotMap::iterator it) +{ + return sg::make_scope_guard( // best effort to rollback + [this, it = it, old_head = head_snapshot, old_count = snapshot_count]() mutable noexcept { + take_snapshot_rollback_guts(it, old_head, old_count); + }); +} + std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) { @@ -149,10 +157,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn throw SnapshotNameTaken{vm_name, snapshot_name}; } - auto rollback_on_failure = sg::make_scope_guard( // best effort to rollback - [this, it = it, old_head = head_snapshot, old_count = snapshot_count]() mutable noexcept { - take_snapshot_rollback_guts(it, old_head, old_count); - }); + auto rollback_on_failure = make_take_snapshot_rollback(it); auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); ret->capture(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 99456965d01..a8fe01cba0d 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -79,6 +79,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); + auto make_take_snapshot_rollback(std::unordered_map>::iterator it); void take_snapshot_rollback_guts(SnapshotMap::iterator it, std::shared_ptr& old_head, size_t old_count); auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; From dee8660446db449ff4d3b5f79043fe5c10aae138 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 26 May 2023 21:24:06 +0100 Subject: [PATCH 204/627] [vm] Use type alias for param Review suggestion. Co-authored-by: ScottH <59572507+sharder996@users.noreply.github.com> Signed-off-by: Ricardo Abreu <6698114+ricab@users.noreply.github.com> --- src/platform/backends/shared/base_virtual_machine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a8fe01cba0d..96f1bb13bc3 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -79,7 +79,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); - auto make_take_snapshot_rollback(std::unordered_map>::iterator it); + auto make_take_snapshot_rollback(SnapshotMap::iterator it); void take_snapshot_rollback_guts(SnapshotMap::iterator it, std::shared_ptr& old_head, size_t old_count); auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; From 8899175f219171da683f743447fbc8c9086208e8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 26 May 2023 21:35:53 +0100 Subject: [PATCH 205/627] [vm] Improve method naming Prompted by review comment. --- .../backends/shared/base_virtual_machine.cpp | 19 ++++++++++--------- .../backends/shared/base_virtual_machine.h | 10 +++++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7660d5db2ad..9273305dc47 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -111,8 +111,8 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri return snapshots.at(name); } -void BaseVirtualMachine::take_snapshot_rollback_guts(SnapshotMap::iterator it, std::shared_ptr& old_head, - size_t old_count) +void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, + size_t old_count) { if (old_head != head_snapshot) { @@ -135,7 +135,7 @@ auto BaseVirtualMachine::make_take_snapshot_rollback(SnapshotMap::iterator it) { return sg::make_scope_guard( // best effort to rollback [this, it = it, old_head = head_snapshot, old_count = snapshot_count]() mutable noexcept { - take_snapshot_rollback_guts(it, old_head, old_count); + take_snapshot_rollback_helper(it, old_head, old_count); }); } @@ -253,12 +253,12 @@ auto BaseVirtualMachine::make_head_file_rollback(const QString& head_path, QFile { return sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), existed = head_file.exists()]() noexcept { - head_file_rollback_guts(head_path, head_file, old_head, existed); + head_file_rollback_helper(head_path, head_file, old_head, existed); }); } -void BaseVirtualMachine::head_file_rollback_guts(const QString& head_path, QFile& head_file, - const std::string& old_head, bool existed) const +void BaseVirtualMachine::head_file_rollback_helper(const QString& head_path, QFile& head_file, + const std::string& old_head, bool existed) const { // best effort, ignore returns if (!existed) @@ -316,12 +316,13 @@ std::string BaseVirtualMachine::generate_snapshot_name() const auto BaseVirtualMachine::make_restore_rollback(const QString& head_path, VMSpecs& specs) { return sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { - top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_guts, this, head_path, old_head, old_specs, specs); + top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_helper, this, head_path, old_head, old_specs, + specs); }); } -void BaseVirtualMachine::restore_rollback_guts(const QString& head_path, const std::shared_ptr& old_head, - const VMSpecs& old_specs, VMSpecs& specs) +void BaseVirtualMachine::restore_rollback_helper(const QString& head_path, const std::shared_ptr& old_head, + const VMSpecs& old_specs, VMSpecs& specs) { // best effort only old_head->apply(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 96f1bb13bc3..3598ad9597f 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -80,11 +80,11 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot(const QJsonObject& json); auto make_take_snapshot_rollback(SnapshotMap::iterator it); - void take_snapshot_rollback_guts(SnapshotMap::iterator it, std::shared_ptr& old_head, size_t old_count); + void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, size_t old_count); auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; - void head_file_rollback_guts(const QString& head_path, QFile& head_file, const std::string& old_head, - bool existed) const; + void head_file_rollback_helper(const QString& head_path, QFile& head_file, const std::string& old_head, + bool existed) const; void persist_head_snapshot(const QDir& snapshot_dir) const; void persist_head_snapshot_name(const QString& head_path) const; @@ -93,8 +93,8 @@ class BaseVirtualMachine : public VirtualMachine std::string generate_snapshot_name() const; auto make_restore_rollback(const QString& head_path, VMSpecs& specs); - void restore_rollback_guts(const QString& head_path, const std::shared_ptr& old_head, - const VMSpecs& old_specs, VMSpecs& specs); + void restore_rollback_helper(const QString& head_path, const std::shared_ptr& old_head, + const VMSpecs& old_specs, VMSpecs& specs); private: SnapshotMap snapshots; From e35dd74b83af1b1904ae00c89471790f031fbaf0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 30 May 2023 14:31:29 +0100 Subject: [PATCH 206/627] [qemu] Rename method to derive tag Make it more suitable for when the tag was already "made". --- src/platform/backends/qemu/qemu_snapshot.cpp | 4 ++-- src/platform/backends/qemu/qemu_snapshot.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 2e07aa1408a..787276f4b35 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -51,7 +51,7 @@ mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const mp::QemuVirtualMac void mp::QemuSnapshot::capture_impl() { - auto tag = make_tag(); + auto tag = derive_tag(); // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) @@ -79,7 +79,7 @@ void mp::QemuSnapshot::apply_impl() // TODO@snapshots // TODO@snapshots implement } -QString mp::QemuSnapshot::make_tag() const +QString mp::QemuSnapshot::derive_tag() const { return snapshot_template.arg(get_name().c_str()); } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index e8f03553cad..ffbb5670af0 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -37,7 +37,7 @@ class QemuSnapshot : public BaseSnapshot void apply_impl() override; private: - QString make_tag() const; + QString derive_tag() const; private: QString image_path; From d811c20fce33b2cca909c78cbf5d8f759f2e9eff Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 30 May 2023 17:44:51 +0100 Subject: [PATCH 207/627] [qemu] Implement snapshot applying --- src/platform/backends/qemu/qemu_snapshot.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 787276f4b35..c6b2146163f 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -37,6 +37,12 @@ std::unique_ptr make_capture_spec(const QString& tag, co return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, /* src_img = */ "", image_path); } + +std::unique_ptr make_restore_spec(const QString& tag, const QString& image_path) +{ + return std::make_unique(QStringList{"snapshot", "-a", tag, image_path}, + /* src_img = */ "", image_path); +} } // namespace mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, @@ -74,9 +80,16 @@ void mp::QemuSnapshot::erase_impl() // TODO@snapshots throw NotImplementedOnThisBackendException{"Snapshot erasing"}; } -void mp::QemuSnapshot::apply_impl() // TODO@snapshots +void mp::QemuSnapshot::apply_impl() // TODO@ricab deduplicate { - // TODO@snapshots implement + auto process = mpp::make_process(make_restore_spec(derive_tag(), image_path)); + + auto process_state = process->execute(); + if (!process_state.completed_successfully()) + { + throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", + process_state.failure_message(), process->read_all_standard_error())); + } } QString mp::QemuSnapshot::derive_tag() const From b1dacdd7731a9105b1ddeb840d98f731ffaa6806 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 30 May 2023 18:04:40 +0100 Subject: [PATCH 208/627] [qemu] Fix empty image name on snapshot loading --- src/platform/backends/qemu/qemu_snapshot.cpp | 3 ++- src/platform/backends/qemu/qemu_snapshot.h | 2 +- src/platform/backends/qemu/qemu_virtual_machine.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index c6b2146163f..719c9e915f5 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -51,7 +51,8 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comme { } -mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const mp::QemuVirtualMachine& vm) : BaseSnapshot(json, vm) +mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const mp::QemuVirtualMachine& vm, const QString& image_path) + : BaseSnapshot(json, vm), image_path{image_path} { } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index ffbb5670af0..e5ae9d01376 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -29,7 +29,7 @@ class QemuSnapshot : public BaseSnapshot public: QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, const QString& image_path); - QemuSnapshot(const QJsonObject& json, const QemuVirtualMachine& vm); + QemuSnapshot(const QJsonObject& json, const QemuVirtualMachine& vm, const QString& image_path); protected: void capture_impl() override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 5d2a666090e..94e6d24b908 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -625,5 +625,5 @@ auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, con auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr { - return std::make_shared(json, *this); + return std::make_shared(json, *this, desc.image.image_path); } From 780f9a1b51005819f26645f4f47c480cdbf2979a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 31 May 2023 11:41:57 +0100 Subject: [PATCH 209/627] [qemu] Extract `qemu-img` boilerplate in snapshots --- src/platform/backends/qemu/qemu_snapshot.cpp | 32 +++++++++----------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 719c9e915f5..d6651f5aaa7 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -43,6 +43,18 @@ std::unique_ptr make_restore_spec(const QString& tag, co return std::make_unique(QStringList{"snapshot", "-a", tag, image_path}, /* src_img = */ "", image_path); } + +void checked_exec_qemu_img(std::unique_ptr spec) +{ + auto process = mpp::make_process(std::move(spec)); + + auto process_state = process->execute(); + if (!process_state.completed_successfully()) + { + throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", + process_state.failure_message(), process->read_all_standard_error())); + } +} } // namespace mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, @@ -66,14 +78,7 @@ void mp::QemuSnapshot::capture_impl() throw std::runtime_error{fmt::format( "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; - auto process = mpp::make_process(make_capture_spec(tag, image_path)); - - auto process_state = process->execute(); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), process->read_all_standard_error())); - } + checked_exec_qemu_img(make_capture_spec(tag, image_path)); } void mp::QemuSnapshot::erase_impl() // TODO@snapshots @@ -81,16 +86,9 @@ void mp::QemuSnapshot::erase_impl() // TODO@snapshots throw NotImplementedOnThisBackendException{"Snapshot erasing"}; } -void mp::QemuSnapshot::apply_impl() // TODO@ricab deduplicate +void mp::QemuSnapshot::apply_impl() { - auto process = mpp::make_process(make_restore_spec(derive_tag(), image_path)); - - auto process_state = process->execute(); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), process->read_all_standard_error())); - } + checked_exec_qemu_img(make_restore_spec(derive_tag(), image_path)); } QString mp::QemuSnapshot::derive_tag() const From 4847771cccc643f5c0b9a9cb448d1512c37ae817 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 17 May 2023 14:32:03 -0700 Subject: [PATCH 210/627] [daemon] generalize function for multiple message types --- src/daemon/daemon.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 1a719f2d36e..66d38d01738 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -747,10 +747,18 @@ InstanceSelectionReport select_instances(InstanceTable& operative_instances, Ins for (const auto& name : names) { - if (seen_instances.insert(name).second) + std::string vm_name; + + using T = std::decay_t>; + if constexpr (std::is_same_v) + vm_name = name; + else if constexpr (std::is_same_v) + vm_name = name.instance_name(); + + if (seen_instances.insert(vm_name).second) { - auto trail = find_instance(operative_instances, deleted_instances, name); - rank_instance(name, trail, ret); + auto trail = find_instance(operative_instances, deleted_instances, vm_name); + rank_instance(vm_name, trail, ret); } } } @@ -1673,12 +1681,8 @@ try // clang-format on return grpc::Status::OK; }; - mp::InstanceNames instance_names; - for (const auto& n : request->instances_snapshots()) - instance_names.add_instance_name(n.instance_name()); - auto [instance_selection, status] = - select_instances_and_react(operative_instances, deleted_instances, instance_names.instance_name(), + select_instances_and_react(operative_instances, deleted_instances, request->instances_snapshots(), InstanceGroup::All, require_existing_instances_reaction); if (status.ok()) From 5511819eba38a7e2b5d28c8e081bac8bd8987981 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 17 May 2023 15:03:32 -0700 Subject: [PATCH 211/627] [daemon] query snapshot info --- src/daemon/daemon.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 66d38d01738..a8b9463ba48 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1671,12 +1671,17 @@ try // clang-format on // TODO@snapshots retrieve snapshot names to gather info auto fetch_snapshot_overview = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; - auto overview = response.mutable_snapshot_overview()->add_overview(); - auto fundamentals = overview->mutable_fundamentals(); - overview->set_instance_name(name); - fundamentals->set_snapshot_name("snapshot1"); - fundamentals->set_comment("This is a sample comment"); + auto get_snapshot_info = [&](std::shared_ptr snapshot) { + auto overview = response.mutable_snapshot_overview()->add_overview(); + auto fundamentals = overview->mutable_fundamentals(); + + overview->set_instance_name(name); + fundamentals->set_snapshot_name(snapshot->get_name()); + fundamentals->set_parent(snapshot->get_parent_name()); + fundamentals->set_comment(snapshot->get_comment()); + // TODO@snapshots populate snapshot creation time once available + }; return grpc::Status::OK; }; From b38730b4bf42198a3902e53624bc64ddf19494bd Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 17 May 2023 15:04:59 -0700 Subject: [PATCH 212/627] [daemon] load cli args into map --- src/daemon/daemon.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a8b9463ba48..a75bfdda92f 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1669,6 +1669,7 @@ try // clang-format on }; // TODO@snapshots retrieve snapshot names to gather info + std::unordered_map> instance_snapshots_map; auto fetch_snapshot_overview = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; @@ -1692,6 +1693,15 @@ try // clang-format on if (status.ok()) { + for (const auto& it : request->instances_snapshots()) + { + if (it.snapshot_name().empty()) + instance_snapshots_map[it.instance_name()] = {}; + else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); + entry == instance_snapshots_map.end() || !entry->second.empty()) + instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); + } + // TODO@snapshots change cmd logic after all info logic paths are added auto cmd = request->snapshot_overview() ? std::function(fetch_snapshot_overview) : std::function(fetch_instance_info); From 6379547e4938d0ddf48a2862a55a5e2444aa49b9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 17 May 2023 15:05:36 -0700 Subject: [PATCH 213/627] [daemon] populate info response with snapshot data --- src/daemon/daemon.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a75bfdda92f..0622dcadada 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1668,7 +1668,6 @@ try // clang-format on return grpc::Status::OK; }; - // TODO@snapshots retrieve snapshot names to gather info std::unordered_map> instance_snapshots_map; auto fetch_snapshot_overview = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; @@ -1684,6 +1683,29 @@ try // clang-format on // TODO@snapshots populate snapshot creation time once available }; + if (const auto& it = instance_snapshots_map.find(name); it == instance_snapshots_map.end()) + { + for (const auto& snapshot : vm.view_snapshots()) + get_snapshot_info(snapshot); + } + else + { + for (const auto& snapshot : it->second) + { + try + { + get_snapshot_info(vm.get_snapshot(snapshot)); + } + catch (const std::out_of_range&) + { + fmt::memory_buffer errors; + add_fmt_to(errors, "snapshot \"{}\" does not exist", snapshot); + + return grpc_status_for(errors, grpc::StatusCode::NOT_FOUND); + } + } + } + return grpc::Status::OK; }; @@ -1706,9 +1728,13 @@ try // clang-format on auto cmd = request->snapshot_overview() ? std::function(fetch_snapshot_overview) : std::function(fetch_instance_info); - cmd_vms(instance_selection.operative_selection, cmd); - deleted = true; - cmd_vms(instance_selection.deleted_selection, cmd); + status = cmd_vms(instance_selection.operative_selection, cmd); + + if (status.ok()) + { + deleted = true; + status = cmd_vms(instance_selection.deleted_selection, cmd); + } if (have_mounts && !MP_SETTINGS.get_as(mp::mounts_key)) mpl::log(mpl::Level::error, category, "Mounts have been disabled on this instance of Multipass"); From 6b8a4f2ae9cfdd6d49b33e37383c3742bc36a12d Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 19 May 2023 15:21:07 -0700 Subject: [PATCH 214/627] [daemon] fix error on empty response --- src/daemon/daemon.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 0622dcadada..39987ae0410 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1562,6 +1562,10 @@ try // clang-format on mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; InfoReply response; + + // Need to 'touch' a report in the response so formatters know what to do with an otherwise empty response + request->snapshot_overview() ? (void)response.mutable_snapshot_overview() + : (void)response.mutable_detailed_report(); bool have_mounts = false; bool deleted = false; auto fetch_instance_info = [&](VirtualMachine& vm) { @@ -1683,7 +1687,8 @@ try // clang-format on // TODO@snapshots populate snapshot creation time once available }; - if (const auto& it = instance_snapshots_map.find(name); it == instance_snapshots_map.end()) + if (const auto& it = instance_snapshots_map.find(name); + it == instance_snapshots_map.end() || it->second.empty()) { for (const auto& snapshot : vm.view_snapshots()) get_snapshot_info(snapshot); @@ -1720,7 +1725,7 @@ try // clang-format on if (it.snapshot_name().empty()) instance_snapshots_map[it.instance_name()] = {}; else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); - entry == instance_snapshots_map.end() || !entry->second.empty()) + entry != instance_snapshots_map.end() && !entry->second.empty()) instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); } From c5eb5666c45ea674ca4b1e387fe0dee82f5785a6 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 1 Jun 2023 07:48:32 -0700 Subject: [PATCH 215/627] [vm] add function to get num snapshots --- include/multipass/virtual_machine.h | 1 + src/platform/backends/shared/base_virtual_machine.cpp | 2 +- src/platform/backends/shared/base_virtual_machine.h | 4 ++++ tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 5 +++++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 35fc7ced3cf..7fbdcc774e9 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -88,6 +88,7 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; + virtual int get_num_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 9273305dc47..0152205d919 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -93,7 +93,7 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& return all_ipv4; } -auto multipass::BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista +auto BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista { SnapshotVista ret; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 3598ad9597f..62bc718c091 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -54,6 +54,10 @@ class BaseVirtualMachine : public VirtualMachine }; SnapshotVista view_snapshots() const noexcept override; + inline int get_num_snapshots() const noexcept override + { + return snapshots.size(); + } std::shared_ptr get_snapshot(const std::string& name) const override; // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 7ea687ad173..f123f5a83dd 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -67,6 +67,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::unique_ptr, make_native_mount_handler, (const SSHKeyProvider*, const std::string&, const VMMount&), (override)); MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); + MOCK_METHOD(int, get_num_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index a46827bf36b..32f31ee837d 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -123,6 +123,11 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + int get_num_snapshots() const noexcept override + { + return 0; + } + std::shared_ptr get_snapshot(const std::string&) const override { return {}; From a36b1c8a8954a9a9b1b4b9824a20908dbf767007 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 1 Jun 2023 07:48:53 -0700 Subject: [PATCH 216/627] [daemon] populate number of snapshots --- src/daemon/daemon.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 39987ae0410..76255f44c00 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1599,6 +1599,7 @@ try // clang-format on } } + instance_info->set_num_snapshots(vm.get_num_snapshots()); instance_info->set_image_release(original_release); instance_info->set_id(vm_image.id); From 8b34251bca8bf68042c3a57df0ea52494a2b9825 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 6 Jun 2023 14:06:06 -0700 Subject: [PATCH 217/627] [base vm] explicitly cast size_t to int --- src/platform/backends/shared/base_virtual_machine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 62bc718c091..37bc1ae28e7 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -56,7 +56,7 @@ class BaseVirtualMachine : public VirtualMachine SnapshotVista view_snapshots() const noexcept override; inline int get_num_snapshots() const noexcept override { - return snapshots.size(); + return static_cast(snapshots.size()); } std::shared_ptr get_snapshot(const std::string& name) const override; From e9d5815b227d5f4496a3397676f97dc8ed07386c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 6 Jun 2023 14:06:42 -0700 Subject: [PATCH 218/627] [daemon] change requests and code tidy up --- src/daemon/daemon.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 76255f44c00..ec439d0b49b 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -747,12 +747,11 @@ InstanceSelectionReport select_instances(InstanceTable& operative_instances, Ins for (const auto& name : names) { + using T = std::decay_t; std::string vm_name; - - using T = std::decay_t>; if constexpr (std::is_same_v) vm_name = name; - else if constexpr (std::is_same_v) + else vm_name = name.instance_name(); if (seen_instances.insert(vm_name).second) @@ -1675,6 +1674,7 @@ try // clang-format on std::unordered_map> instance_snapshots_map; auto fetch_snapshot_overview = [&](VirtualMachine& vm) { + fmt::memory_buffer errors; const auto& name = vm.vm_name; auto get_snapshot_info = [&](std::shared_ptr snapshot) { @@ -1704,15 +1704,12 @@ try // clang-format on } catch (const std::out_of_range&) { - fmt::memory_buffer errors; add_fmt_to(errors, "snapshot \"{}\" does not exist", snapshot); - - return grpc_status_for(errors, grpc::StatusCode::NOT_FOUND); } } } - return grpc::Status::OK; + return grpc_status_for(errors); }; auto [instance_selection, status] = @@ -1724,19 +1721,17 @@ try // clang-format on for (const auto& it : request->instances_snapshots()) { if (it.snapshot_name().empty()) - instance_snapshots_map[it.instance_name()] = {}; + instance_snapshots_map[it.instance_name()]; else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); - entry != instance_snapshots_map.end() && !entry->second.empty()) + entry == instance_snapshots_map.end() || !entry->second.empty()) instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); } - // TODO@snapshots change cmd logic after all info logic paths are added + // TODO@snapshots change cmd logic to include detailed snapshot info output auto cmd = request->snapshot_overview() ? std::function(fetch_snapshot_overview) : std::function(fetch_instance_info); - status = cmd_vms(instance_selection.operative_selection, cmd); - - if (status.ok()) + if ((status = cmd_vms(instance_selection.operative_selection, cmd)).ok()) { deleted = true; status = cmd_vms(instance_selection.deleted_selection, cmd); From cc34560b505391cadad3287f861290a3a9f5ca07 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 23 May 2023 00:06:04 -0700 Subject: [PATCH 219/627] [cli] refactor common code into util class --- src/client/cli/cmd/common_cli.cpp | 19 +++++++++++++++++++ src/client/cli/cmd/common_cli.h | 1 + src/client/cli/cmd/info.cpp | 23 ----------------------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/client/cli/cmd/common_cli.cpp b/src/client/cli/cmd/common_cli.cpp index 8cf1a27de5b..7c260d26f9d 100644 --- a/src/client/cli/cmd/common_cli.cpp +++ b/src/client/cli/cmd/common_cli.cpp @@ -76,6 +76,25 @@ mp::InstanceNames cmd::add_instance_names(const mp::ArgParser* parser, const std return instance_names; } +std::vector cmd::add_instance_and_snapshot_names(const mp::ArgParser* parser) +{ + std::vector instance_snapshot_names; + instance_snapshot_names.reserve(parser->positionalArguments().count()); + + for (const auto& arg : parser->positionalArguments()) + { + mp::InstanceSnapshotPair inst_snap_name; + auto index = arg.indexOf('.'); + inst_snap_name.set_instance_name(arg.left(index).toStdString()); + if (index >= 0) + inst_snap_name.set_snapshot_name(arg.right(arg.length() - index - 1).toStdString()); + + instance_snapshot_names.push_back(inst_snap_name); + } + + return instance_snapshot_names; +} + mp::ParseCode cmd::handle_format_option(const mp::ArgParser* parser, mp::Formatter** chosen_formatter, std::ostream& cerr) { diff --git a/src/client/cli/cmd/common_cli.h b/src/client/cli/cmd/common_cli.h index bd5eeea68f5..4d8d1a10d22 100644 --- a/src/client/cli/cmd/common_cli.h +++ b/src/client/cli/cmd/common_cli.h @@ -43,6 +43,7 @@ const QString format_option_name{"format"}; 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); InstanceNames add_instance_names(const ArgParser* parser, const std::string& default_name); +std::vector add_instance_and_snapshot_names(const ArgParser* parser); ParseCode handle_format_option(const ArgParser* parser, Formatter** chosen_formatter, std::ostream& cerr); std::string instance_action_message_for(const InstanceNames& instance_names, const std::string& action_name); ReturnCode run_cmd(const QStringList& args, const ArgParser* parser, std::ostream& cout, std::ostream& cerr); diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index b4cb504d001..34bb7d9143a 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -24,29 +24,6 @@ namespace mp = multipass; namespace cmd = multipass::cmd; -namespace -{ -// TODO@snapshots move this to common_cli once required by other commands -std::vector add_instance_and_snapshot_names(const mp::ArgParser* parser) -{ - std::vector instance_snapshot_names; - instance_snapshot_names.reserve(parser->positionalArguments().count()); - - for (const auto& arg : parser->positionalArguments()) - { - mp::InstanceSnapshotPair inst_snap_name; - auto index = arg.indexOf('.'); - inst_snap_name.set_instance_name(arg.left(index).toStdString()); - if (index >= 0) - inst_snap_name.set_snapshot_name(arg.right(arg.length() - index - 1).toStdString()); - - instance_snapshot_names.push_back(inst_snap_name); - } - - return instance_snapshot_names; -} -} // namespace - mp::ReturnCode cmd::Info::run(mp::ArgParser* parser) { auto ret = parse_args(parser); From 1d2b0ba07ec68f188179428c05cecb6847f2e405 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 7 Jun 2023 20:04:55 -0700 Subject: [PATCH 220/627] [daemon] use reference to instance name instead of copied value --- src/daemon/daemon.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index ec439d0b49b..848fd2429fe 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -748,16 +748,16 @@ InstanceSelectionReport select_instances(InstanceTable& operative_instances, Ins for (const auto& name : names) { using T = std::decay_t; - std::string vm_name; + const std::string* vm_name; if constexpr (std::is_same_v) - vm_name = name; + vm_name = &name; else - vm_name = name.instance_name(); + vm_name = &name.instance_name(); - if (seen_instances.insert(vm_name).second) + if (seen_instances.insert(*vm_name).second) { - auto trail = find_instance(operative_instances, deleted_instances, vm_name); - rank_instance(vm_name, trail, ret); + auto trail = find_instance(operative_instances, deleted_instances, *vm_name); + rank_instance(*vm_name, trail, ret); } } } From 5c78491305a532ecc9016b99d6363daf93bf0fb3 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 23 May 2023 00:26:06 -0700 Subject: [PATCH 221/627] [cli] adapt delete cli to accept snapshots --- src/client/cli/cmd/delete.cpp | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index dd8ce3b7af2..c9ffab14484 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -75,24 +75,23 @@ std::string cmd::Delete::name() const QString cmd::Delete::short_help() const { - return QStringLiteral("Delete instances"); + return QStringLiteral("Delete instances and snapshots"); } QString cmd::Delete::description() const { - return QStringLiteral("Delete instances, to be purged with the \"purge\" command,\n" - "or recovered with the \"recover\" command."); + return QStringLiteral("Delete instances and snapshots, to be purged with the \"purge\" command,\n" + "or recovered with the \"recover\" command (instances only)."); } mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) { - parser->addPositionalArgument("name", "Names of instances to delete", " [ ...]"); + parser->addPositionalArgument("name", "Names of instances and snapshots to delete", + "[.snapshot] [[.snapshot] ...]"); - QCommandLineOption all_option(all_option_name, "Delete all instances"); - parser->addOption(all_option); - - QCommandLineOption purge_option({"p", "purge"}, "Purge instances immediately"); - parser->addOption(purge_option); + QCommandLineOption all_option(all_option_name, "Delete all instances and snapshots"); + QCommandLineOption purge_option({"p", "purge"}, "Purge deleted instances and snapshots immediately"); + parser->addOptions({all_option, purge_option}); auto status = parser->commandParse(this); if (status != ParseCode::Ok) @@ -102,11 +101,21 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) if (parse_code != ParseCode::Ok) return parse_code; - request.mutable_instance_names()->CopyFrom(add_instance_names(parser)); + for (const auto& arg : parser->positionalArguments()) + { + if (arg.indexOf('.') >= 0 && !parser->isSet("purge")) + { + cerr << "Snapshots can only be purged (after deletion, they cannot be recovered). Please use the `--purge` " + "flag if that is what you want.\n"; + return mp::ParseCode::CommandLineError; + } + } + + for (const auto& item : add_instance_and_snapshot_names(parser)) + request.add_instances_snapshots()->CopyFrom(item); if (parser->isSet(purge_option)) - { request.set_purge(true); - } + return status; } From a0387a4dac3091f351ea03d2312d8ef2106bb84d Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 12 Jun 2023 08:02:27 -0700 Subject: [PATCH 222/627] [vm] use int type to track snapshot details --- src/platform/backends/shared/base_virtual_machine.cpp | 10 +++++----- src/platform/backends/shared/base_virtual_machine.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 0152205d919..302efd845a1 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -43,8 +43,8 @@ namespace constexpr auto snapshot_extension = "snapshot.json"; constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; -constexpr auto index_digits = 4; // these two go together -constexpr auto max_snapshots = 1000ull; // replace suffix with uz for size_t in C++23 +constexpr auto index_digits = 4; // these two go together +constexpr auto max_snapshots = 1000; constexpr auto yes_overwrite = true; } // namespace @@ -112,7 +112,7 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri } void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, - size_t old_count) + int old_count) { if (old_head != head_snapshot) { @@ -188,7 +188,7 @@ void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) { try { - snapshot_count = std::stoull(mpu::contents_of(snapshot_dir.filePath(count_filename))); + snapshot_count = std::stoi(mpu::contents_of(snapshot_dir.filePath(count_filename))); head_snapshot = snapshots.at(mpu::contents_of(snapshot_dir.filePath(head_filename))); } catch (FileOpenFailedException&) @@ -201,7 +201,7 @@ void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) template void BaseVirtualMachine::log_latest_snapshot(LockT lock) const { - auto num_snapshots = snapshots.size(); + auto num_snapshots = static_cast(snapshots.size()); auto parent_name = head_snapshot->get_parent_name(); assert(num_snapshots <= snapshot_count && "can't have more snapshots than were ever taken"); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 37bc1ae28e7..3ec2e3ec8fe 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -84,7 +84,7 @@ class BaseVirtualMachine : public VirtualMachine void load_snapshot(const QJsonObject& json); auto make_take_snapshot_rollback(SnapshotMap::iterator it); - void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, size_t old_count); + void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count); auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; void head_file_rollback_helper(const QString& head_path, QFile& head_file, const std::string& old_head, @@ -103,7 +103,7 @@ class BaseVirtualMachine : public VirtualMachine private: SnapshotMap snapshots; std::shared_ptr head_snapshot = nullptr; - size_t snapshot_count = 0; // tracks the number of snapshots ever taken (regardless or deletes) + int snapshot_count = 0; // tracks the number of snapshots ever taken (regardless or deletes) mutable std::recursive_mutex snapshot_mutex; }; From 991411154d69805690739928e01c8f1ca4f9fb20 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 23 May 2023 00:26:32 -0700 Subject: [PATCH 223/627] [rpc] add snapshot field to delete rpc msg --- src/rpc/multipass.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index ed092a60c73..dd3ef68172a 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -423,7 +423,7 @@ message RestartReply { } message DeleteRequest { - InstanceNames instance_names = 1; + repeated InstanceSnapshotPair instances_snapshots = 1; bool purge = 2; int32 verbosity_level = 3; } From 2d97c7f61cec9bfe0eb4203d3586663a676926ee Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 23 May 2023 00:28:08 -0700 Subject: [PATCH 224/627] [daemon] process adapted delete request and add snapshot delete placeholder code --- src/daemon/daemon.cpp | 47 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 848fd2429fe..a35af473731 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1148,6 +1148,23 @@ bool is_ipv4_valid(const std::string& ipv4) return true; } +template +std::unordered_map> map_snapshots_to_instances(const Instances& instances) +{ + std::unordered_map> instance_snapshots_map; + + for (const auto& it : instances) + { + if (it.snapshot_name().empty()) + instance_snapshots_map[it.instance_name()]; + else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); + entry == instance_snapshots_map.end() || !entry->second.empty()) + instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); + } + + return instance_snapshots_map; +} + void add_aliases(google::protobuf::RepeatedPtrField* container, const std::string& remote_name, const mp::VMImageInfo& info, const std::string& default_remote) { @@ -1718,14 +1735,7 @@ try // clang-format on if (status.ok()) { - for (const auto& it : request->instances_snapshots()) - { - if (it.snapshot_name().empty()) - instance_snapshots_map[it.instance_name()]; - else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); - entry == instance_snapshots_map.end() || !entry->second.empty()) - instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); - } + instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); // TODO@snapshots change cmd logic to include detailed snapshot info output auto cmd = @@ -2186,12 +2196,13 @@ try // clang-format on DeleteReply response; auto [instance_selection, status] = - select_instances_and_react(operative_instances, deleted_instances, request->instance_names().instance_name(), + select_instances_and_react(operative_instances, deleted_instances, request->instances_snapshots(), InstanceGroup::All, require_existing_instances_reaction); if (status.ok()) { const bool purge = request->purge(); + auto instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); for (const auto& vm_it : instance_selection.operative_selection) { @@ -2207,6 +2218,24 @@ try // clang-format on if (purge) { + // TODO@snapshots call method to delete snapshots + /* + if (const auto& it = instance_snapshots_map.find(name); + it == instance_snapshots_map.end() || it.second.empty()) + { + // Delete instance and snapshots + // release_resources(name); + // response.add_purged_instances(name); + } + else + { + for (const auto& snapshot_name : instance_snapshots_map[name]) + { + // Delete snapshot + } + } + */ + release_resources(name); response.add_purged_instances(name); } From dba427c82d4023d03e6784cafe39e965c7b6a86c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 13 Jun 2023 14:53:29 -0700 Subject: [PATCH 225/627] [cli] edit user strings and optimize loops --- src/client/cli/cmd/delete.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index c9ffab14484..92a067d9bdc 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -80,8 +80,10 @@ QString cmd::Delete::short_help() const QString cmd::Delete::description() const { - return QStringLiteral("Delete instances and snapshots, to be purged with the \"purge\" command,\n" - "or recovered with the \"recover\" command (instances only)."); + return QStringLiteral( + "Delete instances and snapshots. Instances can be purged immediately or later on," + "with the \"purge\" command. Until they are purged, instances can be recovered" + "with the \"recover\" command. Snapshots cannot be recovered after deletion and must be purged at once."); } mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) @@ -90,7 +92,7 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) "[.snapshot] [[.snapshot] ...]"); QCommandLineOption all_option(all_option_name, "Delete all instances and snapshots"); - QCommandLineOption purge_option({"p", "purge"}, "Purge deleted instances and snapshots immediately"); + QCommandLineOption purge_option({"p", "purge"}, "Purge specified instances and snapshots immediately"); parser->addOptions({all_option, purge_option}); auto status = parser->commandParse(this); @@ -101,21 +103,18 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) if (parse_code != ParseCode::Ok) return parse_code; - for (const auto& arg : parser->positionalArguments()) + request.set_purge(parser->isSet(purge_option)); + for (const auto& item : add_instance_and_snapshot_names(parser)) { - if (arg.indexOf('.') >= 0 && !parser->isSet("purge")) + if (item.has_snapshot_name() && !request.purge()) { cerr << "Snapshots can only be purged (after deletion, they cannot be recovered). Please use the `--purge` " "flag if that is what you want.\n"; return mp::ParseCode::CommandLineError; } - } - for (const auto& item : add_instance_and_snapshot_names(parser)) request.add_instances_snapshots()->CopyFrom(item); - - if (parser->isSet(purge_option)) - request.set_purge(true); + } return status; } From 57179eb6dd0f9439c981c38f6e94cc907538b729 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 14 Jun 2023 18:40:13 +0100 Subject: [PATCH 226/627] [daemon] Fix mapping of instance/snapshot args --- src/daemon/daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a35af473731..e9a408655c0 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1156,7 +1156,7 @@ std::unordered_map> map_snapshots_t for (const auto& it : instances) { if (it.snapshot_name().empty()) - instance_snapshots_map[it.instance_name()]; + instance_snapshots_map[it.instance_name()].clear(); else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); entry == instance_snapshots_map.end() || !entry->second.empty()) instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); From 6d90e94a13f9bcfeedb4a2dc5dc07c2f9dc70778 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 15 Jun 2023 21:37:33 +0100 Subject: [PATCH 227/627] [daemon] Factor out repeated getters To ease readability. --- src/daemon/daemon.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e9a408655c0..d53af22089a 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1155,11 +1155,14 @@ std::unordered_map> map_snapshots_t for (const auto& it : instances) { - if (it.snapshot_name().empty()) - instance_snapshots_map[it.instance_name()].clear(); - else if (const auto& entry = instance_snapshots_map.find(it.instance_name()); + const auto& instance = it.instance_name(); + const auto& snapshot = it.snapshot_name(); + + if (snapshot.empty()) + instance_snapshots_map[instance].clear(); + else if (const auto& entry = instance_snapshots_map.find(instance); entry == instance_snapshots_map.end() || !entry->second.empty()) - instance_snapshots_map[it.instance_name()].insert(it.snapshot_name()); + instance_snapshots_map[instance].insert(snapshot); } return instance_snapshots_map; From a2fec1e083337af54f47a0febd6b00b634533df9 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 15 Jun 2023 21:43:48 +0100 Subject: [PATCH 228/627] [daemon] Use known type instead of templating --- src/daemon/daemon.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d53af22089a..c04417c59f0 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1148,12 +1148,13 @@ bool is_ipv4_valid(const std::string& ipv4) return true; } -template -std::unordered_map> map_snapshots_to_instances(const Instances& instances) +using InstanceSnapshotPairs = google::protobuf::RepeatedPtrField; +std::unordered_map> +map_snapshots_to_instances(const InstanceSnapshotPairs& instances_snapshots) { std::unordered_map> instance_snapshots_map; - for (const auto& it : instances) + for (const auto& it : instances_snapshots) { const auto& instance = it.instance_name(); const auto& snapshot = it.snapshot_name(); From 66e738532540c8dc97c163af6c4568d4bbbb50a6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 14 Jun 2023 17:00:21 +0100 Subject: [PATCH 229/627] [vm] Assert stopped precondition to take snapshot --- .../backends/shared/base_virtual_machine.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 302efd845a1..8fe62d000d1 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -40,6 +40,12 @@ namespace mpu = multipass::utils; namespace { +using St = mp::VirtualMachine::State; +void assert_vm_stopped(St state) +{ + assert(state == St::off || state == St::stopped); +} + constexpr auto snapshot_extension = "snapshot.json"; constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; @@ -51,8 +57,7 @@ constexpr auto yes_overwrite = true; namespace multipass { -BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name) - : VirtualMachine(state, vm_name){}; +BaseVirtualMachine::BaseVirtualMachine(St state, const std::string& vm_name) : VirtualMachine(state, vm_name){}; BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; @@ -60,7 +65,7 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& { std::vector all_ipv4; - if (current_state() == State::running) + if (current_state() == St::running) { QString ip_a_output; @@ -146,6 +151,8 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn { std::unique_lock lock{snapshot_mutex}; + assert_vm_stopped(state); // precondition + if (snapshot_count > max_snapshots) throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded", max_snapshots)}; snapshot_name = name.empty() ? generate_snapshot_name() : name; @@ -336,10 +343,8 @@ void BaseVirtualMachine::restore_rollback_helper(const QString& head_path, const void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) { - using St [[maybe_unused]] = VirtualMachine::State; - std::unique_lock lock{snapshot_mutex}; - assert(state == St::off || state == St::stopped); + assert_vm_stopped(state); // precondition auto snapshot = snapshots.at(name); // TODO@snapshots convert out_of_range exception, here and `get_snapshot` From bb9e936c547b1847ff7befcd2a8ad01aefe63ff9 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 14 Jun 2023 19:41:51 +0100 Subject: [PATCH 230/627] [daemon] Handle operative snapshots in `delet` Accommodate deleting snapshots of operative instances in `delet`, with a placeholder implementation for now. --- src/daemon/daemon.cpp | 47 ++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 6c2dc4ab107..581907c9c7b 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2214,42 +2214,39 @@ try // clang-format on auto& instance = vm_it->second; assert(!vm_instance_specs[name].deleted); - if (instance->current_state() == VirtualMachine::State::delayed_shutdown) - delayed_shutdown_instances.erase(name); + auto contained_in_snapshots_map = instance_snapshots_map.count(name); + assert(contained_in_snapshots_map || !request->instances_snapshots_size()); - mounts[name].clear(); - instance->shutdown(); - - if (purge) + if (!contained_in_snapshots_map || instance_snapshots_map[name].empty()) // we need to delete the instance { - // TODO@snapshots call method to delete snapshots - /* - if (const auto& it = instance_snapshots_map.find(name); - it == instance_snapshots_map.end() || it.second.empty()) + if (instance->current_state() == VirtualMachine::State::delayed_shutdown) + delayed_shutdown_instances.erase(name); + + mounts[name].clear(); + instance->shutdown(); + + if (purge) { - // Delete instance and snapshots - // release_resources(name); - // response.add_purged_instances(name); + release_resources(name); + response.add_purged_instances(name); } else { - for (const auto& snapshot_name : instance_snapshots_map[name]) - { - // Delete snapshot - } + deleted_instances[name] = std::move(instance); + vm_instance_specs[name].deleted = true; } - */ - release_resources(name); - response.add_purged_instances(name); + operative_instances.erase(vm_it); } - else + else // we need to delete snapshots { - deleted_instances[name] = std::move(instance); - vm_instance_specs[name].deleted = true; - } + assert(purge && "precondition: snapshots can only be purged"); - operative_instances.erase(vm_it); + for (const auto& snapshot_name : instance_snapshots_map[name]) + { + (void)snapshot_name; // TODO@ricab delete snapshot + } + } } if (purge) From 7ce694cb468054a98e60130752dd5d6962375788 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 15 Jun 2023 21:45:55 +0100 Subject: [PATCH 231/627] [daemon] Typedef InstanceSnapshotsMap For readability. --- src/daemon/daemon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index c04417c59f0..6c2dc4ab107 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1149,10 +1149,10 @@ bool is_ipv4_valid(const std::string& ipv4) } using InstanceSnapshotPairs = google::protobuf::RepeatedPtrField; -std::unordered_map> -map_snapshots_to_instances(const InstanceSnapshotPairs& instances_snapshots) +using InstanceSnapshotsMap = std::unordered_map>; +InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& instances_snapshots) { - std::unordered_map> instance_snapshots_map; + InstanceSnapshotsMap instance_snapshots_map; for (const auto& it : instances_snapshots) { @@ -1693,7 +1693,7 @@ try // clang-format on return grpc::Status::OK; }; - std::unordered_map> instance_snapshots_map; + InstanceSnapshotsMap instance_snapshots_map; auto fetch_snapshot_overview = [&](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; From 40037b099dbd8a14ea8f64d92f8eafd43fe8807c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 15 Jun 2023 19:25:06 +0100 Subject: [PATCH 232/627] [daemon] Refactor `delet` Refactor `Daemon::delet`, to avoid repeating steps and accommodate deleting snapshots of both operative and deleted instances. --- src/daemon/daemon.cpp | 85 +++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 581907c9c7b..513b131b484 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2208,60 +2208,59 @@ try // clang-format on const bool purge = request->purge(); auto instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); - for (const auto& vm_it : instance_selection.operative_selection) + // start with deleted instances, to avoid iterator invalidation when moving instances there + for (const auto& selection : {instance_selection.deleted_selection, instance_selection.operative_selection}) { - const auto& name = vm_it->first; - auto& instance = vm_it->second; - assert(!vm_instance_specs[name].deleted); - - auto contained_in_snapshots_map = instance_snapshots_map.count(name); - assert(contained_in_snapshots_map || !request->instances_snapshots_size()); - - if (!contained_in_snapshots_map || instance_snapshots_map[name].empty()) // we need to delete the instance + for (const auto& vm_it : selection) { - if (instance->current_state() == VirtualMachine::State::delayed_shutdown) - delayed_shutdown_instances.erase(name); + const auto& name = vm_it->first; - mounts[name].clear(); - instance->shutdown(); + auto contained_in_snapshots_map = instance_snapshots_map.count(name); + assert(contained_in_snapshots_map || !request->instances_snapshots_size()); - if (purge) - { - release_resources(name); - response.add_purged_instances(name); - } - else + if (!contained_in_snapshots_map || instance_snapshots_map[name].empty()) // we're asked to delete the VM { - deleted_instances[name] = std::move(instance); - vm_instance_specs[name].deleted = true; - } + auto& instance = vm_it->second; + auto* erase_from = purge ? &deleted_instances : nullptr; // to begin with - operative_instances.erase(vm_it); - } - else // we need to delete snapshots - { - assert(purge && "precondition: snapshots can only be purged"); + if (!vm_instance_specs[name].deleted) + { + erase_from = &operative_instances; + if (instance->current_state() == VirtualMachine::State::delayed_shutdown) + delayed_shutdown_instances.erase(name); - for (const auto& snapshot_name : instance_snapshots_map[name]) - { - (void)snapshot_name; // TODO@ricab delete snapshot + mounts[name].clear(); + instance->shutdown(); + + if (!purge) + { + vm_instance_specs[name].deleted = true; + deleted_instances[name] = std::move(instance); + } + } + + if (purge) + { + response.add_purged_instances(name); + release_resources(name); + } + + if (erase_from) + erase_from->erase(vm_it); + + persist_instances(); } - } - } + else // we're asked to delete snapshots + { + assert(purge && "precondition: snapshots can only be purged"); - if (purge) - { - for (const auto& vm_it : instance_selection.deleted_selection) - { - const auto& name = vm_it->first; - assert(vm_instance_specs[name].deleted); - response.add_purged_instances(name); - release_resources(name); - deleted_instances.erase(vm_it); + for (const auto& snapshot_name : instance_snapshots_map[name]) + { + (void)snapshot_name; // TODO@ricab delete snapshot + } + } } } - - persist_instances(); } server->Write(response); From b0bcde1d860eff72876723958880030b24f70d98 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 15 Jun 2023 22:42:11 +0100 Subject: [PATCH 233/627] [daemon] Add a few debug logs on instance deletion --- src/daemon/daemon.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 513b131b484..b5f2e7158d2 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2225,6 +2225,7 @@ try // clang-format on if (!vm_instance_specs[name].deleted) { + mpl::log(mpl::Level::debug, category, fmt::format("Deleting instance: {}", name)); erase_from = &operative_instances; if (instance->current_state() == VirtualMachine::State::delayed_shutdown) delayed_shutdown_instances.erase(name); @@ -2236,13 +2237,18 @@ try // clang-format on { vm_instance_specs[name].deleted = true; deleted_instances[name] = std::move(instance); + mpl::log(mpl::Level::debug, category, fmt::format("Instance deleted: {}", name)); } } + else + mpl::log(mpl::Level::debug, category, fmt::format("Instance is already deleted: {}", name)); if (purge) { response.add_purged_instances(name); release_resources(name); + + mpl::log(mpl::Level::debug, category, fmt::format("Instance purged: {}", name)); } if (erase_from) @@ -2256,7 +2262,8 @@ try // clang-format on for (const auto& snapshot_name : instance_snapshots_map[name]) { - (void)snapshot_name; // TODO@ricab delete snapshot + mpl::log(mpl::Level::info, "TODO", // TODO@ricab actually delete snapshot + fmt::format("placeholder for snapshot deletion: {}.{}", name, snapshot_name)); } } } From 042c6c28dd9fd1ccc35284d13bdbddd6bac3c8a7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 15 Jun 2023 19:29:22 +0100 Subject: [PATCH 234/627] [daemon] Log also on `purge` and `recover` --- src/daemon/daemon.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index b5f2e7158d2..f97fbed547d 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1426,8 +1426,10 @@ try // clang-format on for (const auto& del : deleted_instances) { - release_resources(del.first); - response.add_purged_instances(del.first); + const auto& name = del.first; + release_resources(name); + response.add_purged_instances(name); + mpl::log(mpl::Level::debug, category, fmt::format("Instance purged: {}", name)); } deleted_instances.clear(); @@ -1973,6 +1975,7 @@ try // clang-format on operative_instances[name] = std::move(vm_it->second); deleted_instances.erase(vm_it); init_mounts(name); + mpl::log(mpl::Level::debug, category, fmt::format("Instance recovered: {}", name)); } persist_instances(); } From 5bedb303edc3976b341e6663cccfe0450664a1de Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 10:15:15 +0100 Subject: [PATCH 235/627] [daemon] Extract method to delete an instance --- src/daemon/daemon.cpp | 76 +++++++++++++++++++++++-------------------- src/daemon/daemon.h | 2 ++ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index f97fbed547d..d4d90918885 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2223,41 +2223,8 @@ try // clang-format on if (!contained_in_snapshots_map || instance_snapshots_map[name].empty()) // we're asked to delete the VM { - auto& instance = vm_it->second; - auto* erase_from = purge ? &deleted_instances : nullptr; // to begin with - - if (!vm_instance_specs[name].deleted) - { - mpl::log(mpl::Level::debug, category, fmt::format("Deleting instance: {}", name)); - erase_from = &operative_instances; - if (instance->current_state() == VirtualMachine::State::delayed_shutdown) - delayed_shutdown_instances.erase(name); - - mounts[name].clear(); - instance->shutdown(); - - if (!purge) - { - vm_instance_specs[name].deleted = true; - deleted_instances[name] = std::move(instance); - mpl::log(mpl::Level::debug, category, fmt::format("Instance deleted: {}", name)); - } - } - else - mpl::log(mpl::Level::debug, category, fmt::format("Instance is already deleted: {}", name)); - - if (purge) - { - response.add_purged_instances(name); - release_resources(name); - - mpl::log(mpl::Level::debug, category, fmt::format("Instance purged: {}", name)); - } - - if (erase_from) - erase_from->erase(vm_it); - - persist_instances(); + delete_vm(vm_it, purge, response); + persist_instances(); // TODO@ricab persist only at the end, but only if there were deleted instances } else // we're asked to delete snapshots { @@ -2951,6 +2918,45 @@ void mp::Daemon::create_vm(const CreateRequest* request, prepare_future_watcher->setFuture(QtConcurrent::run(make_vm_description)); } +// TODO@ricab use typedef for iterator +void mp::Daemon::delete_vm(std::unordered_map::iterator vm_it, + bool purge, DeleteReply& response) +{ + auto& [name, instance] = *vm_it; + auto* erase_from = purge ? &deleted_instances : nullptr; // to begin with + + if (!vm_instance_specs[name].deleted) + { + mpl::log(mpl::Level::debug, category, fmt::format("Deleting instance: {}", name)); + erase_from = &operative_instances; + if (instance->current_state() == VirtualMachine::State::delayed_shutdown) + delayed_shutdown_instances.erase(name); + + mounts[name].clear(); + instance->shutdown(); + + if (!purge) + { + vm_instance_specs[name].deleted = true; + deleted_instances[name] = std::move(instance); + mpl::log(mpl::Level::debug, category, fmt::format("Instance deleted: {}", name)); + } + } + else + mpl::log(mpl::Level::debug, category, fmt::format("Instance is already deleted: {}", name)); + + if (purge) + { + response.add_purged_instances(name); + release_resources(name); + + mpl::log(mpl::Level::debug, category, fmt::format("Instance purged: {}", name)); + } + + if (erase_from) + erase_from->erase(vm_it); +} + grpc::Status mp::Daemon::reboot_vm(VirtualMachine& vm) { if (vm.state == VirtualMachine::State::delayed_shutdown) diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 75ec63ec9da..07b004d28b5 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -148,6 +148,8 @@ public slots: void release_resources(const std::string& instance); void create_vm(const CreateRequest* request, grpc::ServerReaderWriterInterface* server, std::promise* status_promise, bool start); + void delete_vm(std::unordered_map::iterator vm_it, bool purge, + DeleteReply& response); grpc::Status reboot_vm(VirtualMachine& vm); grpc::Status shutdown_vm(VirtualMachine& vm, const std::chrono::milliseconds delay); grpc::Status cancel_vm_shutdown(const VirtualMachine& vm); From 02859bd41a951db6b8c2795b154e0c807b9c4147 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 10:58:19 +0100 Subject: [PATCH 236/627] [daemon] Pull typedef to header --- src/daemon/daemon.cpp | 19 +++++++++---------- src/daemon/daemon.h | 10 ++++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d4d90918885..25d584e1904 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -674,7 +674,10 @@ enum class InstanceGroup All }; -using InstanceTable = std::unordered_map; +// Hack to import typedef here, without making it part of the Daemon's public interface +// clang-format off +struct TapDaemon : private mp::Daemon { using Daemon::InstanceTable; }; // clang-format on +using InstanceTable = TapDaemon::InstanceTable; using InstanceTrail = std::variant>; // missing instances @@ -1202,12 +1205,10 @@ auto timeout_for(const int requested_timeout, const int blueprint_timeout) return mp::default_timeout; } -mp::SettingsHandler* -register_instance_mod(std::unordered_map& vm_instance_specs, - std::unordered_map& vm_instances, - const std::unordered_map& deleted_instances, - const std::unordered_set& preparing_instances, - std::function instance_persister) +mp::SettingsHandler* register_instance_mod(std::unordered_map& vm_instance_specs, + InstanceTable& vm_instances, const InstanceTable& deleted_instances, + const std::unordered_set& preparing_instances, + std::function instance_persister) { return MP_SETTINGS.register_handler(std::make_unique( vm_instance_specs, vm_instances, deleted_instances, preparing_instances, std::move(instance_persister))); @@ -2918,9 +2919,7 @@ void mp::Daemon::create_vm(const CreateRequest* request, prepare_future_watcher->setFuture(QtConcurrent::run(make_vm_description)); } -// TODO@ricab use typedef for iterator -void mp::Daemon::delete_vm(std::unordered_map::iterator vm_it, - bool purge, DeleteReply& response) +void mp::Daemon::delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteReply& response) { auto& [name, instance] = *vm_it; auto* erase_from = purge ? &deleted_instances : nullptr; // to begin with diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 07b004d28b5..a5094231ba6 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -42,6 +42,7 @@ namespace multipass { struct DaemonConfig; class SettingsHandler; + class Daemon : public QObject, public multipass::VMStatusMonitor { Q_OBJECT @@ -52,6 +53,8 @@ class Daemon : public QObject, public multipass::VMStatusMonitor void persist_instances(); protected: + using InstanceTable = std::unordered_map; + void on_resume() override; void on_stop() override; void on_shutdown() override; @@ -148,8 +151,7 @@ public slots: void release_resources(const std::string& instance); void create_vm(const CreateRequest* request, grpc::ServerReaderWriterInterface* server, std::promise* status_promise, bool start); - void delete_vm(std::unordered_map::iterator vm_it, bool purge, - DeleteReply& response); + void delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteReply& response); grpc::Status reboot_vm(VirtualMachine& vm); grpc::Status shutdown_vm(VirtualMachine& vm, const std::chrono::milliseconds delay); grpc::Status cancel_vm_shutdown(const VirtualMachine& vm); @@ -184,8 +186,8 @@ public slots: std::unique_ptr config; std::unordered_map vm_instance_specs; - std::unordered_map operative_instances; - std::unordered_map deleted_instances; + InstanceTable operative_instances; + InstanceTable deleted_instances; std::unordered_map> delayed_shutdown_instances; std::unordered_set allocated_mac_addrs; DaemonRpc daemon_rpc; From a49de12149963d125d412ce654b41f18afb79dca Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 11:30:41 +0100 Subject: [PATCH 237/627] [daemon] Persist deleted instances only once --- src/daemon/daemon.cpp | 15 ++++++++++++--- src/daemon/daemon.h | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 25d584e1904..729d18f7fd7 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2210,6 +2210,7 @@ try // clang-format on if (status.ok()) { const bool purge = request->purge(); + auto instances_dirty = false; auto instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); // start with deleted instances, to avoid iterator invalidation when moving instances there @@ -2224,8 +2225,7 @@ try // clang-format on if (!contained_in_snapshots_map || instance_snapshots_map[name].empty()) // we're asked to delete the VM { - delete_vm(vm_it, purge, response); - persist_instances(); // TODO@ricab persist only at the end, but only if there were deleted instances + instances_dirty |= delete_vm(vm_it, purge, response); } else // we're asked to delete snapshots { @@ -2239,6 +2239,9 @@ try // clang-format on } } } + + if (instances_dirty) + persist_instances(); } server->Write(response); @@ -2919,10 +2922,11 @@ void mp::Daemon::create_vm(const CreateRequest* request, prepare_future_watcher->setFuture(QtConcurrent::run(make_vm_description)); } -void mp::Daemon::delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteReply& response) +bool mp::Daemon::delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteReply& response) { auto& [name, instance] = *vm_it; auto* erase_from = purge ? &deleted_instances : nullptr; // to begin with + auto instances_dirty = false; if (!vm_instance_specs[name].deleted) { @@ -2938,6 +2942,8 @@ void mp::Daemon::delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteRepl { vm_instance_specs[name].deleted = true; deleted_instances[name] = std::move(instance); + + instances_dirty = true; mpl::log(mpl::Level::debug, category, fmt::format("Instance deleted: {}", name)); } } @@ -2949,11 +2955,14 @@ void mp::Daemon::delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteRepl response.add_purged_instances(name); release_resources(name); + instances_dirty = true; mpl::log(mpl::Level::debug, category, fmt::format("Instance purged: {}", name)); } if (erase_from) erase_from->erase(vm_it); + + return instances_dirty; } grpc::Status mp::Daemon::reboot_vm(VirtualMachine& vm) diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index a5094231ba6..451417afe16 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -151,7 +151,7 @@ public slots: void release_resources(const std::string& instance); void create_vm(const CreateRequest* request, grpc::ServerReaderWriterInterface* server, std::promise* status_promise, bool start); - void delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteReply& response); + bool delete_vm(InstanceTable::iterator vm_it, bool purge, DeleteReply& response); grpc::Status reboot_vm(VirtualMachine& vm); grpc::Status shutdown_vm(VirtualMachine& vm, const std::chrono::milliseconds delay); grpc::Status cancel_vm_shutdown(const VirtualMachine& vm); From bcb27f0331a328de7c361a1f180ac66a08dc40bc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 11:45:21 +0100 Subject: [PATCH 238/627] [daemon] Delegate snapshot deletion on VM --- include/multipass/virtual_machine.h | 1 + src/daemon/daemon.cpp | 5 +---- src/platform/backends/shared/base_virtual_machine.cpp | 6 ++++++ src/platform/backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 4 ++++ 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 7fbdcc774e9..9d3defa3187 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -92,6 +92,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; + virtual void delete_snapshot(const std::string& name) = 0; virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 729d18f7fd7..e9d70a9083b 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2232,10 +2232,7 @@ try // clang-format on assert(purge && "precondition: snapshots can only be purged"); for (const auto& snapshot_name : instance_snapshots_map[name]) - { - mpl::log(mpl::Level::info, "TODO", // TODO@ricab actually delete snapshot - fmt::format("placeholder for snapshot deletion: {}.{}", name, snapshot_name)); - } + vm_it->second->delete_snapshot(snapshot_name); } } } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 8fe62d000d1..b11446f1fc8 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -179,6 +179,12 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } +void BaseVirtualMachine::delete_snapshot(const std::string& name) +{ + mpl::log(mpl::Level::debug, vm_name, fmt::format("Deleting snapshot: {}", name)); + // TODO@ricab +} + void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) { std::unique_lock lock{snapshot_mutex}; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 3ec2e3ec8fe..6abcd411647 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -64,6 +64,7 @@ class BaseVirtualMachine : public VirtualMachine // derived classes is a big refactor std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; + void delete_snapshot(const std::string& name) override; void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; void load_snapshots(const QDir& snapshot_dir) override; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index f123f5a83dd..d8124d78149 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -71,6 +71,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 32f31ee837d..0359fade2e4 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -139,6 +139,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + void delete_snapshot(const std::string&) override + { + } + void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override { } From ee48e587bc570ec02f33ff635e602d202815241e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 12:17:29 +0100 Subject: [PATCH 239/627] [vm] Implement snapshot deletion --- .../backends/shared/base_virtual_machine.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index b11446f1fc8..085f06920f3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -182,7 +182,15 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn void BaseVirtualMachine::delete_snapshot(const std::string& name) { mpl::log(mpl::Level::debug, vm_name, fmt::format("Deleting snapshot: {}", name)); - // TODO@ricab + + auto snapshot = snapshots.at(name); + snapshot->erase(); + + for (auto& [ignore, other] : snapshots) + if (other->get_parent() == snapshot) + other->set_parent(snapshot->get_parent()); + + snapshots.erase(name); // TODO@ricab avoid searching again } void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) @@ -352,7 +360,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition - auto snapshot = snapshots.at(name); // TODO@snapshots convert out_of_range exception, here and `get_snapshot` + auto snapshot = snapshots.at(name); // TODO@snapshots convert out_of_range exception, here and wherever `at` is used // TODO@snapshots convert into runtime_errors (persisted info could have been tampered with) assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); From db3dbddd305395d66237d752686099d1302ff994 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 12:46:40 +0100 Subject: [PATCH 240/627] [vm] Add and use a non-const snapshot getter Keep it public as it will be useful to update snapshot's name or comment. --- include/multipass/virtual_machine.h | 1 + src/platform/backends/shared/base_virtual_machine.cpp | 9 +++++++-- src/platform/backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 5 +++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 9d3defa3187..fccfcf4a45e 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -90,6 +90,7 @@ class VirtualMachine : private DisabledCopyMove virtual SnapshotVista view_snapshots() const noexcept = 0; virtual int get_num_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; + virtual std::shared_ptr get_snapshot(const std::string& name) = 0; virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; virtual void delete_snapshot(const std::string& name) = 0; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 085f06920f3..2dbc6f3ee80 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -116,6 +116,11 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri return snapshots.at(name); } +std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) +{ + return std::const_pointer_cast(std::as_const(*this).get_snapshot(name)); +} + void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count) { @@ -210,7 +215,7 @@ void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) try { snapshot_count = std::stoi(mpu::contents_of(snapshot_dir.filePath(count_filename))); - head_snapshot = snapshots.at(mpu::contents_of(snapshot_dir.filePath(head_filename))); + head_snapshot = get_snapshot(mpu::contents_of(snapshot_dir.filePath(head_filename))); } catch (FileOpenFailedException&) { @@ -360,7 +365,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition - auto snapshot = snapshots.at(name); // TODO@snapshots convert out_of_range exception, here and wherever `at` is used + auto snapshot = get_snapshot(name); // TODO@snapshots convert into runtime_errors (persisted info could have been tampered with) assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 6abcd411647..a6b977d28ac 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -59,6 +59,7 @@ class BaseVirtualMachine : public VirtualMachine return static_cast(snapshots.size()); } std::shared_ptr get_snapshot(const std::string& name) const override; + std::shared_ptr get_snapshot(const std::string& name) override; // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index d8124d78149..15646dcfeb4 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -69,6 +69,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); MOCK_METHOD(int, get_num_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); + MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 0359fade2e4..d8621c4e09e 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -133,6 +133,11 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + std::shared_ptr get_snapshot(const std::string& name) override + { + return {}; + } + std::shared_ptr take_snapshot(const QDir&, const VMSpecs&, const std::string&, const std::string&) override { From 223edbc3c1fad1349a1270178c05cbb7cc32431c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 12:47:44 +0100 Subject: [PATCH 241/627] [vm] Improve error on missing snapshot --- src/platform/backends/shared/base_virtual_machine.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2dbc6f3ee80..38806b12453 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -113,7 +113,14 @@ auto BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) const { const std::unique_lock lock{snapshot_mutex}; - return snapshots.at(name); + try + { + return snapshots.at(name); + } + catch (const std::out_of_range&) + { + throw std::runtime_error(fmt::format("No such snapshot: {}.{}", vm_name, name)); + } } std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) From 0f935005d63b07db50f9350a01bf0d60284d8b66 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 12:52:24 +0100 Subject: [PATCH 242/627] [vm] Avoid searching for snapshot twice to delete --- .../backends/shared/base_virtual_machine.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 38806b12453..ee5ff8d5245 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -195,14 +195,19 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) { mpl::log(mpl::Level::debug, vm_name, fmt::format("Deleting snapshot: {}", name)); - auto snapshot = snapshots.at(name); - snapshot->erase(); + if (auto it = snapshots.find(name); it != snapshots.end()) + { + auto snapshot = it->second; + snapshot->erase(); - for (auto& [ignore, other] : snapshots) - if (other->get_parent() == snapshot) - other->set_parent(snapshot->get_parent()); + for (auto& [ignore, other] : snapshots) + if (other->get_parent() == snapshot) + other->set_parent(snapshot->get_parent()); - snapshots.erase(name); // TODO@ricab avoid searching again + snapshots.erase(it); + } + else + throw std::runtime_error(fmt::format("No such snapshot: {}.{}", vm_name, name)); } void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) From 905cb5ed2e59222dd380a9522ca6254955db1591 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 13:04:20 +0100 Subject: [PATCH 243/627] [vm] Use dedicated exception for missing snapshots Not that it needs any special treatment for the time being, but to avoid forming the same error message in different places. --- .../backends/shared/base_virtual_machine.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index ee5ff8d5245..df9e148eba9 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -41,10 +41,14 @@ namespace mpu = multipass::utils; namespace { using St = mp::VirtualMachine::State; -void assert_vm_stopped(St state) +class NoSuchSnapshot : public std::runtime_error { - assert(state == St::off || state == St::stopped); -} +public: + NoSuchSnapshot(const std::string& vm_name, const std::string& snapshot_name) + : std::runtime_error{fmt::format("No such snapshot: {}.{}", vm_name, snapshot_name)} + { + } +}; constexpr auto snapshot_extension = "snapshot.json"; constexpr auto head_filename = "snapshot-head"; @@ -52,6 +56,11 @@ constexpr auto count_filename = "snapshot-count"; constexpr auto index_digits = 4; // these two go together constexpr auto max_snapshots = 1000; constexpr auto yes_overwrite = true; + +void assert_vm_stopped(St state) +{ + assert(state == St::off || state == St::stopped); +} } // namespace namespace multipass @@ -119,7 +128,7 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri } catch (const std::out_of_range&) { - throw std::runtime_error(fmt::format("No such snapshot: {}.{}", vm_name, name)); + throw NoSuchSnapshot{vm_name, name}; } } @@ -207,7 +216,7 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) snapshots.erase(it); } else - throw std::runtime_error(fmt::format("No such snapshot: {}.{}", vm_name, name)); + throw NoSuchSnapshot{vm_name, name}; } void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) From 5083cdd8b0b75e7665947c1fa73f57f3f71c50d8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 13:05:15 +0100 Subject: [PATCH 244/627] [vm] Move method into unnamed namespace No reason for it to be a method. --- src/platform/backends/shared/base_virtual_machine.cpp | 10 +++++----- src/platform/backends/shared/base_virtual_machine.h | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index df9e148eba9..99937dd7b7c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -61,6 +61,11 @@ void assert_vm_stopped(St state) { assert(state == St::off || state == St::stopped); } + +QString derive_head_path(const QDir& snapshot_dir) +{ + return snapshot_dir.filePath(head_filename); +} } // namespace namespace multipass @@ -345,11 +350,6 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const head_file_rollback.dismiss(); } -QString BaseVirtualMachine::derive_head_path(const QDir& snapshot_dir) const -{ - return snapshot_dir.filePath(head_filename); -} - void BaseVirtualMachine::persist_head_snapshot_name(const QString& head_path) const { MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a6b977d28ac..a193e74d5d5 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -94,8 +94,6 @@ class BaseVirtualMachine : public VirtualMachine void persist_head_snapshot(const QDir& snapshot_dir) const; void persist_head_snapshot_name(const QString& head_path) const; - - QString derive_head_path(const QDir& snapshot_dir) const; std::string generate_snapshot_name() const; auto make_restore_rollback(const QString& head_path, VMSpecs& specs); From 6b3803e22fd6f0f2138c596a69690cdc8a8a9635 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 13:06:40 +0100 Subject: [PATCH 245/627] [vm] Move log to after snapshot deletion --- src/platform/backends/shared/base_virtual_machine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 99937dd7b7c..1cb211b3019 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -207,8 +207,6 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn void BaseVirtualMachine::delete_snapshot(const std::string& name) { - mpl::log(mpl::Level::debug, vm_name, fmt::format("Deleting snapshot: {}", name)); - if (auto it = snapshots.find(name); it != snapshots.end()) { auto snapshot = it->second; @@ -219,6 +217,7 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) other->set_parent(snapshot->get_parent()); snapshots.erase(it); + mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } else throw NoSuchSnapshot{vm_name, name}; From 253239b282ec45c12b1eb4570e65edf50cd1bf7c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 19:23:44 +0100 Subject: [PATCH 246/627] [snapshots] Drop unsustainable consts Make the Snapshot's pointer to its parent be non-const, adapting the necessary cascade of non-constness from there on. Also change function signatures such that parameters that can now be used for output come last. This allows a VM to update its head snapshot from another snapshot's parent (e.g. to delete the current head), while retaining the power to edit that snapshot (e.g. by applying it to revert a failed) restore. A VM's head snapshot must be editable, e.g. so that it can be applied when rolling back. Moreover, we need to be able to update the head snapshot from another snapshot's parent (e.g. to delete). In parallel, this requires Snapshot constructors to receive non-const references to VMs, which better reflects the fact that a snapshot can change the VM that it pertains to. --- include/multipass/snapshot.h | 3 +- .../libvirt/libvirt_virtual_machine.cpp | 2 +- .../libvirt/libvirt_virtual_machine.h | 3 +- .../backends/lxd/lxd_virtual_machine.cpp | 4 +-- .../backends/lxd/lxd_virtual_machine.h | 3 +- src/platform/backends/qemu/qemu_snapshot.cpp | 8 +++--- src/platform/backends/qemu/qemu_snapshot.h | 7 +++-- .../backends/qemu/qemu_virtual_machine.cpp | 6 ++-- .../backends/qemu/qemu_virtual_machine.h | 3 +- .../backends/shared/base_snapshot.cpp | 28 +++++++++---------- src/platform/backends/shared/base_snapshot.h | 27 +++++++++++------- .../backends/shared/base_virtual_machine.cpp | 2 +- .../backends/shared/base_virtual_machine.h | 4 +-- tests/stub_snapshot.h | 7 ++++- tests/test_base_virtual_machine.cpp | 4 +-- 15 files changed, 61 insertions(+), 50 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index c859f78ad79..7a280f9f9d6 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -41,6 +41,7 @@ class Snapshot : private DisabledCopyMove virtual std::string get_comment() const = 0; virtual std::string get_parent_name() const = 0; virtual std::shared_ptr get_parent() const = 0; + virtual std::shared_ptr get_parent() = 0; virtual int get_num_cores() const noexcept = 0; virtual MemorySize get_mem_size() const noexcept = 0; @@ -55,7 +56,7 @@ class Snapshot : private DisabledCopyMove virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file virtual void set_comment(const std::string&) = 0; - virtual void set_parent(std::shared_ptr) = 0; + virtual void set_parent(std::shared_ptr) = 0; virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 99cc32adfc2..33f78ea20b9 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -555,7 +555,7 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) } auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, const mp::VMSpecs& specs) + const VMSpecs& specs, std::shared_ptr parent) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index cab7f38e91f..d10580a14e2 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -61,8 +61,7 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine protected: std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const VMSpecs& specs) override; + const VMSpecs& specs, std::shared_ptr parent) override; private: DomainUPtr initialize_domain_info(virConnectPtr connection); diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index cb1464daf09..d0e16c4f5f8 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -483,8 +483,8 @@ mp::LXDVirtualMachine::make_native_mount_handler(const SSHKeyProvider* ssh_key_p return std::make_unique(manager, this, ssh_key_provider, target, mount); } -auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, - std::shared_ptr parent, const mp::VMSpecs& specs) +auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, + const VMSpecs& specs, std::shared_ptr parent) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index 965b33072be..f02da3fd539 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -59,8 +59,7 @@ class LXDVirtualMachine : public BaseVirtualMachine protected: std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const VMSpecs& specs) override; + const VMSpecs& specs, std::shared_ptr parent) override; private: const QString name; diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index d6651f5aaa7..bd3ff3a256f 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -57,13 +57,13 @@ void checked_exec_qemu_img(std::unique_ptr spec) } } // namespace -mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, const VMSpecs& specs, const QString& image_path) - : BaseSnapshot(name, comment, std::move(parent), specs), image_path{image_path} +mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + const QString& image_path, std::shared_ptr parent) + : BaseSnapshot(name, comment, specs, std::move(parent)), image_path{image_path} { } -mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const mp::QemuVirtualMachine& vm, const QString& image_path) +mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const QString& image_path, QemuVirtualMachine& vm) : BaseSnapshot(json, vm), image_path{image_path} { } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index e5ae9d01376..1d505123aaf 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -18,6 +18,7 @@ #ifndef MULTIPASS_QEMU_SNAPSHOT_H #define MULTIPASS_QEMU_SNAPSHOT_H +#include "qemu_virtual_machine.h" #include namespace multipass @@ -27,9 +28,9 @@ class QemuVirtualMachine; class QemuSnapshot : public BaseSnapshot { public: - QemuSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, - const VMSpecs& specs, const QString& image_path); - QemuSnapshot(const QJsonObject& json, const QemuVirtualMachine& vm, const QString& image_path); + QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, const QString& image_path, + std::shared_ptr parent); + QemuSnapshot(const QJsonObject& json, const QString& image_path, QemuVirtualMachine& vm); protected: void capture_impl() override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 94e6d24b908..0cd78b5ff39 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -616,14 +616,14 @@ mp::QemuVirtualMachine::MountArgs& mp::QemuVirtualMachine::modifiable_mount_args } auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, const mp::VMSpecs& specs) + const VMSpecs& specs, std::shared_ptr parent) -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise - return std::make_shared(name, comment, std::move(parent), specs, desc.image.image_path); + return std::make_shared(name, comment, specs, desc.image.image_path, std::move(parent)); } auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr { - return std::make_shared(json, *this, desc.image.image_path); + return std::make_shared(json, desc.image.image_path, *this); } diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 127a4a1fa81..ce2af41c5fd 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -75,8 +75,7 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const VMSpecs& specs) override; + const VMSpecs& specs, std::shared_ptr parent) override; private: void on_started(); diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index da95c33667a..cd4874e9e6a 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -63,7 +63,7 @@ std::unordered_map load_mounts(const QJsonArray& json) return mounts; } -std::shared_ptr find_parent(const QJsonObject& json, const mp::VirtualMachine& vm) +std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMachine& vm) { auto parent_name = json["parent"].toString().toStdString(); try @@ -78,19 +78,19 @@ std::shared_ptr find_parent(const QJsonObject& json, const m } } // namespace -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) - std::shared_ptr parent, int num_cores, MemorySize mem_size, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, - std::unordered_map mounts, QJsonObject metadata) + std::unordered_map mounts, QJsonObject metadata, + std::shared_ptr parent) : name{name}, comment{comment}, - parent{std::move(parent)}, num_cores{num_cores}, mem_size{mem_size}, disk_space{disk_space}, state{state}, mounts{std::move(mounts)}, - metadata{std::move(metadata)} + metadata{std::move(metadata)}, + parent{std::move(parent)} { if (name.empty()) throw std::runtime_error{"Snapshot names cannot be empty"}; @@ -102,28 +102,28 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme throw std::runtime_error{fmt::format("Invalid disk size for snapshot: {}", disk_bytes)}; } -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, const VMSpecs& specs) - : BaseSnapshot{name, comment, std::move(parent), specs.num_cores, specs.mem_size, specs.disk_space, - specs.state, specs.mounts, specs.metadata} +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + std::shared_ptr parent) + : BaseSnapshot{name, comment, specs.num_cores, specs.mem_size, specs.disk_space, + specs.state, specs.mounts, specs.metadata, std::move(parent)} { } -mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, const VirtualMachine& vm) +mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) : BaseSnapshot(InnerJsonTag{}, json["snapshot"].toObject(), vm) { } -mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, const VirtualMachine& vm) +mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm) : BaseSnapshot{json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment - find_parent(json, vm), // parent json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space static_cast(json["state"].toInt()), // state load_mounts(json["mounts"].toArray()), // mounts - json["metadata"].toObject()} // metadata + json["metadata"].toObject(), // metadata + find_parent(json, vm)} // parent { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 4655b4ec5c8..09ecd5aece8 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -34,14 +34,15 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, - const VMSpecs& specs); - BaseSnapshot(const QJsonObject& json, const VirtualMachine& vm); + BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + std::shared_ptr parent); + BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); std::string get_name() const override; std::string get_comment() const override; std::string get_parent_name() const override; std::shared_ptr get_parent() const override; + std::shared_ptr get_parent() override; int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; @@ -56,7 +57,7 @@ class BaseSnapshot : public Snapshot void set_name(const std::string& n) override; void set_comment(const std::string& c) override; - void set_parent(std::shared_ptr p) override; + void set_parent(std::shared_ptr p) override; void capture() final; void erase() final; @@ -71,15 +72,14 @@ class BaseSnapshot : public Snapshot struct InnerJsonTag { }; - BaseSnapshot(InnerJsonTag, const QJsonObject& json, const VirtualMachine& vm); - BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, - int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, - std::unordered_map mounts, QJsonObject metadata); + BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); + BaseSnapshot(const std::string& name, const std::string& comment, int num_cores, MemorySize mem_size, + MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, + QJsonObject metadata, std::shared_ptr parent); private: std::string name; std::string comment; - std::shared_ptr parent; // This class is non-copyable and having these const simplifies thread safety const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -89,6 +89,8 @@ class BaseSnapshot : public Snapshot const std::unordered_map mounts; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QJsonObject metadata; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + std::shared_ptr parent; + mutable std::recursive_mutex mutex; }; } // namespace multipass @@ -120,6 +122,11 @@ inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr std::shared_ptr +{ + return std::const_pointer_cast(std::as_const(*this).get_parent()); +} + inline int multipass::BaseSnapshot::get_num_cores() const noexcept { return num_cores; @@ -162,7 +169,7 @@ inline void multipass::BaseSnapshot::set_comment(const std::string& c) comment = c; } -inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) +inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) { const std::unique_lock lock{mutex}; parent = std::move(p); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1cb211b3019..b4af777f793 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -192,7 +192,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn auto rollback_on_failure = make_take_snapshot_rollback(it); - auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, head_snapshot, specs); + auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, specs, head_snapshot); ret->capture(); ++snapshot_count; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a193e74d5d5..993965a8e34 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -72,8 +72,8 @@ class BaseVirtualMachine : public VirtualMachine protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const VMSpecs& specs) = 0; + const VMSpecs& specs, + std::shared_ptr parent) = 0; private: using SnapshotMap = std::unordered_map>; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 4c2ad7d17b3..759785970c8 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -47,6 +47,11 @@ struct StubSnapshot : public Snapshot return nullptr; } + std::shared_ptr get_parent() override + { + return nullptr; + } + int get_num_cores() const noexcept override { return 0; @@ -90,7 +95,7 @@ struct StubSnapshot : public Snapshot { } - void set_parent(std::shared_ptr) override + void set_parent(std::shared_ptr) override { } diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index a3ccabc0fe9..088af34b76c 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -115,8 +115,8 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine protected: std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - std::shared_ptr parent, - const mp::VMSpecs& specs) override + const mp::VMSpecs& specs, + std::shared_ptr parent) override { return nullptr; } From df5c3cd66e73ee1f9f263fac530f59bba6feeec4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 19:26:31 +0100 Subject: [PATCH 247/627] [vm] Update the head snapshot on delete --- src/platform/backends/shared/base_virtual_machine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index b4af777f793..879759a65ed 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -216,6 +216,9 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) if (other->get_parent() == snapshot) other->set_parent(snapshot->get_parent()); + if (head_snapshot == snapshot) + head_snapshot = snapshot->get_parent(); + snapshots.erase(it); mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } From 4a4f310e9a15534e53b5a59373870a649693e684 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 19:38:47 +0100 Subject: [PATCH 248/627] [vm] Lock snapshot deletion --- src/platform/backends/shared/base_virtual_machine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 879759a65ed..ec8c2900ac1 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -207,6 +207,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn void BaseVirtualMachine::delete_snapshot(const std::string& name) { + std::unique_lock lock{snapshot_mutex}; if (auto it = snapshots.find(name); it != snapshots.end()) { auto snapshot = it->second; From 37938603441de3479c3ddec78c0c3f4cc77ec6aa Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 19:47:59 +0100 Subject: [PATCH 249/627] [vm] Remove obsolete assertion We can now have null parents after the first snapshot, because the head snapshot can get deleted --- src/platform/backends/shared/base_virtual_machine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index ec8c2900ac1..4e2533cfcb6 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -260,7 +260,6 @@ void BaseVirtualMachine::log_latest_snapshot(LockT lock) const auto parent_name = head_snapshot->get_parent_name(); assert(num_snapshots <= snapshot_count && "can't have more snapshots than were ever taken"); - assert(bool(head_snapshot->get_parent()) == bool(num_snapshots - 1) && "null parent this is the 1st snapshot"); if (auto log_detail_lvl = mpl::Level::debug; log_detail_lvl <= mpl::get_logging_level()) { From fd74ac230477881355cf3f70fc6427d9797d7bb6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 11:15:27 +0100 Subject: [PATCH 250/627] [vm] Add a directory param to `delete_snapshot` So that the VM can persist snapshot deletion. --- include/multipass/virtual_machine.h | 2 +- src/daemon/daemon.cpp | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- src/platform/backends/shared/base_virtual_machine.h | 2 +- tests/mock_virtual_machine.h | 2 +- tests/stub_virtual_machine.h | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index fccfcf4a45e..0322a68f1f8 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -93,7 +93,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(const std::string& name) = 0; virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; - virtual void delete_snapshot(const std::string& name) = 0; + virtual void delete_snapshot(const QDir& snapshot_dir, const std::string& name) = 0; virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e9d70a9083b..0a540fea48a 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2232,7 +2232,7 @@ try // clang-format on assert(purge && "precondition: snapshots can only be purged"); for (const auto& snapshot_name : instance_snapshots_map[name]) - vm_it->second->delete_snapshot(snapshot_name); + vm_it->second->delete_snapshot(instance_directory(name, *config), snapshot_name); } } } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4e2533cfcb6..73142b1b367 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -205,7 +205,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } -void BaseVirtualMachine::delete_snapshot(const std::string& name) +void BaseVirtualMachine::delete_snapshot(const QDir& /*snapshot_dir*/, const std::string& name) { std::unique_lock lock{snapshot_mutex}; if (auto it = snapshots.find(name); it != snapshots.end()) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 993965a8e34..fd5faf8fd55 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -65,7 +65,7 @@ class BaseVirtualMachine : public VirtualMachine // derived classes is a big refactor std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, const std::string& name, const std::string& comment) override; - void delete_snapshot(const std::string& name) override; + void delete_snapshot(const QDir& snapshot_dir, const std::string& name) override; void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; void load_snapshots(const QDir& snapshot_dir) override; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 15646dcfeb4..760b6d3c164 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -72,7 +72,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); - MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); + MOCK_METHOD(void, delete_snapshot, (const QDir& snapshot_dir, const std::string& name), (override)); MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index d8621c4e09e..191ecc703f0 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -144,7 +144,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - void delete_snapshot(const std::string&) override + void delete_snapshot(const QDir& snapshot_dir, const std::string&) override { } From ffd7ee69c9a05c17fe84263432abb29872b6c589 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 11:18:17 +0100 Subject: [PATCH 251/627] [daemon] Improve variable name Clarify what name we're referring when deleting (instance vs. snapshot). --- src/daemon/daemon.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 0a540fea48a..9dba556771a 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2218,12 +2218,13 @@ try // clang-format on { for (const auto& vm_it : selection) { - const auto& name = vm_it->first; + const auto& instance_name = vm_it->first; - auto contained_in_snapshots_map = instance_snapshots_map.count(name); + auto contained_in_snapshots_map = instance_snapshots_map.count(instance_name); assert(contained_in_snapshots_map || !request->instances_snapshots_size()); - if (!contained_in_snapshots_map || instance_snapshots_map[name].empty()) // we're asked to delete the VM + if (!contained_in_snapshots_map || + instance_snapshots_map[instance_name].empty()) // we're asked to delete the VM { instances_dirty |= delete_vm(vm_it, purge, response); } @@ -2231,8 +2232,8 @@ try // clang-format on { assert(purge && "precondition: snapshots can only be purged"); - for (const auto& snapshot_name : instance_snapshots_map[name]) - vm_it->second->delete_snapshot(instance_directory(name, *config), snapshot_name); + for (const auto& snapshot_name : instance_snapshots_map[instance_name]) + vm_it->second->delete_snapshot(instance_directory(instance_name, *config), snapshot_name); } } } From 69199f2118670effa82c8fa76ebf1a6ed495d9a5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 11:31:35 +0100 Subject: [PATCH 252/627] [vm] Persist head snapshot when deleted --- src/platform/backends/shared/base_virtual_machine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 73142b1b367..40b8b0bc73b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -218,7 +218,10 @@ void BaseVirtualMachine::delete_snapshot(const QDir& /*snapshot_dir*/, const std other->set_parent(snapshot->get_parent()); if (head_snapshot == snapshot) + { head_snapshot = snapshot->get_parent(); + persist_head_snapshot_name(derive_head_path(snapshot_dir)); + } snapshots.erase(it); mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); From ae483fb47e4c9baf00049622bce18486d165490e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 11:34:37 +0100 Subject: [PATCH 253/627] [vm] Handle persisting/loading empty head snapshot --- src/platform/backends/shared/base_virtual_machine.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 40b8b0bc73b..2b686b8a957 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -247,7 +247,9 @@ void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) try { snapshot_count = std::stoi(mpu::contents_of(snapshot_dir.filePath(count_filename))); - head_snapshot = get_snapshot(mpu::contents_of(snapshot_dir.filePath(head_filename))); + + auto head_name = mpu::contents_of(snapshot_dir.filePath(head_filename)); + head_snapshot = head_name.empty() ? nullptr : get_snapshot(head_name); } catch (FileOpenFailedException&) { @@ -357,7 +359,8 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const void BaseVirtualMachine::persist_head_snapshot_name(const QString& head_path) const { - MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); + auto head_name = head_snapshot ? head_snapshot->get_name() : ""; + MP_UTILS.make_file_with_content(head_path.toStdString(), head_name, yes_overwrite); } std::string BaseVirtualMachine::generate_snapshot_name() const From 0c4fa8136a656c9e681660e5781beec1c9542341 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 13:16:40 +0100 Subject: [PATCH 254/627] [vm] Implement function to find a snapshot's file So that we can delete it when deleting the snapshot. --- .../backends/shared/base_virtual_machine.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2b686b8a957..96deabaf6e7 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -66,6 +66,20 @@ QString derive_head_path(const QDir& snapshot_dir) { return snapshot_dir.filePath(head_filename); } + +QString find_snapshot_file(const QDir& snapshot_dir, const std::string& snapshot_name) +{ + // TODO@ricab extract pattern + auto pattern = QString{R"(????-%1.%2)"}.arg(QString::fromStdString(snapshot_name), snapshot_extension); + auto files = MP_FILEOPS.entryInfoList(snapshot_dir, {pattern}, QDir::Filter::Files | QDir::Filter::Readable); + + if (auto num_found = files.count(); !num_found) + throw std::runtime_error{fmt::format("Could not find snapshot file for pattern '{}'", pattern)}; + else if (num_found > 1) + throw std::runtime_error{fmt::format("Multiple snapshot files found for pattern '{}'", pattern)}; + + return files.first().filePath(); +} } // namespace namespace multipass From 69382ad645daf33712b42d805300439e0d48c716 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 13:17:59 +0100 Subject: [PATCH 255/627] [vm] Persist snapshot deletion --- .../backends/shared/base_virtual_machine.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 96deabaf6e7..386cbc27870 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -51,6 +51,7 @@ class NoSuchSnapshot : public std::runtime_error }; constexpr auto snapshot_extension = "snapshot.json"; +constexpr auto deleting_extension = ".deleting"; constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; constexpr auto index_digits = 4; // these two go together @@ -219,11 +220,22 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } -void BaseVirtualMachine::delete_snapshot(const QDir& /*snapshot_dir*/, const std::string& name) +void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) { std::unique_lock lock{snapshot_mutex}; + if (auto it = snapshots.find(name); it != snapshots.end()) { + auto snapshot_filename = find_snapshot_file(snapshot_dir, name); + auto deleting_filename = QString{"%1.%2"}.arg(snapshot_filename, deleting_extension); + + if (!QFile{snapshot_filename}.rename(deleting_filename)) + throw std::runtime_error{fmt::format("Failed to rename snapshot file: {}", snapshot_filename)}; + + auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filename, &snapshot_filename]() noexcept { + QFile{deleting_filename}.rename(snapshot_filename); // best effort, ignore return + }); + auto snapshot = it->second; snapshot->erase(); @@ -236,8 +248,13 @@ void BaseVirtualMachine::delete_snapshot(const QDir& /*snapshot_dir*/, const std head_snapshot = snapshot->get_parent(); persist_head_snapshot_name(derive_head_path(snapshot_dir)); } + rollback_snapshot_file.dismiss(); + + if (!QFile{deleting_filename}.remove()) + mpl::log(mpl::Level::warning, vm_name, + fmt::format("Could not delete temporary snapshot file: {}", deleting_filename)); - snapshots.erase(it); + snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } else From 823e82d20107b1bef20ec473a72a5beb78f045ec Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 13:37:42 +0100 Subject: [PATCH 256/627] [vm] Extract pattern forming for snapshot files --- .../backends/shared/base_virtual_machine.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 386cbc27870..644ad1d3d61 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -68,10 +68,20 @@ QString derive_head_path(const QDir& snapshot_dir) return snapshot_dir.filePath(head_filename); } +QString derive_index_string(int index) +{ + return QString{"%1"}.arg(index, index_digits, 10, QLatin1Char('0')); +} + +QString derive_snapshot_filename(const QString& index, const QString& name) +{ + return QString{"%1-%2.%3"}.arg(index, name, snapshot_extension); +} + QString find_snapshot_file(const QDir& snapshot_dir, const std::string& snapshot_name) { - // TODO@ricab extract pattern - auto pattern = QString{R"(????-%1.%2)"}.arg(QString::fromStdString(snapshot_name), snapshot_extension); + auto index_wildcard = "????"; + auto pattern = derive_snapshot_filename(index_wildcard, QString::fromStdString(snapshot_name)); auto files = MP_FILEOPS.entryInfoList(snapshot_dir, {pattern}, QDir::Filter::Files | QDir::Filter::Readable); if (auto num_found = files.count(); !num_found) @@ -363,9 +373,8 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const { assert(head_snapshot); - const auto snapshot_filename = QString{"%1-%2.%3"} - .arg(snapshot_count, index_digits, 10, QLatin1Char('0')) - .arg(QString::fromStdString(head_snapshot->get_name()), snapshot_extension); + const auto snapshot_filename = derive_snapshot_filename(derive_index_string(snapshot_count), + QString::fromStdString(head_snapshot->get_name())); auto snapshot_path = snapshot_dir.filePath(snapshot_filename); auto head_path = derive_head_path(snapshot_dir); From 6af4957879fd63f639b02a939aa0d62573baa093 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 16:22:38 +0100 Subject: [PATCH 257/627] [vm] Improve naming for a couple of variables --- .../backends/shared/base_virtual_machine.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 644ad1d3d61..2dfbb88fd3c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -236,14 +236,14 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st if (auto it = snapshots.find(name); it != snapshots.end()) { - auto snapshot_filename = find_snapshot_file(snapshot_dir, name); - auto deleting_filename = QString{"%1.%2"}.arg(snapshot_filename, deleting_extension); + auto snapshot_filepath = find_snapshot_file(snapshot_dir, name); + auto deleting_filepath = QString{"%1.%2"}.arg(snapshot_filepath, deleting_extension); - if (!QFile{snapshot_filename}.rename(deleting_filename)) - throw std::runtime_error{fmt::format("Failed to rename snapshot file: {}", snapshot_filename)}; + if (!QFile{snapshot_filepath}.rename(deleting_filepath)) + throw std::runtime_error{fmt::format("Failed to rename snapshot file: {}", snapshot_filepath)}; - auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filename, &snapshot_filename]() noexcept { - QFile{deleting_filename}.rename(snapshot_filename); // best effort, ignore return + auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { + QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return }); auto snapshot = it->second; @@ -260,9 +260,9 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st } rollback_snapshot_file.dismiss(); - if (!QFile{deleting_filename}.remove()) + if (!QFile{deleting_filepath}.remove()) mpl::log(mpl::Level::warning, vm_name, - fmt::format("Could not delete temporary snapshot file: {}", deleting_filename)); + fmt::format("Could not delete temporary snapshot file: {}", deleting_filepath)); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); From 282ec22a94bfb242f389c41eeb75ad19980ffc36 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 16:25:04 +0100 Subject: [PATCH 258/627] [vm] Improve error message --- src/platform/backends/shared/base_virtual_machine.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2dfbb88fd3c..dec60b98435 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -240,7 +240,8 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto deleting_filepath = QString{"%1.%2"}.arg(snapshot_filepath, deleting_extension); if (!QFile{snapshot_filepath}.rename(deleting_filepath)) - throw std::runtime_error{fmt::format("Failed to rename snapshot file: {}", snapshot_filepath)}; + throw std::runtime_error{ + fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return From 61ca7305dd0ac5596de3ecdae6b60427b333da3c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 16:35:00 +0100 Subject: [PATCH 259/627] [vm] Use QTemporaryDir for snapshots being deleted --- .../backends/shared/base_virtual_machine.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index dec60b98435..2430e5386a4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -33,6 +33,7 @@ #include #include #include +#include namespace mp = multipass; namespace mpl = multipass::logging; @@ -51,7 +52,6 @@ class NoSuchSnapshot : public std::runtime_error }; constexpr auto snapshot_extension = "snapshot.json"; -constexpr auto deleting_extension = ".deleting"; constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; constexpr auto index_digits = 4; // these two go together @@ -78,7 +78,7 @@ QString derive_snapshot_filename(const QString& index, const QString& name) return QString{"%1-%2.%3"}.arg(index, name, snapshot_extension); } -QString find_snapshot_file(const QDir& snapshot_dir, const std::string& snapshot_name) +QFileInfo find_snapshot_file(const QDir& snapshot_dir, const std::string& snapshot_name) { auto index_wildcard = "????"; auto pattern = derive_snapshot_filename(index_wildcard, QString::fromStdString(snapshot_name)); @@ -89,7 +89,7 @@ QString find_snapshot_file(const QDir& snapshot_dir, const std::string& snapshot else if (num_found > 1) throw std::runtime_error{fmt::format("Multiple snapshot files found for pattern '{}'", pattern)}; - return files.first().filePath(); + return files.first(); } } // namespace @@ -236,8 +236,14 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st if (auto it = snapshots.find(name); it != snapshots.end()) { - auto snapshot_filepath = find_snapshot_file(snapshot_dir, name); - auto deleting_filepath = QString{"%1.%2"}.arg(snapshot_filepath, deleting_extension); + auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, name); + auto snapshot_filepath = snapshot_fileinfo.filePath(); + + QTemporaryDir tmp_dir{}; + if (!tmp_dir.isValid()) + throw std::runtime_error{"Could not create temporary directory"}; + + auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); if (!QFile{snapshot_filepath}.rename(deleting_filepath)) throw std::runtime_error{ @@ -261,9 +267,6 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st } rollback_snapshot_file.dismiss(); - if (!QFile{deleting_filepath}.remove()) - mpl::log(mpl::Level::warning, vm_name, - fmt::format("Could not delete temporary snapshot file: {}", deleting_filepath)); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); From 7e51c7bdb2dbbad01c3a86ecaf2ec9b30d0dc817 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 16:45:06 +0100 Subject: [PATCH 260/627] [vm] Change order of steps to delete a snapshot To have a clear boundary between steps and can and can't be rolled back on failure. --- src/platform/backends/shared/base_virtual_machine.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2430e5386a4..74f13fa7351 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -256,10 +256,6 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto snapshot = it->second; snapshot->erase(); - for (auto& [ignore, other] : snapshots) - if (other->get_parent() == snapshot) - other->set_parent(snapshot->get_parent()); - if (head_snapshot == snapshot) { head_snapshot = snapshot->get_parent(); @@ -267,6 +263,10 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st } rollback_snapshot_file.dismiss(); + // No rollbacks from this point on + for (auto& [ignore, other] : snapshots) + if (other->get_parent() == snapshot) + other->set_parent(snapshot->get_parent()); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); From eff60c90c6093eb7bb740039144e9848da182579 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 16:47:42 +0100 Subject: [PATCH 261/627] [vm] Delete temporary dir when no longer needed --- .../backends/shared/base_virtual_machine.cpp | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 74f13fa7351..0cfaf71f6f3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -238,30 +238,33 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st { auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, name); auto snapshot_filepath = snapshot_fileinfo.filePath(); + auto snapshot = it->second; - QTemporaryDir tmp_dir{}; - if (!tmp_dir.isValid()) - throw std::runtime_error{"Could not create temporary directory"}; + { + QTemporaryDir tmp_dir{}; + if (!tmp_dir.isValid()) + throw std::runtime_error{"Could not create temporary directory"}; - auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); + auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); - if (!QFile{snapshot_filepath}.rename(deleting_filepath)) - throw std::runtime_error{ - fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; + if (!QFile{snapshot_filepath}.rename(deleting_filepath)) + throw std::runtime_error{ + fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; - auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { - QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return - }); + auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { + QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return + }); - auto snapshot = it->second; - snapshot->erase(); + snapshot->erase(); - if (head_snapshot == snapshot) - { - head_snapshot = snapshot->get_parent(); - persist_head_snapshot_name(derive_head_path(snapshot_dir)); + if (head_snapshot == snapshot) + { + head_snapshot = snapshot->get_parent(); + persist_head_snapshot_name(derive_head_path(snapshot_dir)); + } + + rollback_snapshot_file.dismiss(); } - rollback_snapshot_file.dismiss(); // No rollbacks from this point on for (auto& [ignore, other] : snapshots) From 0832e03bae93cb7498d1424fa1e3a4565408e167 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 17:48:36 +0100 Subject: [PATCH 262/627] [vm] Rollback head snapshot when erase fails --- .../backends/shared/base_virtual_machine.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 0cfaf71f6f3..548e9a05e4a 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -255,18 +255,33 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return }); - snapshot->erase(); + auto wrote_head = false; + auto head_path = derive_head_path(snapshot_dir); + auto rollback_head = + sg::make_scope_guard([this, old_head = head_snapshot, &head_path, &wrote_head]() mutable noexcept { + if (head_snapshot != old_head) + { + head_snapshot = std::move(old_head); + if (wrote_head) + top_catch_all(vm_name, [&head_path, &old_head] { + MP_UTILS.make_file_with_content(head_path.toStdString(), old_head->get_name(), + yes_overwrite); + }); + } + }); if (head_snapshot == snapshot) { head_snapshot = snapshot->get_parent(); - persist_head_snapshot_name(derive_head_path(snapshot_dir)); + persist_head_snapshot_name(head_path); + wrote_head = true; } + snapshot->erase(); + rollback_head.dismiss(); rollback_snapshot_file.dismiss(); - } + } // No rollbacks from this point on - // No rollbacks from this point on for (auto& [ignore, other] : snapshots) if (other->get_parent() == snapshot) other->set_parent(snapshot->get_parent()); From 0dc797b2e879206b6656813c6647464b5f9cb61e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 17:59:22 +0100 Subject: [PATCH 263/627] [vm] Extract rollback for deleted head --- .../backends/shared/base_virtual_machine.cpp | 33 ++++++++++++------- .../backends/shared/base_virtual_machine.h | 4 +++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 548e9a05e4a..763e06ceaaf 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -230,6 +230,26 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } +auto BaseVirtualMachine::make_deleted_head_rollback(const QString& head_path, const bool& wrote_head) +{ + return sg::make_scope_guard([this, old_head = head_snapshot, &head_path, &wrote_head]() mutable noexcept { + deleted_head_rollback_helper(head_path, wrote_head, old_head); + }); +} + +void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, + std::shared_ptr& old_head) +{ + if (head_snapshot != old_head) + { + head_snapshot = std::move(old_head); + if (wrote_head) + top_catch_all(vm_name, [&head_path, &old_head] { + MP_UTILS.make_file_with_content(head_path.toStdString(), old_head->get_name(), yes_overwrite); + }); + } +} + void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) { std::unique_lock lock{snapshot_mutex}; @@ -257,18 +277,7 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto wrote_head = false; auto head_path = derive_head_path(snapshot_dir); - auto rollback_head = - sg::make_scope_guard([this, old_head = head_snapshot, &head_path, &wrote_head]() mutable noexcept { - if (head_snapshot != old_head) - { - head_snapshot = std::move(old_head); - if (wrote_head) - top_catch_all(vm_name, [&head_path, &old_head] { - MP_UTILS.make_file_with_content(head_path.toStdString(), old_head->get_name(), - yes_overwrite); - }); - } - }); + auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); if (head_snapshot == snapshot) { diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index fd5faf8fd55..ac3499892e5 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -100,6 +100,10 @@ class BaseVirtualMachine : public VirtualMachine void restore_rollback_helper(const QString& head_path, const std::shared_ptr& old_head, const VMSpecs& old_specs, VMSpecs& specs); + auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); + void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, + std::shared_ptr& old_head); + private: SnapshotMap snapshots; std::shared_ptr head_snapshot = nullptr; From 4697ab5643657be005b45b5eaccf7e489e0f2328 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 18:03:06 +0100 Subject: [PATCH 264/627] [vm] Invert `if` condition, to reduce indentation --- .../backends/shared/base_virtual_machine.cpp | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 763e06ceaaf..da41c24f4a2 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -254,52 +254,51 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st { std::unique_lock lock{snapshot_mutex}; - if (auto it = snapshots.find(name); it != snapshots.end()) - { - auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, name); - auto snapshot_filepath = snapshot_fileinfo.filePath(); - auto snapshot = it->second; + auto it = snapshots.find(name); + if (it == snapshots.end()) + throw NoSuchSnapshot{vm_name, name}; - { - QTemporaryDir tmp_dir{}; - if (!tmp_dir.isValid()) - throw std::runtime_error{"Could not create temporary directory"}; + auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, name); + auto snapshot_filepath = snapshot_fileinfo.filePath(); + auto snapshot = it->second; - auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); + { + QTemporaryDir tmp_dir{}; + if (!tmp_dir.isValid()) + throw std::runtime_error{"Could not create temporary directory"}; - if (!QFile{snapshot_filepath}.rename(deleting_filepath)) - throw std::runtime_error{ - fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; + auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); - auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { - QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return - }); + if (!QFile{snapshot_filepath}.rename(deleting_filepath)) + throw std::runtime_error{ + fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; - auto wrote_head = false; - auto head_path = derive_head_path(snapshot_dir); - auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); + auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { + QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return + }); - if (head_snapshot == snapshot) - { - head_snapshot = snapshot->get_parent(); - persist_head_snapshot_name(head_path); - wrote_head = true; - } + auto wrote_head = false; + auto head_path = derive_head_path(snapshot_dir); + auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); - snapshot->erase(); - rollback_head.dismiss(); - rollback_snapshot_file.dismiss(); - } // No rollbacks from this point on + if (head_snapshot == snapshot) + { + head_snapshot = snapshot->get_parent(); + persist_head_snapshot_name(head_path); + wrote_head = true; + } - for (auto& [ignore, other] : snapshots) - if (other->get_parent() == snapshot) - other->set_parent(snapshot->get_parent()); + snapshot->erase(); + rollback_head.dismiss(); + rollback_snapshot_file.dismiss(); + } // No rollbacks from this point on - snapshots.erase(it); // doesn't throw - mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); - } - else - throw NoSuchSnapshot{vm_name, name}; + for (auto& [ignore, other] : snapshots) + if (other->get_parent() == snapshot) + other->set_parent(snapshot->get_parent()); + + snapshots.erase(it); // doesn't throw + mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) From af6823836b6aa7cbc0f2a7ab7af6a390b1f6054e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 18:24:45 +0100 Subject: [PATCH 265/627] [vm] Extract bulk of snapshot deletion To improve readability. --- .../backends/shared/base_virtual_machine.cpp | 32 +++++++++++-------- .../backends/shared/base_virtual_machine.h | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index da41c24f4a2..af1c7a4aee1 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -250,17 +250,10 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, } } -void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) +void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot) { - std::unique_lock lock{snapshot_mutex}; - - auto it = snapshots.find(name); - if (it == snapshots.end()) - throw NoSuchSnapshot{vm_name, name}; - - auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, name); + auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot.get_name()); auto snapshot_filepath = snapshot_fileinfo.filePath(); - auto snapshot = it->second; { QTemporaryDir tmp_dir{}; @@ -281,18 +274,31 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto head_path = derive_head_path(snapshot_dir); auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); - if (head_snapshot == snapshot) + if (head_snapshot.get() == &snapshot) { - head_snapshot = snapshot->get_parent(); + head_snapshot = snapshot.get_parent(); persist_head_snapshot_name(head_path); wrote_head = true; } - snapshot->erase(); + snapshot.erase(); rollback_head.dismiss(); rollback_snapshot_file.dismiss(); - } // No rollbacks from this point on + } +} + +void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) +{ + std::unique_lock lock{snapshot_mutex}; + + auto it = snapshots.find(name); + if (it == snapshots.end()) + throw NoSuchSnapshot{vm_name, name}; + + auto snapshot = it->second; + delete_snapshot_helper(snapshot_dir, *snapshot); + // No rollbacks from this point on for (auto& [ignore, other] : snapshots) if (other->get_parent() == snapshot) other->set_parent(snapshot->get_parent()); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index ac3499892e5..19a40c3266d 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -103,6 +103,7 @@ class BaseVirtualMachine : public VirtualMachine auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); + void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); private: SnapshotMap snapshots; From 265c51cde31880b75d9bfc6d7ad997aa7c1bc69c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 18:43:23 +0100 Subject: [PATCH 266/627] [vm] Fix snapshot file rollback --- src/platform/backends/shared/base_virtual_machine.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index af1c7a4aee1..e0213cbed63 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -412,15 +412,15 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const const auto snapshot_filename = derive_snapshot_filename(derive_index_string(snapshot_count), QString::fromStdString(head_snapshot->get_name())); - auto snapshot_path = snapshot_dir.filePath(snapshot_filename); + auto snapshot_filepath = snapshot_dir.filePath(snapshot_filename); auto head_path = derive_head_path(snapshot_dir); auto count_path = snapshot_dir.filePath(count_filename); - auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filename]() noexcept { - QFile{snapshot_filename}.remove(); // best effort, ignore return + auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filepath]() noexcept { + QFile{snapshot_filepath}.remove(); // best effort, ignore return }); - mp::write_json(head_snapshot->serialize(), snapshot_path); + mp::write_json(head_snapshot->serialize(), snapshot_filepath); QFile head_file{head_path}; From f5a17f7eee45e44ecb62767c4c4b290130ba54c9 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 19:33:42 +0100 Subject: [PATCH 267/627] [vm] Persist snapshot's with deleted parents --- src/platform/backends/shared/base_virtual_machine.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e0213cbed63..c6247b59478 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -300,8 +300,14 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st // No rollbacks from this point on for (auto& [ignore, other] : snapshots) + { if (other->get_parent() == snapshot) + { + const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); other->set_parent(snapshot->get_parent()); + mp::write_json(other->serialize(), other_filepath); + } + } snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); From 3c7aa9db06bc661dfd2d73a2704f9515cbb5b4e4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 18:53:00 +0100 Subject: [PATCH 268/627] [vm] Remove obsolete braces --- .../backends/shared/base_virtual_machine.cpp | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c6247b59478..8649fc2c26d 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -255,36 +255,34 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapsh auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot.get_name()); auto snapshot_filepath = snapshot_fileinfo.filePath(); - { - QTemporaryDir tmp_dir{}; - if (!tmp_dir.isValid()) - throw std::runtime_error{"Could not create temporary directory"}; - - auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); + QTemporaryDir tmp_dir{}; + if (!tmp_dir.isValid()) + throw std::runtime_error{"Could not create temporary directory"}; - if (!QFile{snapshot_filepath}.rename(deleting_filepath)) - throw std::runtime_error{ - fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; + auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); - auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { - QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return - }); + if (!QFile{snapshot_filepath}.rename(deleting_filepath)) + throw std::runtime_error{ + fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; - auto wrote_head = false; - auto head_path = derive_head_path(snapshot_dir); - auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); + auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { + QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return + }); - if (head_snapshot.get() == &snapshot) - { - head_snapshot = snapshot.get_parent(); - persist_head_snapshot_name(head_path); - wrote_head = true; - } + auto wrote_head = false; + auto head_path = derive_head_path(snapshot_dir); + auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); - snapshot.erase(); - rollback_head.dismiss(); - rollback_snapshot_file.dismiss(); + if (head_snapshot.get() == &snapshot) + { + head_snapshot = snapshot.get_parent(); + persist_head_snapshot_name(head_path); + wrote_head = true; } + + snapshot.erase(); + rollback_head.dismiss(); + rollback_snapshot_file.dismiss(); } void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) From defa9683f27f40a19f200d740c2c4a8a67929a09 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 19:57:32 +0100 Subject: [PATCH 269/627] [vm] Extract updating parents on delete --- .../backends/shared/base_virtual_machine.cpp | 23 +++++++++++-------- .../backends/shared/base_virtual_machine.h | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 8649fc2c26d..92cc34c3e3b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -285,6 +285,19 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapsh rollback_snapshot_file.dismiss(); } +void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, Snapshot& deleted_parent) +{ + for (auto& [ignore, other] : snapshots) + { + if (other->get_parent().get() == &deleted_parent) + { + const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); + other->set_parent(deleted_parent.get_parent()); + write_json(other->serialize(), other_filepath); + } + } +} + void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) { std::unique_lock lock{snapshot_mutex}; @@ -297,15 +310,7 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st delete_snapshot_helper(snapshot_dir, *snapshot); // No rollbacks from this point on - for (auto& [ignore, other] : snapshots) - { - if (other->get_parent() == snapshot) - { - const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); - other->set_parent(snapshot->get_parent()); - mp::write_json(other->serialize(), other_filepath); - } - } + update_parents(snapshot_dir, *snapshot); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 19a40c3266d..c1e470cad3a 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -104,6 +104,7 @@ class BaseVirtualMachine : public VirtualMachine void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); + void update_parents(const QDir& snapshot_dir, Snapshot& deleted_parent); private: SnapshotMap snapshots; From 83705deb63168c17350ae531a863bff1e71681f7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 19:59:12 +0100 Subject: [PATCH 270/627] [vm] Extract deriving new parent for update --- src/platform/backends/shared/base_virtual_machine.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 92cc34c3e3b..23a7f23762e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -287,12 +287,14 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapsh void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, Snapshot& deleted_parent) { + auto new_parent = deleted_parent.get_parent(); for (auto& [ignore, other] : snapshots) { if (other->get_parent().get() == &deleted_parent) { + other->set_parent(new_parent); + const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); - other->set_parent(deleted_parent.get_parent()); write_json(other->serialize(), other_filepath); } } From 5f01638ff910ce3fb3a87e71052fd30de0433912 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:16:21 +0100 Subject: [PATCH 271/627] [vm] Implement rolling back parent updates --- .../backends/shared/base_virtual_machine.cpp | 27 ++++++++++++++----- .../backends/shared/base_virtual_machine.h | 2 +- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 23a7f23762e..bfc642ed28b 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -285,19 +285,36 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapsh rollback_snapshot_file.dismiss(); } -void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, Snapshot& deleted_parent) +void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) { - auto new_parent = deleted_parent.get_parent(); + auto new_parent = deleted_parent->get_parent(); + + std::unordered_map updated_snapshot_paths; + updated_snapshot_paths.reserve(snapshots.size()); + + auto rollback = sg::make_scope_guard([&updated_snapshot_paths, deleted_parent]() noexcept { // TODO@ricab catchall + for (auto [snapshot, snapshot_filepath] : updated_snapshot_paths) + { + snapshot->set_parent(deleted_parent); + if (!snapshot_filepath.isEmpty()) + write_json(snapshot->serialize(), snapshot_filepath); + } + }); + for (auto& [ignore, other] : snapshots) { - if (other->get_parent().get() == &deleted_parent) + if (other->get_parent() == deleted_parent) { other->set_parent(new_parent); + updated_snapshot_paths[other.get()]; const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); write_json(other->serialize(), other_filepath); + updated_snapshot_paths[other.get()] = other_filepath; } } + + rollback.dismiss(); } void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) @@ -310,9 +327,7 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto snapshot = it->second; delete_snapshot_helper(snapshot_dir, *snapshot); - - // No rollbacks from this point on - update_parents(snapshot_dir, *snapshot); + update_parents(snapshot_dir, snapshot); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index c1e470cad3a..a12e6eb041f 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -104,7 +104,7 @@ class BaseVirtualMachine : public VirtualMachine void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); - void update_parents(const QDir& snapshot_dir, Snapshot& deleted_parent); + void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent); private: SnapshotMap snapshots; From e3670dcc4055e94231d2578b8e65073d4dc4a298 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:22:54 +0100 Subject: [PATCH 272/627] [vm] Extract helper for parent-update rollback --- .../backends/shared/base_virtual_machine.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index bfc642ed28b..9487954e58c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -91,6 +91,17 @@ QFileInfo find_snapshot_file(const QDir& snapshot_dir, const std::string& snapsh return files.first(); } + +void update_parents_rollback_helper(const std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths) +{ + for (auto [snapshot, snapshot_filepath] : updated_snapshot_paths) + { + snapshot->set_parent(deleted_parent); + if (!snapshot_filepath.isEmpty()) + mp::write_json(snapshot->serialize(), snapshot_filepath); + } +} } // namespace namespace multipass @@ -293,12 +304,7 @@ void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_pt updated_snapshot_paths.reserve(snapshots.size()); auto rollback = sg::make_scope_guard([&updated_snapshot_paths, deleted_parent]() noexcept { // TODO@ricab catchall - for (auto [snapshot, snapshot_filepath] : updated_snapshot_paths) - { - snapshot->set_parent(deleted_parent); - if (!snapshot_filepath.isEmpty()) - write_json(snapshot->serialize(), snapshot_filepath); - } + update_parents_rollback_helper(deleted_parent, updated_snapshot_paths); }); for (auto& [ignore, other] : snapshots) From 207cea7086df108c1d017fb19d8f0fd33d4f9aab Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:24:43 +0100 Subject: [PATCH 273/627] [vm] Catch exceptions when rolling back parents --- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 9487954e58c..1763ee96036 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -303,8 +303,8 @@ void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_pt std::unordered_map updated_snapshot_paths; updated_snapshot_paths.reserve(snapshots.size()); - auto rollback = sg::make_scope_guard([&updated_snapshot_paths, deleted_parent]() noexcept { // TODO@ricab catchall - update_parents_rollback_helper(deleted_parent, updated_snapshot_paths); + auto rollback = sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { + top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); }); for (auto& [ignore, other] : snapshots) From ca4b5fc30c0f298766a37790298129ef6b64e484 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:28:46 +0100 Subject: [PATCH 274/627] [vm] Extract making of parent-update rollback --- .../backends/shared/base_virtual_machine.cpp | 13 ++++++++++--- src/platform/backends/shared/base_virtual_machine.h | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1763ee96036..7449a8e3995 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -296,6 +296,15 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapsh rollback_snapshot_file.dismiss(); } +auto BaseVirtualMachine::make_parent_update_rollback( + const std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths) const +{ + return sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { + top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); + }); +} + void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) { auto new_parent = deleted_parent->get_parent(); @@ -303,9 +312,7 @@ void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_pt std::unordered_map updated_snapshot_paths; updated_snapshot_paths.reserve(snapshots.size()); - auto rollback = sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { - top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); - }); + auto rollback = make_parent_update_rollback(deleted_parent, updated_snapshot_paths); for (auto& [ignore, other] : snapshots) { diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a12e6eb041f..a701485bc94 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -103,8 +103,10 @@ class BaseVirtualMachine : public VirtualMachine auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); - void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent); + auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths) const; + void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); private: SnapshotMap snapshots; From e6c021ee704d1315826affbef9aeccbec68b414c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:35:29 +0100 Subject: [PATCH 275/627] [vm] Change method name, to prep for replacement --- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- src/platform/backends/shared/base_virtual_machine.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7449a8e3995..7d5a48c6b46 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -305,7 +305,7 @@ auto BaseVirtualMachine::make_parent_update_rollback( }); } -void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) +void BaseVirtualMachine::update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) { auto new_parent = deleted_parent->get_parent(); @@ -340,7 +340,7 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto snapshot = it->second; delete_snapshot_helper(snapshot_dir, *snapshot); - update_parents(snapshot_dir, snapshot); + update_parents_obsolete(snapshot_dir, snapshot); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a701485bc94..0a206cbbd5f 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -103,7 +103,7 @@ class BaseVirtualMachine : public VirtualMachine auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); - void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent); + void update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent); auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) const; void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); From f09059d1343c4291178139d2dfe9ddbedb523a1e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:42:58 +0100 Subject: [PATCH 276/627] [vm] Extract inner steps for updating parents --- .../backends/shared/base_virtual_machine.cpp | 13 +++++++++---- src/platform/backends/shared/base_virtual_machine.h | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7d5a48c6b46..7ffabe14d92 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -307,13 +307,20 @@ auto BaseVirtualMachine::make_parent_update_rollback( void BaseVirtualMachine::update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) { - auto new_parent = deleted_parent->get_parent(); - std::unordered_map updated_snapshot_paths; updated_snapshot_paths.reserve(snapshots.size()); auto rollback = make_parent_update_rollback(deleted_parent, updated_snapshot_paths); + update_parents(snapshot_dir, deleted_parent, updated_snapshot_paths); + + rollback.dismiss(); +} + +void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths) +{ + auto new_parent = deleted_parent->get_parent(); for (auto& [ignore, other] : snapshots) { if (other->get_parent() == deleted_parent) @@ -326,8 +333,6 @@ void BaseVirtualMachine::update_parents_obsolete(const QDir& snapshot_dir, std:: updated_snapshot_paths[other.get()] = other_filepath; } } - - rollback.dismiss(); } void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 0a206cbbd5f..12e2b0c2453 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -103,9 +103,13 @@ class BaseVirtualMachine : public VirtualMachine auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); + + void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths); void update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent); auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) const; + void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); private: From 052e852e21c333cdadc05ccf303c041753244bad Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 20:43:42 +0100 Subject: [PATCH 277/627] [vm] Rename rollback variable --- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7ffabe14d92..9e86fe802c7 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -310,11 +310,11 @@ void BaseVirtualMachine::update_parents_obsolete(const QDir& snapshot_dir, std:: std::unordered_map updated_snapshot_paths; updated_snapshot_paths.reserve(snapshots.size()); - auto rollback = make_parent_update_rollback(deleted_parent, updated_snapshot_paths); + auto rollback_parent_updates = make_parent_update_rollback(deleted_parent, updated_snapshot_paths); update_parents(snapshot_dir, deleted_parent, updated_snapshot_paths); - rollback.dismiss(); + rollback_parent_updates.dismiss(); } void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, From 8c09b12734c68dbb2f7add0379fede3fa18b3b08 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 22:12:11 +0100 Subject: [PATCH 278/627] [vm] Fix use after move --- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 9e86fe802c7..ff327e2e1e3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -255,8 +255,8 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, { head_snapshot = std::move(old_head); if (wrote_head) - top_catch_all(vm_name, [&head_path, &old_head] { - MP_UTILS.make_file_with_content(head_path.toStdString(), old_head->get_name(), yes_overwrite); + top_catch_all(vm_name, [this, &head_path] { + MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); }); } } From d77a3d2b2ba8c5deddad8e4f06819907494ad0a1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 22:20:24 +0100 Subject: [PATCH 279/627] [vm] Update param in snapshot helper Use a (ref to the) shared_ptr to the snapshot being deleted, in delete helper function, to accommodate rolling back parent updates, by creating new shared_ptrs from the original.+ --- .../backends/shared/base_virtual_machine.cpp | 12 ++++++------ src/platform/backends/shared/base_virtual_machine.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index ff327e2e1e3..e4861113d35 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -261,9 +261,9 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, } } -void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot) +void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::shared_ptr& snapshot) { - auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot.get_name()); + auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot->get_name()); auto snapshot_filepath = snapshot_fileinfo.filePath(); QTemporaryDir tmp_dir{}; @@ -284,14 +284,14 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, Snapsh auto head_path = derive_head_path(snapshot_dir); auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); - if (head_snapshot.get() == &snapshot) + if (head_snapshot == snapshot) { - head_snapshot = snapshot.get_parent(); + head_snapshot = snapshot->get_parent(); persist_head_snapshot_name(head_path); wrote_head = true; } - snapshot.erase(); + snapshot->erase(); rollback_head.dismiss(); rollback_snapshot_file.dismiss(); } @@ -344,7 +344,7 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st throw NoSuchSnapshot{vm_name, name}; auto snapshot = it->second; - delete_snapshot_helper(snapshot_dir, *snapshot); + delete_snapshot_helper(snapshot_dir, snapshot); update_parents_obsolete(snapshot_dir, snapshot); snapshots.erase(it); // doesn't throw diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 12e2b0c2453..76d849f7d71 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -110,7 +110,7 @@ class BaseVirtualMachine : public VirtualMachine auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) const; - void delete_snapshot_helper(const QDir& snapshot_dir, Snapshot& snapshot); + void delete_snapshot_helper(const QDir& snapshot_dir, std::shared_ptr& snapshot); private: SnapshotMap snapshots; From 8a5ada6de789871265cf0b142878235fc23ff4bb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 22:23:45 +0100 Subject: [PATCH 280/627] [vm] Update/rollback parents from delete helper So that all rollbacks can be executed until the snapshot is successfully erased with the backend. --- .../backends/shared/base_virtual_machine.cpp | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e4861113d35..5892fe251da 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -261,6 +261,15 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, } } +auto BaseVirtualMachine::make_parent_update_rollback( + const std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths) const +{ + return sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { + top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); + }); +} + void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::shared_ptr& snapshot) { auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot->get_name()); @@ -291,30 +300,21 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s wrote_head = true; } + std::unordered_map updated_snapshot_paths; + updated_snapshot_paths.reserve(snapshots.size()); + + auto rollback_parent_updates = make_parent_update_rollback(snapshot, updated_snapshot_paths); + update_parents(snapshot_dir, snapshot, updated_snapshot_paths); + snapshot->erase(); + rollback_parent_updates.dismiss(); rollback_head.dismiss(); rollback_snapshot_file.dismiss(); } -auto BaseVirtualMachine::make_parent_update_rollback( - const std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) const -{ - return sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { - top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); - }); -} - void BaseVirtualMachine::update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) { - std::unordered_map updated_snapshot_paths; - updated_snapshot_paths.reserve(snapshots.size()); - auto rollback_parent_updates = make_parent_update_rollback(deleted_parent, updated_snapshot_paths); - - update_parents(snapshot_dir, deleted_parent, updated_snapshot_paths); - - rollback_parent_updates.dismiss(); } void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, @@ -345,7 +345,6 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st auto snapshot = it->second; delete_snapshot_helper(snapshot_dir, snapshot); - update_parents_obsolete(snapshot_dir, snapshot); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); From 3ad443d74f444c67b4db0fb497b95d4662d0b508 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 22:24:20 +0100 Subject: [PATCH 281/627] [vm] Delete obsolete method --- src/platform/backends/shared/base_virtual_machine.cpp | 5 ----- src/platform/backends/shared/base_virtual_machine.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 5892fe251da..45623ad9c8d 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -312,11 +312,6 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s rollback_snapshot_file.dismiss(); } -void BaseVirtualMachine::update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent) -{ - -} - void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) { diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 76d849f7d71..0580d9db885 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -106,7 +106,6 @@ class BaseVirtualMachine : public VirtualMachine void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths); - void update_parents_obsolete(const QDir& snapshot_dir, std::shared_ptr& deleted_parent); auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) const; From b2c7e237b6776168ee5bbdf5317941dac0720da0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 22:36:12 +0100 Subject: [PATCH 282/627] [vm] Extract updating a deleted head snapshot --- .../backends/shared/base_virtual_machine.cpp | 20 ++++++++++++------- .../backends/shared/base_virtual_machine.h | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 45623ad9c8d..c96c88e32c3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -241,6 +241,18 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } +bool BaseVirtualMachine::updated_deleted_head(std::shared_ptr& snapshot, const QString& head_path) +{ + if (head_snapshot == snapshot) + { + head_snapshot = snapshot->get_parent(); + persist_head_snapshot_name(head_path); + return true; + } + + return false; +} + auto BaseVirtualMachine::make_deleted_head_rollback(const QString& head_path, const bool& wrote_head) { return sg::make_scope_guard([this, old_head = head_snapshot, &head_path, &wrote_head]() mutable noexcept { @@ -292,13 +304,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s auto wrote_head = false; auto head_path = derive_head_path(snapshot_dir); auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); - - if (head_snapshot == snapshot) - { - head_snapshot = snapshot->get_parent(); - persist_head_snapshot_name(head_path); - wrote_head = true; - } + wrote_head = updated_deleted_head(snapshot, head_path); std::unordered_map updated_snapshot_paths; updated_snapshot_paths.reserve(snapshots.size()); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 0580d9db885..98bb6e6c1cb 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -100,6 +100,7 @@ class BaseVirtualMachine : public VirtualMachine void restore_rollback_helper(const QString& head_path, const std::shared_ptr& old_head, const VMSpecs& old_specs, VMSpecs& specs); + bool updated_deleted_head(std::shared_ptr& snapshot, const QString& head_path); auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, std::shared_ptr& old_head); From 2f9363fb3bd6e8b4df95e429bbde10655e03f12c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 19 Jun 2023 22:41:36 +0100 Subject: [PATCH 283/627] [vm] Improve readability of delete_snapshot_helper With a slight reordering and a few comments. --- src/platform/backends/shared/base_virtual_machine.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c96c88e32c3..387fbe0a898 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -284,13 +284,13 @@ auto BaseVirtualMachine::make_parent_update_rollback( void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::shared_ptr& snapshot) { - auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot->get_name()); - auto snapshot_filepath = snapshot_fileinfo.filePath(); - + // Remove snapshot file QTemporaryDir tmp_dir{}; if (!tmp_dir.isValid()) throw std::runtime_error{"Could not create temporary directory"}; + auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot->get_name()); + auto snapshot_filepath = snapshot_fileinfo.filePath(); auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); if (!QFile{snapshot_filepath}.rename(deleting_filepath)) @@ -301,17 +301,20 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return }); + // Update head if deleted auto wrote_head = false; auto head_path = derive_head_path(snapshot_dir); auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); wrote_head = updated_deleted_head(snapshot, head_path); + // Update children of deleted snapshot std::unordered_map updated_snapshot_paths; updated_snapshot_paths.reserve(snapshots.size()); auto rollback_parent_updates = make_parent_update_rollback(snapshot, updated_snapshot_paths); update_parents(snapshot_dir, snapshot, updated_snapshot_paths); + // Erase the snapshot with the backend and dismiss rollbacks on success snapshot->erase(); rollback_parent_updates.dismiss(); rollback_head.dismiss(); From f2fe832a2ad9dc20431be246716f63eff3c788d6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 27 Jun 2023 18:11:51 +0100 Subject: [PATCH 284/627] [exceptions] Move NoSuchSnapshot to own header So that it can be included and handled elsewhere. --- .../exceptions/snapshot_exceptions.h | 38 +++++++++++++++++++ .../backends/shared/base_virtual_machine.cpp | 10 +---- 2 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 include/multipass/exceptions/snapshot_exceptions.h diff --git a/include/multipass/exceptions/snapshot_exceptions.h b/include/multipass/exceptions/snapshot_exceptions.h new file mode 100644 index 00000000000..793473ba498 --- /dev/null +++ b/include/multipass/exceptions/snapshot_exceptions.h @@ -0,0 +1,38 @@ +/* + * 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_SNAPSHOT_EXCEPTIONS_H +#define MULTIPASS_SNAPSHOT_EXCEPTIONS_H + +#include +#include + +#include + +namespace multipass +{ +class NoSuchSnapshot : public std::runtime_error +{ +public: + NoSuchSnapshot(const std::string& vm_name, const std::string& snapshot_name) + : std::runtime_error{fmt::format("No such snapshot: {}.{}", vm_name, snapshot_name)} + { + } +}; +} // namespace multipass + +#endif // MULTIPASS_SNAPSHOT_EXCEPTIONS_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 387fbe0a898..13ac19082cb 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -19,6 +19,7 @@ #include "daemon/vm_specs.h" // TODO@snapshots move this #include +#include #include #include #include @@ -42,15 +43,6 @@ namespace mpu = multipass::utils; namespace { using St = mp::VirtualMachine::State; -class NoSuchSnapshot : public std::runtime_error -{ -public: - NoSuchSnapshot(const std::string& vm_name, const std::string& snapshot_name) - : std::runtime_error{fmt::format("No such snapshot: {}.{}", vm_name, snapshot_name)} - { - } -}; - constexpr auto snapshot_extension = "snapshot.json"; constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; From cfcbd931ff3b7e1195247989ba8b303f26ac57b4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 27 Jun 2023 18:15:48 +0100 Subject: [PATCH 285/627] [exceptions] Group dedicated snapshot exceptions Group dedicated snapshot exceptions in a single header file. --- .../exceptions/snapshot_exceptions.h | 9 +++++ .../exceptions/snapshot_name_taken.h | 38 ------------------- src/daemon/daemon.cpp | 2 +- .../backends/shared/base_virtual_machine.cpp | 1 - 4 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 include/multipass/exceptions/snapshot_name_taken.h diff --git a/include/multipass/exceptions/snapshot_exceptions.h b/include/multipass/exceptions/snapshot_exceptions.h index 793473ba498..0e8c8c4001f 100644 --- a/include/multipass/exceptions/snapshot_exceptions.h +++ b/include/multipass/exceptions/snapshot_exceptions.h @@ -25,6 +25,15 @@ namespace multipass { +class SnapshotNameTaken : public std::runtime_error +{ +public: + SnapshotNameTaken(const std::string& instance_name, const std::string& snapshot_name) + : std::runtime_error{fmt::format(R"(Snapshot "{}.{}" already exists)", instance_name, snapshot_name)} + { + } +}; + class NoSuchSnapshot : public std::runtime_error { public: diff --git a/include/multipass/exceptions/snapshot_name_taken.h b/include/multipass/exceptions/snapshot_name_taken.h deleted file mode 100644 index 11a17cf490c..00000000000 --- a/include/multipass/exceptions/snapshot_name_taken.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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_SNAPSHOT_NAME_TAKEN_H -#define MULTIPASS_SNAPSHOT_NAME_TAKEN_H - -#include -#include - -#include - -namespace multipass -{ -class SnapshotNameTaken : public std::runtime_error -{ -public: - SnapshotNameTaken(const std::string& instance_name, const std::string& snapshot_name) - : std::runtime_error{fmt::format(R"(Snapshot "{}.{}" already exists)", instance_name, snapshot_name)} - { - } -}; -} // namespace multipass - -#endif // MULTIPASS_SNAPSHOT_NAME_TAKEN_H diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9dba556771a..e514f7e753d 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 13ac19082cb..e11d44def62 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include From cd8154f00d250f44cdfdab2e238099349194e8cc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 27 Jun 2023 18:28:41 +0100 Subject: [PATCH 286/627] [daemon] Handle dedicated NoSuchSnapshot exception Replace the previous catch of `std::out_of_range` with the dedicated exception for missing snapshots. --- src/daemon/daemon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e514f7e753d..13ff6e510c9 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1726,9 +1726,9 @@ try // clang-format on { get_snapshot_info(vm.get_snapshot(snapshot)); } - catch (const std::out_of_range&) + catch (const NoSuchSnapshot& e) { - add_fmt_to(errors, "snapshot \"{}\" does not exist", snapshot); + add_fmt_to(errors, e.what()); } } } From 2ef74527074d5b49a97027ad37f4919d92a0931a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 16 Jun 2023 19:39:33 +0100 Subject: [PATCH 287/627] [qemu] Implement snapshot deletion --- src/platform/backends/qemu/qemu_snapshot.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index bd3ff3a256f..53222eb8e44 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -44,6 +44,12 @@ std::unique_ptr make_restore_spec(const QString& tag, co /* src_img = */ "", image_path); } +std::unique_ptr make_delete_spec(const QString& tag, const QString& image_path) +{ + return std::make_unique(QStringList{"snapshot", "-d", tag, image_path}, + /* src_img = */ "", image_path); +} + void checked_exec_qemu_img(std::unique_ptr spec) { auto process = mpp::make_process(std::move(spec)); @@ -81,9 +87,9 @@ void mp::QemuSnapshot::capture_impl() checked_exec_qemu_img(make_capture_spec(tag, image_path)); } -void mp::QemuSnapshot::erase_impl() // TODO@snapshots +void mp::QemuSnapshot::erase_impl() { - throw NotImplementedOnThisBackendException{"Snapshot erasing"}; + checked_exec_qemu_img(make_delete_spec(derive_tag(), image_path)); } void mp::QemuSnapshot::apply_impl() From 75f2683e71a9739e2dc656f9951e3ea276663bef Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 13 Jun 2023 14:16:02 -0700 Subject: [PATCH 288/627] [snapshot] add creation timestamp field to snapshot metadata --- include/multipass/snapshot.h | 2 ++ src/platform/backends/qemu/qemu_snapshot.cpp | 2 +- .../backends/shared/base_snapshot.cpp | 21 +++++++++------ src/platform/backends/shared/base_snapshot.h | 26 +++++++++++++++---- tests/stub_snapshot.h | 9 +++++++ 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 7a280f9f9d6..dd5428d7c7d 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -40,6 +40,7 @@ class Snapshot : private DisabledCopyMove virtual std::string get_name() const = 0; virtual std::string get_comment() const = 0; virtual std::string get_parent_name() const = 0; + virtual QDateTime get_creation_timestamp() const = 0; virtual std::shared_ptr get_parent() const = 0; virtual std::shared_ptr get_parent() = 0; @@ -56,6 +57,7 @@ class Snapshot : private DisabledCopyMove virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file virtual void set_comment(const std::string&) = 0; + virtual void set_creation_timestamp(const QDateTime&) = 0; virtual void set_parent(std::shared_ptr) = 0; virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 53222eb8e44..c2a64dd0676 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -65,7 +65,7 @@ void checked_exec_qemu_img(std::unique_ptr spec) mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, const QString& image_path, std::shared_ptr parent) - : BaseSnapshot(name, comment, specs, std::move(parent)), image_path{image_path} + : BaseSnapshot(name, comment, QDateTime::currentDateTimeUtc(), specs, std::move(parent)), image_path{image_path} { } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index cd4874e9e6a..e35198710f4 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -78,12 +78,14 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa } } // namespace -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, int num_cores, MemorySize mem_size, - MemorySize disk_space, VirtualMachine::State state, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, + const QDateTime& creation_timestamp, // NOLINT(modernize-pass-by-value) + int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata, std::shared_ptr parent) : name{name}, comment{comment}, + creation_timestamp{creation_timestamp}, num_cores{num_cores}, mem_size{mem_size}, disk_space{disk_space}, @@ -102,10 +104,10 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme throw std::runtime_error{fmt::format("Invalid disk size for snapshot: {}", disk_bytes)}; } -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent) - : BaseSnapshot{name, comment, specs.num_cores, specs.mem_size, specs.disk_space, - specs.state, specs.mounts, specs.metadata, std::move(parent)} +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, + const VMSpecs& specs, std::shared_ptr parent) + : BaseSnapshot{name, comment, creation_timestamp, specs.num_cores, specs.mem_size, specs.disk_space, + specs.state, specs.mounts, specs.metadata, std::move(parent)} { } @@ -115,8 +117,10 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) } mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm) - : BaseSnapshot{json["name"].toString().toStdString(), // name - json["comment"].toString().toStdString(), // comment + : BaseSnapshot{json["name"].toString().toStdString(), // name + json["comment"].toString().toStdString(), // comment + QDateTime::fromString(json["creation_timestamp"].toString(), + "yyyy-MM-ddTHH:mm:ss.zzzZ"), // creation_timestamp json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space @@ -134,6 +138,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); + snapshot.insert("creation_timestamp", creation_timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzzZ")); snapshot.insert("num_cores", num_cores); snapshot.insert("mem_size", QString::number(mem_size.in_bytes())); snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 09ecd5aece8..83dd7464fa1 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -34,13 +34,14 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent); + BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, + const VMSpecs& specs, std::shared_ptr parent); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); std::string get_name() const override; std::string get_comment() const override; std::string get_parent_name() const override; + QDateTime get_creation_timestamp() const override; std::shared_ptr get_parent() const override; std::shared_ptr get_parent() override; @@ -57,6 +58,7 @@ class BaseSnapshot : public Snapshot void set_name(const std::string& n) override; void set_comment(const std::string& c) override; + void set_creation_timestamp(const QDateTime& t) override; void set_parent(std::shared_ptr p) override; void capture() final; @@ -73,13 +75,15 @@ class BaseSnapshot : public Snapshot { }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); - BaseSnapshot(const std::string& name, const std::string& comment, int num_cores, MemorySize mem_size, - MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, - QJsonObject metadata, std::shared_ptr parent); + BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, + int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, + std::unordered_map mounts, QJsonObject metadata, + std::shared_ptr parent); private: std::string name; std::string comment; + QDateTime creation_timestamp; // This class is non-copyable and having these const simplifies thread safety const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -107,6 +111,12 @@ inline std::string multipass::BaseSnapshot::get_comment() const return comment; } +inline QDateTime multipass::BaseSnapshot::get_creation_timestamp() const +{ + const std::unique_lock lock{mutex}; + return creation_timestamp; +} + inline std::string multipass::BaseSnapshot::get_parent_name() const { std::unique_lock lock{mutex}; @@ -169,6 +179,12 @@ inline void multipass::BaseSnapshot::set_comment(const std::string& c) comment = c; } +inline void multipass::BaseSnapshot::set_creation_timestamp(const QDateTime& t) +{ + const std::unique_lock lock{mutex}; + creation_timestamp = t; +} + inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) { const std::unique_lock lock{mutex}; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 759785970c8..11ac707766b 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -37,6 +37,11 @@ struct StubSnapshot : public Snapshot return {}; } + QDateTime get_creation_timestamp() const noexcept override + { + return QDateTime{}; + } + std::string get_parent_name() const override { return {}; @@ -95,6 +100,10 @@ struct StubSnapshot : public Snapshot { } + void set_creation_timestamp(const QDateTime&) override + { + } + void set_parent(std::shared_ptr) override { } From 67f7e5f0e793715f05189dd38aec2bbbd6fce216 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 13 Jun 2023 14:16:40 -0700 Subject: [PATCH 289/627] [daemon] populate info reply with snapshot creation time --- src/daemon/daemon.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 13ff6e510c9..10428ae726f 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1709,7 +1709,10 @@ try // clang-format on fundamentals->set_snapshot_name(snapshot->get_name()); fundamentals->set_parent(snapshot->get_parent_name()); fundamentals->set_comment(snapshot->get_comment()); - // TODO@snapshots populate snapshot creation time once available + + auto timestamp = fundamentals->mutable_creation_timestamp(); + timestamp->set_seconds(snapshot->get_creation_timestamp().toSecsSinceEpoch()); + timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1000000); }; if (const auto& it = instance_snapshots_map.find(name); From 3e4bdc5ed89d210b6f6c7d4681b00b14b4b732ba Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 23 Jun 2023 10:07:10 -0700 Subject: [PATCH 290/627] [snapshot] remove unneeded setter and set member variable to const --- include/multipass/snapshot.h | 1 - src/platform/backends/shared/base_snapshot.h | 10 +--------- tests/stub_snapshot.h | 4 ---- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index dd5428d7c7d..9f79c784862 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -57,7 +57,6 @@ class Snapshot : private DisabledCopyMove virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file virtual void set_comment(const std::string&) = 0; - virtual void set_creation_timestamp(const QDateTime&) = 0; virtual void set_parent(std::shared_ptr) = 0; virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 83dd7464fa1..f563340fcdb 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -58,7 +58,6 @@ class BaseSnapshot : public Snapshot void set_name(const std::string& n) override; void set_comment(const std::string& c) override; - void set_creation_timestamp(const QDateTime& t) override; void set_parent(std::shared_ptr p) override; void capture() final; @@ -83,7 +82,7 @@ class BaseSnapshot : public Snapshot private: std::string name; std::string comment; - QDateTime creation_timestamp; + const QDateTime creation_timestamp; // This class is non-copyable and having these const simplifies thread safety const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -113,7 +112,6 @@ inline std::string multipass::BaseSnapshot::get_comment() const inline QDateTime multipass::BaseSnapshot::get_creation_timestamp() const { - const std::unique_lock lock{mutex}; return creation_timestamp; } @@ -179,12 +177,6 @@ inline void multipass::BaseSnapshot::set_comment(const std::string& c) comment = c; } -inline void multipass::BaseSnapshot::set_creation_timestamp(const QDateTime& t) -{ - const std::unique_lock lock{mutex}; - creation_timestamp = t; -} - inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) { const std::unique_lock lock{mutex}; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 11ac707766b..015c7da8e22 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -100,10 +100,6 @@ struct StubSnapshot : public Snapshot { } - void set_creation_timestamp(const QDateTime&) override - { - } - void set_parent(std::shared_ptr) override { } From a0821433fdd4f889751311988f74b2df4343f866 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 23 Jun 2023 10:08:15 -0700 Subject: [PATCH 291/627] [snapshot] move creation timestamp to base class --- src/platform/backends/qemu/qemu_snapshot.cpp | 2 +- .../backends/shared/base_snapshot.cpp | 23 ++++++++++++------- src/platform/backends/shared/base_snapshot.h | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index c2a64dd0676..53222eb8e44 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -65,7 +65,7 @@ void checked_exec_qemu_img(std::unique_ptr spec) mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, const QString& image_path, std::shared_ptr parent) - : BaseSnapshot(name, comment, QDateTime::currentDateTimeUtc(), specs, std::move(parent)), image_path{image_path} + : BaseSnapshot(name, comment, specs, std::move(parent)), image_path{image_path} { } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index e35198710f4..c90eb105a08 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -78,8 +78,7 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa } } // namespace -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, - const QDateTime& creation_timestamp, // NOLINT(modernize-pass-by-value) +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata, std::shared_ptr parent) @@ -104,10 +103,18 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme throw std::runtime_error{fmt::format("Invalid disk size for snapshot: {}", disk_bytes)}; } -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, - const VMSpecs& specs, std::shared_ptr parent) - : BaseSnapshot{name, comment, creation_timestamp, specs.num_cores, specs.mem_size, specs.disk_space, - specs.state, specs.mounts, specs.metadata, std::move(parent)} +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + std::shared_ptr parent) + : BaseSnapshot{name, + comment, + QDateTime::currentDateTimeUtc(), + specs.num_cores, + specs.mem_size, + specs.disk_space, + specs.state, + specs.mounts, + specs.metadata, + std::move(parent)} { } @@ -120,7 +127,7 @@ mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMac : BaseSnapshot{json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment QDateTime::fromString(json["creation_timestamp"].toString(), - "yyyy-MM-ddTHH:mm:ss.zzzZ"), // creation_timestamp + Qt::ISODate), // creation_timestamp json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space @@ -138,7 +145,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); - snapshot.insert("creation_timestamp", creation_timestamp.toString("yyyy-MM-ddTHH:mm:ss.zzzZ")); + snapshot.insert("creation_timestamp", creation_timestamp.toString(Qt::ISODate)); snapshot.insert("num_cores", num_cores); snapshot.insert("mem_size", QString::number(mem_size.in_bytes())); snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index f563340fcdb..35af8560121 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -34,8 +34,8 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, - const VMSpecs& specs, std::shared_ptr parent); + BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + std::shared_ptr parent); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); std::string get_name() const override; From dcbced2e824ef68ce4d27f60e55c5a53aca048cc Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 23 Jun 2023 10:08:32 -0700 Subject: [PATCH 292/627] [review] formatting changes --- include/multipass/snapshot.h | 1 + src/daemon/daemon.cpp | 2 +- src/platform/backends/shared/base_snapshot.cpp | 5 +++-- src/platform/backends/shared/base_snapshot.h | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 9f79c784862..4e764d7772a 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -26,6 +26,7 @@ #include class QJsonObject; +class QDateTime; namespace multipass { diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 10428ae726f..cc5020ac1b8 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1712,7 +1712,7 @@ try // clang-format on auto timestamp = fundamentals->mutable_creation_timestamp(); timestamp->set_seconds(snapshot->get_creation_timestamp().toSecsSinceEpoch()); - timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1000000); + timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); }; if (const auto& it = instance_snapshots_map.find(name); diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index c90eb105a08..0c4b7868dc5 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -78,8 +78,9 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa } } // namespace -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, - int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) + const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, + MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata, std::shared_ptr parent) : name{name}, diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 35af8560121..6b6b4a7d234 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -40,8 +40,8 @@ class BaseSnapshot : public Snapshot std::string get_name() const override; std::string get_comment() const override; - std::string get_parent_name() const override; QDateTime get_creation_timestamp() const override; + std::string get_parent_name() const override; std::shared_ptr get_parent() const override; std::shared_ptr get_parent() override; From 25d31880e3d687969511ddcfa2d2066c936bc0e8 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 27 Jun 2023 23:07:33 -0700 Subject: [PATCH 293/627] [snapshot] move const members together --- src/platform/backends/shared/base_snapshot.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 6b6b4a7d234..852e5aa6553 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -74,7 +74,7 @@ class BaseSnapshot : public Snapshot { }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); - BaseSnapshot(const std::string& name, const std::string& comment, const QDateTime& creation_timestamp, + BaseSnapshot(const std::string& name, const std::string& get_comment, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata, std::shared_ptr parent); @@ -82,9 +82,9 @@ class BaseSnapshot : public Snapshot private: std::string name; std::string comment; - const QDateTime creation_timestamp; // This class is non-copyable and having these const simplifies thread safety + const QDateTime creation_timestamp; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const MemorySize mem_size; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const MemorySize disk_space; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) From 4b800d56a638ac3d4b5710895c038e83ace5ed3f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 28 Jun 2023 09:56:47 -0700 Subject: [PATCH 294/627] [snapshot] include ms in recorded snapshot time --- src/platform/backends/shared/base_snapshot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 0c4b7868dc5..4bc58724845 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -128,7 +128,7 @@ mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMac : BaseSnapshot{json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment QDateTime::fromString(json["creation_timestamp"].toString(), - Qt::ISODate), // creation_timestamp + Qt::ISODateWithMs), // creation_timestamp json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space @@ -146,7 +146,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); - snapshot.insert("creation_timestamp", creation_timestamp.toString(Qt::ISODate)); + snapshot.insert("creation_timestamp", creation_timestamp.toString(Qt::ISODateWithMs)); snapshot.insert("num_cores", num_cores); snapshot.insert("mem_size", QString::number(mem_size.in_bytes())); snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); From 9372a3e33d42520d949a15b5ae019e0398af126c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 27 Jun 2023 22:29:59 -0700 Subject: [PATCH 295/627] [cli] avoid using word to define itself --- src/client/cli/cmd/delete.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index 92a067d9bdc..e84b15c57a7 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -92,7 +92,7 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) "[.snapshot] [[.snapshot] ...]"); QCommandLineOption all_option(all_option_name, "Delete all instances and snapshots"); - QCommandLineOption purge_option({"p", "purge"}, "Purge specified instances and snapshots immediately"); + QCommandLineOption purge_option({"p", "purge"}, "Permanently delete specified instances and snapshots immediately"); parser->addOptions({all_option, purge_option}); auto status = parser->commandParse(this); From 79ea05641b79738534dbb497adbdf970a962365a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 3 Jul 2023 10:11:46 -0700 Subject: [PATCH 296/627] [cli] add custom error msg when mixing instances and snapshots with purge flag --- src/client/cli/cmd/delete.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index e84b15c57a7..0d93ddf9a22 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -104,17 +104,40 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) return parse_code; request.set_purge(parser->isSet(purge_option)); + + bool instance_found = false, snapshot_found = false; + std::string instances, snapshots; for (const auto& item : add_instance_and_snapshot_names(parser)) { - if (item.has_snapshot_name() && !request.purge()) + if (!item.has_snapshot_name()) + { + instances.append(fmt::format("{} ", item.instance_name())); + instance_found = true; + } + else { - cerr << "Snapshots can only be purged (after deletion, they cannot be recovered). Please use the `--purge` " - "flag if that is what you want.\n"; - return mp::ParseCode::CommandLineError; + snapshots.append(fmt::format("{}.{} ", item.instance_name(), item.snapshot_name())); + snapshot_found = true; } request.add_instances_snapshots()->CopyFrom(item); } + if (snapshot_found && !request.purge()) + { + auto no_purge_base_error_msg = + "Snapshots can only be purged (after deletion, they cannot be recovered). Please use the `--purge` " + "flag if that is what you want"; + + if (instance_found) + cerr << fmt::format("{}:\n\n\tmultipass delete --purge {}\n\nYou can use a separate command to delete " + "instances without purging them:\n\n\tmultipass delete {}\n", + no_purge_base_error_msg, snapshots, instances); + else + cerr << fmt::format("{}.\n", no_purge_base_error_msg); + + return mp::ParseCode::CommandLineError; + } + return status; } From 3b475a12aadc102de402ce7b387decd88b3deac6 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Jul 2023 07:24:56 -0700 Subject: [PATCH 297/627] [cli] move string to unnamed namespace --- src/client/cli/cmd/delete.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index 0d93ddf9a22..ca36a3128b0 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -24,6 +24,13 @@ namespace mp = multipass; namespace cmd = multipass::cmd; +namespace +{ +constexpr auto no_purge_base_error_msg = + "Snapshots can only be purged (after deletion, they cannot be recovered). Please use the `--purge` " + "flag if that is what you want"; +} + mp::ReturnCode cmd::Delete::run(mp::ArgParser* parser) { auto ret = parse_args(parser); @@ -125,10 +132,6 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) if (snapshot_found && !request.purge()) { - auto no_purge_base_error_msg = - "Snapshots can only be purged (after deletion, they cannot be recovered). Please use the `--purge` " - "flag if that is what you want"; - if (instance_found) cerr << fmt::format("{}:\n\n\tmultipass delete --purge {}\n\nYou can use a separate command to delete " "instances without purging them:\n\n\tmultipass delete {}\n", From 8655dfab81f242425bf1430125c23274d2e2259d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 13 Jun 2023 12:21:07 +0100 Subject: [PATCH 298/627] [qemu] Keep a VM description ref in snapshots --- src/platform/backends/qemu/qemu_snapshot.cpp | 9 +++++---- src/platform/backends/qemu/qemu_snapshot.h | 10 ++++++---- src/platform/backends/qemu/qemu_virtual_machine.cpp | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 53222eb8e44..55331134cdb 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -21,6 +21,7 @@ #include #include +#include #include @@ -64,13 +65,13 @@ void checked_exec_qemu_img(std::unique_ptr spec) } // namespace mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - const QString& image_path, std::shared_ptr parent) - : BaseSnapshot(name, comment, specs, std::move(parent)), image_path{image_path} + std::shared_ptr parent, VirtualMachineDescription& desc) + : BaseSnapshot(name, comment, specs, std::move(parent)), desc{desc}, image_path{desc.image.image_path} { } -mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, const QString& image_path, QemuVirtualMachine& vm) - : BaseSnapshot(json, vm), image_path{image_path} +mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc) + : BaseSnapshot(json, vm), desc{desc}, image_path{desc.image.image_path} { } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 1d505123aaf..49adf86f3a7 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -24,13 +24,14 @@ namespace multipass { class QemuVirtualMachine; +class VirtualMachineDescription; class QemuSnapshot : public BaseSnapshot { public: - QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, const QString& image_path, - std::shared_ptr parent); - QemuSnapshot(const QJsonObject& json, const QString& image_path, QemuVirtualMachine& vm); + QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + std::shared_ptr parent, VirtualMachineDescription& desc); + QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc); protected: void capture_impl() override; @@ -41,7 +42,8 @@ class QemuSnapshot : public BaseSnapshot QString derive_tag() const; private: - QString image_path; + VirtualMachineDescription& desc; + const QString& image_path; }; } // namespace multipass diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 0cd78b5ff39..a3d3e93de8b 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -620,10 +620,10 @@ auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, con -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise - return std::make_shared(name, comment, specs, desc.image.image_path, std::move(parent)); + return std::make_shared(name, comment, specs, std::move(parent), desc); } auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr { - return std::make_shared(json, desc.image.image_path, *this); + return std::make_shared(json, *this, desc); } From 0450ccbd4f2af27071a19acbe76bac8436eeb80a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 13 Jun 2023 12:33:30 +0100 Subject: [PATCH 299/627] [qemu] Update modifiable properties in snapshot Update modifiable VM properties in the QEMU VM's internal description, when applying a snapshot. --- src/platform/backends/qemu/qemu_snapshot.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 55331134cdb..00d4052bf92 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -95,6 +95,9 @@ void mp::QemuSnapshot::erase_impl() void mp::QemuSnapshot::apply_impl() { + desc.num_cores = BaseSnapshot::get_num_cores(); + desc.mem_size = BaseSnapshot::get_mem_size(); + desc.disk_space = BaseSnapshot::get_disk_space(); checked_exec_qemu_img(make_restore_spec(derive_tag(), image_path)); } From 266080b6e24b658cec91dae27516a88d9b11025f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 10 Jul 2023 08:27:21 -0700 Subject: [PATCH 300/627] [cli] refactor changes into separate functions for readability --- src/client/cli/cmd/delete.cpp | 23 ++++++++++++++++++----- src/client/cli/cmd/delete.h | 3 +++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index ca36a3128b0..f62d4c1ea59 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -106,15 +106,22 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) if (status != ParseCode::Ok) return status; - auto parse_code = check_for_name_and_all_option_conflict(parser, cerr); - if (parse_code != ParseCode::Ok) - return parse_code; + status = check_for_name_and_all_option_conflict(parser, cerr); + if (status != ParseCode::Ok) + return status; request.set_purge(parser->isSet(purge_option)); + status = parse_instances_snapshots(parser); + + return status; +} + +mp::ParseCode cmd::Delete::parse_instances_snapshots(mp::ArgParser* parser) +{ bool instance_found = false, snapshot_found = false; std::string instances, snapshots; - for (const auto& item : add_instance_and_snapshot_names(parser)) + for (const auto& item : cmd::add_instance_and_snapshot_names(parser)) { if (!item.has_snapshot_name()) { @@ -130,6 +137,12 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) request.add_instances_snapshots()->CopyFrom(item); } + return enforce_purged_snapshots(instances, snapshots, instance_found, snapshot_found); +} + +mp::ParseCode cmd::Delete::enforce_purged_snapshots(std::string& instances, std::string& snapshots, bool instance_found, + bool snapshot_found) +{ if (snapshot_found && !request.purge()) { if (instance_found) @@ -142,5 +155,5 @@ mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) return mp::ParseCode::CommandLineError; } - return status; + return mp::ParseCode::Ok; } diff --git a/src/client/cli/cmd/delete.h b/src/client/cli/cmd/delete.h index 058820191a6..2d06d76b8c7 100644 --- a/src/client/cli/cmd/delete.h +++ b/src/client/cli/cmd/delete.h @@ -45,6 +45,9 @@ class Delete final : public Command DeleteRequest request; ParseCode parse_args(ArgParser* parser); + ParseCode parse_instances_snapshots(ArgParser* parser); + ParseCode enforce_purged_snapshots(std::string& instances, std::string& snapshots, bool instance_found, + bool snapshot_found); }; } // namespace cmd } // namespace multipass From 414e1edd42fccfa26e192aea4c1686c14570a998 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 13 Jun 2023 13:07:22 +0100 Subject: [PATCH 301/627] [qemu] Rollback description update on apply error --- src/platform/backends/qemu/qemu_snapshot.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 00d4052bf92..2aa713d8e30 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -21,8 +21,11 @@ #include #include +#include #include +#include + #include namespace mp = multipass; @@ -95,10 +98,17 @@ void mp::QemuSnapshot::erase_impl() void mp::QemuSnapshot::apply_impl() { + + auto rollback = sg::make_scope_guard([this, old_desc = desc]() noexcept { + top_catch_all(BaseSnapshot::get_name(), [this, &old_desc]() { desc = old_desc; }); + }); + desc.num_cores = BaseSnapshot::get_num_cores(); desc.mem_size = BaseSnapshot::get_mem_size(); desc.disk_space = BaseSnapshot::get_disk_space(); checked_exec_qemu_img(make_restore_spec(derive_tag(), image_path)); + + rollback.dismiss(); } QString mp::QemuSnapshot::derive_tag() const From a50acf8cabf373647e60e7e3da4aeb87d5591490 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 13 Jun 2023 13:08:47 +0100 Subject: [PATCH 302/627] [qemu] Remove unnecessary qualification in calls --- src/platform/backends/qemu/qemu_snapshot.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 2aa713d8e30..aaca4329dba 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -98,16 +98,14 @@ void mp::QemuSnapshot::erase_impl() void mp::QemuSnapshot::apply_impl() { + auto rollback = sg::make_scope_guard( + [this, old_desc = desc]() noexcept { top_catch_all(get_name(), [this, &old_desc]() { desc = old_desc; }); }); - auto rollback = sg::make_scope_guard([this, old_desc = desc]() noexcept { - top_catch_all(BaseSnapshot::get_name(), [this, &old_desc]() { desc = old_desc; }); - }); + desc.num_cores = get_num_cores(); + desc.mem_size = get_mem_size(); + desc.disk_space = get_disk_space(); - desc.num_cores = BaseSnapshot::get_num_cores(); - desc.mem_size = BaseSnapshot::get_mem_size(); - desc.disk_space = BaseSnapshot::get_disk_space(); checked_exec_qemu_img(make_restore_spec(derive_tag(), image_path)); - rollback.dismiss(); } From b0ef80115ad2a85cc6cd77b594cac94ef26d8f2d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 22:55:15 +0100 Subject: [PATCH 303/627] [daemon] Implement skeleton for mount updating Implement the skeleton of a mount-updating function and call it when restoring a snapshot. --- src/daemon/daemon.cpp | 56 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index cc5020ac1b8..cf2b723dc93 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1214,6 +1214,48 @@ mp::SettingsHandler* register_instance_mod(std::unordered_map& vm_mounts, + mp::VirtualMachine* vm) +{ + auto& mount_specs = vm_specs.mounts; + + // Erase any outdated mount handlers + for (auto mounts_it = vm_mounts.begin(); mounts_it != vm_mounts.end(); ++mounts_it) + { + if (auto it = mount_specs.find(mounts_it->first); + it == mount_specs.end() /* TODO@ricab || mounts don't match */) + { + // TODO@ricab handle managed mounts properly + vm_mounts.erase(mounts_it); + } + } + + // Add handlers for any new mounts + std::vector mounts_to_remove; + for (const auto& [target, mount_spec] : mount_specs) + { + if (vm_mounts.find(target) == vm_mounts.end()) + { + try + { + // TODO@ricab make mount and insert + } + catch (const std::exception& e) + { + mpl::log(mpl::Level::warning, category, + fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", mount_spec.source_path, target, + vm->vm_name, e.what())); + mounts_to_remove.push_back(target); + } + } + } + + for (const auto& mount_target : mounts_to_remove) + mount_specs.erase(mount_target); // TODO@ricab could have kept the iterator + + // TODO@ricab what do we do about persisting? +} + } // namespace mp::Daemon::Daemon(std::unique_ptr the_config) @@ -2517,21 +2559,27 @@ try auto spec_it = vm_instance_specs.find(instance_name); assert(spec_it != vm_instance_specs.end() && "missing instance specs"); + auto& vm_specs = spec_it->second; const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); - const auto snapshot = vm_ptr->take_snapshot(vm_dir, spec_it->second, "", - fmt::format("Before restoring {}", request->snapshot())); + const auto snapshot = + vm_ptr->take_snapshot(vm_dir, vm_specs, "", fmt::format("Before restoring {}", request->snapshot())); reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), /* sticky = */ true); } reply_msg(server, "Restoring snapshot"); - vm_ptr->restore_snapshot(vm_dir, request->snapshot(), spec_it->second); + vm_ptr->restore_snapshot(vm_dir, request->snapshot(), vm_specs); + + auto mounts_it = mounts.find(instance_name); + assert(mounts_it != mounts.end() && "uninitialized mounts"); + + update_mounts(vm_specs, mounts_it->second, vm_ptr); persist_instances(); server->Write(reply); @@ -3056,7 +3104,7 @@ grpc::Status mp::Daemon::get_ssh_info_for_vm(VirtualMachine& vm, SSHInfoReply& r return grpc::Status::OK; } -void mp::Daemon::init_mounts(const std::string& name) +void mp::Daemon::init_mounts(const std::string& name) // TODO@ricab this is now a particular case of update_mounts { auto& vm_mounts = mounts[name]; auto& vm_spec_mounts = vm_instance_specs[name].mounts; From 652e54b32cfa2f9cace987b6bfa58ab8b2fd77a4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 16:37:41 +0100 Subject: [PATCH 304/627] [daemon] Turn mount-update function into method --- src/daemon/daemon.cpp | 85 ++++++++++++++++++++++--------------------- src/daemon/daemon.h | 2 + 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index cf2b723dc93..c7c10b91505 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1214,48 +1214,6 @@ mp::SettingsHandler* register_instance_mod(std::unordered_map& vm_mounts, - mp::VirtualMachine* vm) -{ - auto& mount_specs = vm_specs.mounts; - - // Erase any outdated mount handlers - for (auto mounts_it = vm_mounts.begin(); mounts_it != vm_mounts.end(); ++mounts_it) - { - if (auto it = mount_specs.find(mounts_it->first); - it == mount_specs.end() /* TODO@ricab || mounts don't match */) - { - // TODO@ricab handle managed mounts properly - vm_mounts.erase(mounts_it); - } - } - - // Add handlers for any new mounts - std::vector mounts_to_remove; - for (const auto& [target, mount_spec] : mount_specs) - { - if (vm_mounts.find(target) == vm_mounts.end()) - { - try - { - // TODO@ricab make mount and insert - } - catch (const std::exception& e) - { - mpl::log(mpl::Level::warning, category, - fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", mount_spec.source_path, target, - vm->vm_name, e.what())); - mounts_to_remove.push_back(target); - } - } - } - - for (const auto& mount_target : mounts_to_remove) - mount_specs.erase(mount_target); // TODO@ricab could have kept the iterator - - // TODO@ricab what do we do about persisting? -} - } // namespace mp::Daemon::Daemon(std::unique_ptr the_config) @@ -3143,6 +3101,49 @@ void mp::Daemon::stop_mounts(const std::string& name) } } +void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, + std::unordered_map& vm_mounts, + mp::VirtualMachine* vm) +{ + auto& mount_specs = vm_specs.mounts; + + // Erase any outdated mount handlers + for (auto mounts_it = vm_mounts.begin(); mounts_it != vm_mounts.end(); ++mounts_it) + { + if (auto it = mount_specs.find(mounts_it->first); + it == mount_specs.end() /* TODO@ricab || mounts don't match */) + { + // TODO@ricab handle managed mounts properly + vm_mounts.erase(mounts_it); + } + } + + // Add handlers for any new mounts + std::vector mounts_to_remove; + for (const auto& [target, mount_spec] : mount_specs) + { + if (vm_mounts.find(target) == vm_mounts.end()) + { + try + { + // TODO@ricab make mount and insert + } + catch (const std::exception& e) + { + mpl::log(mpl::Level::warning, category, + fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", mount_spec.source_path, target, + vm->vm_name, e.what())); + mounts_to_remove.push_back(target); + } + } + } + + for (const auto& mount_target : mounts_to_remove) + mount_specs.erase(mount_target); // TODO@ricab could have kept the iterator + + // TODO@ricab what do we do about persisting? +} + mp::MountHandler::UPtr mp::Daemon::make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount) { return mount.mount_type == VMMount::MountType::Classic diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 451417afe16..4b69aa3ef86 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -158,6 +158,8 @@ public slots: grpc::Status get_ssh_info_for_vm(VirtualMachine& vm, SSHInfoReply& response); void init_mounts(const std::string& name); void stop_mounts(const std::string& name); + void update_mounts(VMSpecs& vm_specs, std::unordered_map& vm_mounts, + VirtualMachine* vm); MountHandler::UPtr make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount); struct AsyncOperationStatus From 1cd96beb867a23d7bbacb7e0f3a691c4d4cac6f0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 16:41:40 +0100 Subject: [PATCH 305/627] [daemon] Actually create mounts when updating --- src/daemon/daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index c7c10b91505..6c50f8cebdd 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3126,7 +3126,7 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, { try { - // TODO@ricab make mount and insert + vm_mounts[target] = make_mount(vm, target, mount_spec); } catch (const std::exception& e) { From 8fc376954f282c31b786a59a318043d687c1a24d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 18:45:53 +0100 Subject: [PATCH 306/627] [mounts] Keep mount spec in the base MountHandler --- include/multipass/mount_handler.h | 14 ++++++++++---- .../multipass/sshfs_mount/sshfs_mount_handler.h | 2 +- src/platform/backends/lxd/lxd_mount_handler.cpp | 4 ++-- src/platform/backends/lxd/lxd_mount_handler.h | 2 +- src/platform/backends/qemu/qemu_mount_handler.cpp | 10 +++++----- src/platform/backends/qemu/qemu_mount_handler.h | 2 +- src/sshfs_mount/sshfs_mount_handler.cpp | 12 ++++++------ 7 files changed, 26 insertions(+), 20 deletions(-) diff --git a/include/multipass/mount_handler.h b/include/multipass/mount_handler.h index 83f18aa74fd..911808d08d9 100644 --- a/include/multipass/mount_handler.h +++ b/include/multipass/mount_handler.h @@ -79,9 +79,14 @@ class MountHandler : private DisabledCopyMove protected: MountHandler() = default; - MountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, - const std::string& source) - : vm{vm}, ssh_key_provider{ssh_key_provider}, target{target}, source{source}, active{false} + MountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, VMMount mount_spec, + const std::string& target, const std::string& source) + : vm{vm}, + ssh_key_provider{ssh_key_provider}, + mount_spec{std::move(mount_spec)}, + target{target}, + source{source}, + active{false} { std::error_code err; auto source_status = MP_FILEOPS.status(source, err); @@ -114,8 +119,9 @@ class MountHandler : private DisabledCopyMove VirtualMachine* vm; const SSHKeyProvider* ssh_key_provider; + const VMMount mount_spec = {}; const std::string target; - const std::string source; + const std::string source; // TODO@ricab drop this bool active; std::mutex active_mutex; diff --git a/include/multipass/sshfs_mount/sshfs_mount_handler.h b/include/multipass/sshfs_mount/sshfs_mount_handler.h index 71c0c211e63..b25a39355c4 100644 --- a/include/multipass/sshfs_mount/sshfs_mount_handler.h +++ b/include/multipass/sshfs_mount/sshfs_mount_handler.h @@ -29,7 +29,7 @@ class SSHFSMountHandler : public MountHandler { public: SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, - const VMMount& mount); + const VMMount& mount_spec); ~SSHFSMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/platform/backends/lxd/lxd_mount_handler.cpp b/src/platform/backends/lxd/lxd_mount_handler.cpp index c2dc81c2539..4f4bfe7dbd2 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.cpp +++ b/src/platform/backends/lxd/lxd_mount_handler.cpp @@ -29,8 +29,8 @@ namespace multipass { LXDMountHandler::LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, const SSHKeyProvider* ssh_key_provider, const std::string& target_path, - const VMMount& mount) - : MountHandler{lxd_virtual_machine, ssh_key_provider, target_path, mount.source_path}, + const VMMount& mount_spec) + : MountHandler{lxd_virtual_machine, ssh_key_provider, mount_spec, target_path, mount_spec.source_path}, network_manager{network_manager}, lxd_instance_endpoint{ QString("%1/instances/%2").arg(lxd_socket_url.toString(), lxd_virtual_machine->vm_name.c_str())}, diff --git a/src/platform/backends/lxd/lxd_mount_handler.h b/src/platform/backends/lxd/lxd_mount_handler.h index 713135ff914..415f30a71c6 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.h +++ b/src/platform/backends/lxd/lxd_mount_handler.h @@ -27,7 +27,7 @@ class LXDMountHandler : public MountHandler { public: LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, - const SSHKeyProvider* ssh_key_provider, const std::string& target_path, const VMMount& mount); + const SSHKeyProvider* ssh_key_provider, const std::string& target_path, const VMMount& mount_spec); ~LXDMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/platform/backends/qemu/qemu_mount_handler.cpp b/src/platform/backends/qemu/qemu_mount_handler.cpp index 63afa20d226..cfeb9bb8842 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.cpp +++ b/src/platform/backends/qemu/qemu_mount_handler.cpp @@ -32,8 +32,8 @@ constexpr auto category = "qemu-mount-handler"; namespace multipass { QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, - const std::string& target, const VMMount& mount) - : MountHandler{vm, ssh_key_provider, target, mount.source_path}, + const std::string& target, const VMMount& mount_spec) + : MountHandler{vm, ssh_key_provider, mount_spec, target, mount_spec.source_path}, vm_mount_args{vm->modifiable_mount_args()}, // Create a reproducible unique mount tag for each mount. The cmd arg can only be 31 bytes long so part of the // uuid must be truncated. First character of tag must also be alphabetical. @@ -53,14 +53,14 @@ QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* } // Need to ensure no more than one uid/gid map is passed in here. - if (mount.uid_mappings.size() > 1 || mount.gid_mappings.size() > 1) + if (mount_spec.uid_mappings.size() > 1 || mount_spec.gid_mappings.size() > 1) throw std::runtime_error("Only one mapping per native mount allowed."); mpl::log(mpl::Level::info, category, fmt::format("initializing native mount {} => {} in '{}'", source, target, vm->vm_name)); - const auto uid_map = mount.uid_mappings.empty() ? std::make_pair(1000, 1000) : mount.uid_mappings[0]; - const auto gid_map = mount.gid_mappings.empty() ? std::make_pair(1000, 1000) : mount.gid_mappings[0]; + const auto uid_map = mount_spec.uid_mappings.empty() ? std::make_pair(1000, 1000) : mount_spec.uid_mappings[0]; + const auto gid_map = mount_spec.gid_mappings.empty() ? std::make_pair(1000, 1000) : mount_spec.gid_mappings[0]; const auto uid_arg = QString("uid_map=%1:%2,").arg(uid_map.first).arg(uid_map.second == -1 ? 1000 : uid_map.second); const auto gid_arg = QString{"gid_map=%1:%2,"}.arg(gid_map.first).arg(gid_map.second == -1 ? 1000 : gid_map.second); vm_mount_args[tag] = { diff --git a/src/platform/backends/qemu/qemu_mount_handler.h b/src/platform/backends/qemu/qemu_mount_handler.h index d263e997e2f..fffde285c64 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.h +++ b/src/platform/backends/qemu/qemu_mount_handler.h @@ -28,7 +28,7 @@ class QemuMountHandler : public MountHandler { public: QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, - const VMMount& mount); + const VMMount& mount_spec); ~QemuMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/sshfs_mount/sshfs_mount_handler.cpp b/src/sshfs_mount/sshfs_mount_handler.cpp index 76f4c679c78..a338b92c5a6 100644 --- a/src/sshfs_mount/sshfs_mount_handler.cpp +++ b/src/sshfs_mount/sshfs_mount_handler.cpp @@ -112,21 +112,21 @@ catch (const mp::ExitlessSSHProcessException&) namespace multipass { SSHFSMountHandler::SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, - const std::string& target, const VMMount& mount) - : MountHandler{vm, ssh_key_provider, target, mount.source_path}, + const std::string& target, const VMMount& mount_spec) + : MountHandler{vm, ssh_key_provider, mount_spec, target, mount_spec.source_path}, process{nullptr}, config{"", 0, vm->ssh_username(), vm->vm_name, ssh_key_provider->private_key_as_base64(), - mount.source_path, + mount_spec.source_path, target, - mount.gid_mappings, - mount.uid_mappings} + mount_spec.gid_mappings, + mount_spec.uid_mappings} { mpl::log(mpl::Level::info, category, - fmt::format("initializing mount {} => {} in '{}'", mount.source_path, target, vm->vm_name)); + fmt::format("initializing mount {} => {} in '{}'", mount_spec.source_path, target, vm->vm_name)); } bool SSHFSMountHandler::is_active() From 478a85ded6c99207d0ee07cde54bbdfce45aeb99 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 22:59:23 +0100 Subject: [PATCH 307/627] [mounts] Remove obsolete source param from ctor Remove source parameter from the MountHandler's constructor, since that is now available via the mount spec. --- include/multipass/mount_handler.h | 12 +++--------- src/platform/backends/lxd/lxd_mount_handler.cpp | 2 +- src/platform/backends/qemu/qemu_mount_handler.cpp | 2 +- src/sshfs_mount/sshfs_mount_handler.cpp | 2 +- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/include/multipass/mount_handler.h b/include/multipass/mount_handler.h index 911808d08d9..bdb5f56d7b3 100644 --- a/include/multipass/mount_handler.h +++ b/include/multipass/mount_handler.h @@ -80,13 +80,8 @@ class MountHandler : private DisabledCopyMove protected: MountHandler() = default; MountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, VMMount mount_spec, - const std::string& target, const std::string& source) - : vm{vm}, - ssh_key_provider{ssh_key_provider}, - mount_spec{std::move(mount_spec)}, - target{target}, - source{source}, - active{false} + const std::string& target) + : vm{vm}, ssh_key_provider{ssh_key_provider}, mount_spec{std::move(mount_spec)}, target{target}, active{false} { std::error_code err; auto source_status = MP_FILEOPS.status(source, err); @@ -121,8 +116,7 @@ class MountHandler : private DisabledCopyMove const SSHKeyProvider* ssh_key_provider; const VMMount mount_spec = {}; const std::string target; - const std::string source; // TODO@ricab drop this - + const std::string& source = mount_spec.source_path; bool active; std::mutex active_mutex; }; diff --git a/src/platform/backends/lxd/lxd_mount_handler.cpp b/src/platform/backends/lxd/lxd_mount_handler.cpp index 4f4bfe7dbd2..73ec96a7949 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.cpp +++ b/src/platform/backends/lxd/lxd_mount_handler.cpp @@ -30,7 +30,7 @@ namespace multipass LXDMountHandler::LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, const SSHKeyProvider* ssh_key_provider, const std::string& target_path, const VMMount& mount_spec) - : MountHandler{lxd_virtual_machine, ssh_key_provider, mount_spec, target_path, mount_spec.source_path}, + : MountHandler{lxd_virtual_machine, ssh_key_provider, mount_spec, target_path}, network_manager{network_manager}, lxd_instance_endpoint{ QString("%1/instances/%2").arg(lxd_socket_url.toString(), lxd_virtual_machine->vm_name.c_str())}, diff --git a/src/platform/backends/qemu/qemu_mount_handler.cpp b/src/platform/backends/qemu/qemu_mount_handler.cpp index cfeb9bb8842..62724949b29 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.cpp +++ b/src/platform/backends/qemu/qemu_mount_handler.cpp @@ -33,7 +33,7 @@ namespace multipass { QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount_spec) - : MountHandler{vm, ssh_key_provider, mount_spec, target, mount_spec.source_path}, + : MountHandler{vm, ssh_key_provider, mount_spec, target}, vm_mount_args{vm->modifiable_mount_args()}, // Create a reproducible unique mount tag for each mount. The cmd arg can only be 31 bytes long so part of the // uuid must be truncated. First character of tag must also be alphabetical. diff --git a/src/sshfs_mount/sshfs_mount_handler.cpp b/src/sshfs_mount/sshfs_mount_handler.cpp index a338b92c5a6..456c9f1b99d 100644 --- a/src/sshfs_mount/sshfs_mount_handler.cpp +++ b/src/sshfs_mount/sshfs_mount_handler.cpp @@ -113,7 +113,7 @@ namespace multipass { SSHFSMountHandler::SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount_spec) - : MountHandler{vm, ssh_key_provider, mount_spec, target, mount_spec.source_path}, + : MountHandler{vm, ssh_key_provider, mount_spec, target}, process{nullptr}, config{"", 0, From 80ff8fc92fe92f81cb7c3bfa2f06f4f0e7300444 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 19:05:58 +0100 Subject: [PATCH 308/627] [mounts] Add accessor to mount spec in handler --- include/multipass/mount_handler.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/multipass/mount_handler.h b/include/multipass/mount_handler.h index bdb5f56d7b3..74e7fec5a9c 100644 --- a/include/multipass/mount_handler.h +++ b/include/multipass/mount_handler.h @@ -67,6 +67,11 @@ class MountHandler : private DisabledCopyMove active = false; } + const VMMount& get_mount_spec() const + { + return mount_spec; + } + virtual bool is_active() { return active; From 1d855514cc8d87f7e26cef6184ae9e2675d51dba Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 19:25:51 +0100 Subject: [PATCH 309/627] [mounts] Take the mount spec by value and move Take `mount_spec` by value and move, also in concrete mount handlers. --- include/multipass/sshfs_mount/sshfs_mount_handler.h | 2 +- src/platform/backends/lxd/lxd_mount_handler.cpp | 4 ++-- src/platform/backends/lxd/lxd_mount_handler.h | 2 +- src/platform/backends/qemu/qemu_mount_handler.cpp | 12 +++++++----- src/platform/backends/qemu/qemu_mount_handler.h | 2 +- src/sshfs_mount/sshfs_mount_handler.cpp | 12 ++++++------ 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/include/multipass/sshfs_mount/sshfs_mount_handler.h b/include/multipass/sshfs_mount/sshfs_mount_handler.h index b25a39355c4..9f98409b877 100644 --- a/include/multipass/sshfs_mount/sshfs_mount_handler.h +++ b/include/multipass/sshfs_mount/sshfs_mount_handler.h @@ -29,7 +29,7 @@ class SSHFSMountHandler : public MountHandler { public: SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, - const VMMount& mount_spec); + VMMount mount_spec); ~SSHFSMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/platform/backends/lxd/lxd_mount_handler.cpp b/src/platform/backends/lxd/lxd_mount_handler.cpp index 73ec96a7949..d0815770f8b 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.cpp +++ b/src/platform/backends/lxd/lxd_mount_handler.cpp @@ -29,8 +29,8 @@ namespace multipass { LXDMountHandler::LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, const SSHKeyProvider* ssh_key_provider, const std::string& target_path, - const VMMount& mount_spec) - : MountHandler{lxd_virtual_machine, ssh_key_provider, mount_spec, target_path}, + VMMount mount_spec) + : MountHandler{lxd_virtual_machine, ssh_key_provider, std::move(mount_spec), target_path}, network_manager{network_manager}, lxd_instance_endpoint{ QString("%1/instances/%2").arg(lxd_socket_url.toString(), lxd_virtual_machine->vm_name.c_str())}, diff --git a/src/platform/backends/lxd/lxd_mount_handler.h b/src/platform/backends/lxd/lxd_mount_handler.h index 415f30a71c6..cce12b48836 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.h +++ b/src/platform/backends/lxd/lxd_mount_handler.h @@ -27,7 +27,7 @@ class LXDMountHandler : public MountHandler { public: LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, - const SSHKeyProvider* ssh_key_provider, const std::string& target_path, const VMMount& mount_spec); + const SSHKeyProvider* ssh_key_provider, const std::string& target_path, VMMount mount_spec); ~LXDMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/platform/backends/qemu/qemu_mount_handler.cpp b/src/platform/backends/qemu/qemu_mount_handler.cpp index 62724949b29..dd04a47fd32 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.cpp +++ b/src/platform/backends/qemu/qemu_mount_handler.cpp @@ -32,8 +32,8 @@ constexpr auto category = "qemu-mount-handler"; namespace multipass { QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, - const std::string& target, const VMMount& mount_spec) - : MountHandler{vm, ssh_key_provider, mount_spec, target}, + const std::string& target, VMMount mount_spec) + : MountHandler{vm, ssh_key_provider, std::move(mount_spec), target}, vm_mount_args{vm->modifiable_mount_args()}, // Create a reproducible unique mount tag for each mount. The cmd arg can only be 31 bytes long so part of the // uuid must be truncated. First character of tag must also be alphabetical. @@ -53,14 +53,16 @@ QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* } // Need to ensure no more than one uid/gid map is passed in here. - if (mount_spec.uid_mappings.size() > 1 || mount_spec.gid_mappings.size() > 1) + if (this->mount_spec.uid_mappings.size() > 1 || this->mount_spec.gid_mappings.size() > 1) throw std::runtime_error("Only one mapping per native mount allowed."); mpl::log(mpl::Level::info, category, fmt::format("initializing native mount {} => {} in '{}'", source, target, vm->vm_name)); - const auto uid_map = mount_spec.uid_mappings.empty() ? std::make_pair(1000, 1000) : mount_spec.uid_mappings[0]; - const auto gid_map = mount_spec.gid_mappings.empty() ? std::make_pair(1000, 1000) : mount_spec.gid_mappings[0]; + const auto uid_map = + this->mount_spec.uid_mappings.empty() ? std::make_pair(1000, 1000) : this->mount_spec.uid_mappings[0]; + const auto gid_map = + this->mount_spec.gid_mappings.empty() ? std::make_pair(1000, 1000) : this->mount_spec.gid_mappings[0]; const auto uid_arg = QString("uid_map=%1:%2,").arg(uid_map.first).arg(uid_map.second == -1 ? 1000 : uid_map.second); const auto gid_arg = QString{"gid_map=%1:%2,"}.arg(gid_map.first).arg(gid_map.second == -1 ? 1000 : gid_map.second); vm_mount_args[tag] = { diff --git a/src/platform/backends/qemu/qemu_mount_handler.h b/src/platform/backends/qemu/qemu_mount_handler.h index fffde285c64..0e4c3bd6752 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.h +++ b/src/platform/backends/qemu/qemu_mount_handler.h @@ -28,7 +28,7 @@ class QemuMountHandler : public MountHandler { public: QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, - const VMMount& mount_spec); + VMMount mount_spec); ~QemuMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/sshfs_mount/sshfs_mount_handler.cpp b/src/sshfs_mount/sshfs_mount_handler.cpp index 456c9f1b99d..9b308bfed9a 100644 --- a/src/sshfs_mount/sshfs_mount_handler.cpp +++ b/src/sshfs_mount/sshfs_mount_handler.cpp @@ -112,21 +112,21 @@ catch (const mp::ExitlessSSHProcessException&) namespace multipass { SSHFSMountHandler::SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, - const std::string& target, const VMMount& mount_spec) - : MountHandler{vm, ssh_key_provider, mount_spec, target}, + const std::string& target, VMMount mount_spec) + : MountHandler{vm, ssh_key_provider, std::move(mount_spec), target}, process{nullptr}, config{"", 0, vm->ssh_username(), vm->vm_name, ssh_key_provider->private_key_as_base64(), - mount_spec.source_path, + source, target, - mount_spec.gid_mappings, - mount_spec.uid_mappings} + this->mount_spec.gid_mappings, + this->mount_spec.uid_mappings} { mpl::log(mpl::Level::info, category, - fmt::format("initializing mount {} => {} in '{}'", mount_spec.source_path, target, vm->vm_name)); + fmt::format("initializing mount {} => {} in '{}'", this->mount_spec.source_path, target, vm->vm_name)); } bool SSHFSMountHandler::is_active() From 9247b438062dd472240f1806d31ea3b28817a1f8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 23:19:39 +0100 Subject: [PATCH 310/627] [daemon] Compare mount specs when updating mounts Compare the actual specs (not just the target), when looking for mount handlers to erase during mount updating. --- include/multipass/vm_mount.h | 6 ++++++ src/daemon/daemon.cpp | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/multipass/vm_mount.h b/include/multipass/vm_mount.h index 380051a6397..3f1e911b24f 100644 --- a/include/multipass/vm_mount.h +++ b/include/multipass/vm_mount.h @@ -43,6 +43,12 @@ inline bool operator==(const VMMount& a, const VMMount& b) return std::tie(a.source_path, a.gid_mappings, a.uid_mappings) == std::tie(b.source_path, b.gid_mappings, b.uid_mappings); } + +inline bool operator!=(const VMMount& a, const VMMount& b) // TODO drop in C++20 +{ + return !(a == b); +} + } // namespace multipass namespace fmt diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 6c50f8cebdd..93ebebbd425 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3110,8 +3110,8 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, // Erase any outdated mount handlers for (auto mounts_it = vm_mounts.begin(); mounts_it != vm_mounts.end(); ++mounts_it) { - if (auto it = mount_specs.find(mounts_it->first); - it == mount_specs.end() /* TODO@ricab || mounts don't match */) + const auto& [target, handler] = *mounts_it; + if (auto it = mount_specs.find(target); it == mount_specs.end() || handler->get_mount_spec() != it->second) { // TODO@ricab handle managed mounts properly vm_mounts.erase(mounts_it); From 50ec2c3fb3c78087ce72828394303005772c208d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 23:24:41 +0100 Subject: [PATCH 311/627] [daemon] Deactivate managed mounts before erasing --- src/daemon/daemon.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 93ebebbd425..69edf67539c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3113,7 +3113,12 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, const auto& [target, handler] = *mounts_it; if (auto it = mount_specs.find(target); it == mount_specs.end() || handler->get_mount_spec() != it->second) { - // TODO@ricab handle managed mounts properly + if (handler->is_mount_managed_by_backend()) + { + assert(handler->is_active()); + handler->deactivate(); + } + vm_mounts.erase(mounts_it); } } From 5ac17f5ad6a8c71da6880344ac155f1e234899fc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 29 Jun 2023 23:30:03 +0100 Subject: [PATCH 312/627] [daemon] Improve variable naming Rename a couple of iterators to facilitate distinction between mount specs and handlers. --- src/daemon/daemon.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 69edf67539c..313f1c9e285 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3108,10 +3108,11 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, auto& mount_specs = vm_specs.mounts; // Erase any outdated mount handlers - for (auto mounts_it = vm_mounts.begin(); mounts_it != vm_mounts.end(); ++mounts_it) + for (auto handlers_it = vm_mounts.begin(); handlers_it != vm_mounts.end(); ++handlers_it) { - const auto& [target, handler] = *mounts_it; - if (auto it = mount_specs.find(target); it == mount_specs.end() || handler->get_mount_spec() != it->second) + const auto& [target, handler] = *handlers_it; + if (auto specs_it = mount_specs.find(target); + specs_it == mount_specs.end() || handler->get_mount_spec() != specs_it->second) { if (handler->is_mount_managed_by_backend()) { @@ -3119,7 +3120,7 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, handler->deactivate(); } - vm_mounts.erase(mounts_it); + vm_mounts.erase(handlers_it); } } From f1c63faf991e086e1f9a26ffaaedbe592ab34021 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 30 Jun 2023 00:29:45 +0100 Subject: [PATCH 313/627] [daemon] Indicate mounts to remove with iterators Thus avoiding extra lookups. --- src/daemon/daemon.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 313f1c9e285..c7712451f69 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3125,9 +3125,11 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, } // Add handlers for any new mounts - std::vector mounts_to_remove; - for (const auto& [target, mount_spec] : mount_specs) + using MountSpecsIt = decltype(mount_specs.begin()); + std::vector mounts_to_remove{}; + for (auto specs_it = mount_specs.begin(); specs_it != mount_specs.end(); ++specs_it) { + const auto& [target, mount_spec] = *specs_it; if (vm_mounts.find(target) == vm_mounts.end()) { try @@ -3139,13 +3141,14 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, mpl::log(mpl::Level::warning, category, fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", mount_spec.source_path, target, vm->vm_name, e.what())); - mounts_to_remove.push_back(target); + + mounts_to_remove.push_back(specs_it); } } } - for (const auto& mount_target : mounts_to_remove) - mount_specs.erase(mount_target); // TODO@ricab could have kept the iterator + for (const auto& specs_it : mounts_to_remove) + mount_specs.erase(specs_it); // unordered_map, so iterators to other elements are not invalidated // TODO@ricab what do we do about persisting? } From af3f64a1213c6498b8411c91bc3fed69335eb838 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 22:15:03 +0100 Subject: [PATCH 314/627] [daemon] Erase mounts as they fail Rather than keeping track of them to erase later. --- src/daemon/daemon.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index c7712451f69..831939f5493 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3125,9 +3125,8 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, } // Add handlers for any new mounts - using MountSpecsIt = decltype(mount_specs.begin()); - std::vector mounts_to_remove{}; - for (auto specs_it = mount_specs.begin(); specs_it != mount_specs.end(); ++specs_it) + auto specs_it = mount_specs.begin(); + while (specs_it != mount_specs.end()) { const auto& [target, mount_spec] = *specs_it; if (vm_mounts.find(target) == vm_mounts.end()) @@ -3142,14 +3141,13 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", mount_spec.source_path, target, vm->vm_name, e.what())); - mounts_to_remove.push_back(specs_it); + specs_it = mount_specs.erase(specs_it); // unordered_map so only iterators to erased element invalidated + continue; } } + ++specs_it; } - for (const auto& specs_it : mounts_to_remove) - mount_specs.erase(specs_it); // unordered_map, so iterators to other elements are not invalidated - // TODO@ricab what do we do about persisting? } From 35b84fbb30c14029dfe5b139cf761952ae1e3024 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 30 Jun 2023 00:40:42 +0100 Subject: [PATCH 315/627] [daemon] Fix incrementing invalid iterator --- src/daemon/daemon.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 831939f5493..bdabdd56900 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3108,7 +3108,8 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, auto& mount_specs = vm_specs.mounts; // Erase any outdated mount handlers - for (auto handlers_it = vm_mounts.begin(); handlers_it != vm_mounts.end(); ++handlers_it) + auto handlers_it = vm_mounts.begin(); + while (handlers_it != vm_mounts.end()) { const auto& [target, handler] = *handlers_it; if (auto specs_it = mount_specs.find(target); @@ -3120,8 +3121,10 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, handler->deactivate(); } - vm_mounts.erase(handlers_it); + handlers_it = vm_mounts.erase(handlers_it); } + else + ++handlers_it; } // Add handlers for any new mounts From 15013b8ab906015f0b75f5e5dc6ce240682b18cf Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 18:08:19 +0100 Subject: [PATCH 316/627] [daemon] Extract pruning of old mount handlers --- src/daemon/daemon.cpp | 45 ++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index bdabdd56900..84ae49248ed 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1214,6 +1214,30 @@ mp::SettingsHandler* register_instance_mod(std::unordered_map& mount_specs, + std::unordered_map& vm_mounts) +{ + auto handlers_it = vm_mounts.begin(); + while (handlers_it != vm_mounts.end()) + { + const auto& [target, handler] = *handlers_it; + if (auto specs_it = mount_specs.find(target); + specs_it == mount_specs.end() || handler->get_mount_spec() != specs_it->second) + { + if (handler->is_mount_managed_by_backend()) + { + assert(handler->is_active()); + handler->deactivate(); + } + + handlers_it = vm_mounts.erase(handlers_it); + } + else + ++handlers_it; + } +} + } // namespace mp::Daemon::Daemon(std::unique_ptr the_config) @@ -3106,26 +3130,7 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, mp::VirtualMachine* vm) { auto& mount_specs = vm_specs.mounts; - - // Erase any outdated mount handlers - auto handlers_it = vm_mounts.begin(); - while (handlers_it != vm_mounts.end()) - { - const auto& [target, handler] = *handlers_it; - if (auto specs_it = mount_specs.find(target); - specs_it == mount_specs.end() || handler->get_mount_spec() != specs_it->second) - { - if (handler->is_mount_managed_by_backend()) - { - assert(handler->is_active()); - handler->deactivate(); - } - - handlers_it = vm_mounts.erase(handlers_it); - } - else - ++handlers_it; - } + prune_obsolete_mounts(mount_specs, vm_mounts); // Add handlers for any new mounts auto specs_it = mount_specs.begin(); From 7613115466d3bbf86fac8f18bff87be1bc941c28 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 18:31:36 +0100 Subject: [PATCH 317/627] [daemon] Return whether to persist updated mounts --- src/daemon/daemon.cpp | 8 +++++--- src/daemon/daemon.h | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 84ae49248ed..9eccff131b7 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2561,7 +2561,7 @@ try auto mounts_it = mounts.find(instance_name); assert(mounts_it != mounts.end() && "uninitialized mounts"); - update_mounts(vm_specs, mounts_it->second, vm_ptr); + update_mounts(vm_specs, mounts_it->second, vm_ptr); // ignore return, we're going to persist anyway persist_instances(); server->Write(reply); @@ -3125,7 +3125,7 @@ void mp::Daemon::stop_mounts(const std::string& name) } } -void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, +bool mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, std::unordered_map& vm_mounts, mp::VirtualMachine* vm) { @@ -3133,6 +3133,7 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, prune_obsolete_mounts(mount_specs, vm_mounts); // Add handlers for any new mounts + auto dirty = false; auto specs_it = mount_specs.begin(); while (specs_it != mount_specs.end()) { @@ -3150,13 +3151,14 @@ void mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, vm->vm_name, e.what())); specs_it = mount_specs.erase(specs_it); // unordered_map so only iterators to erased element invalidated + dirty = true; continue; } } ++specs_it; } - // TODO@ricab what do we do about persisting? + return dirty; } mp::MountHandler::UPtr mp::Daemon::make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount) diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 4b69aa3ef86..b67ba510430 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -20,6 +20,7 @@ #include "daemon_config.h" #include "daemon_rpc.h" +#include "multipass/virtual_machine.h" #include "vm_specs.h" #include @@ -158,7 +159,7 @@ public slots: grpc::Status get_ssh_info_for_vm(VirtualMachine& vm, SSHInfoReply& response); void init_mounts(const std::string& name); void stop_mounts(const std::string& name); - void update_mounts(VMSpecs& vm_specs, std::unordered_map& vm_mounts, + bool update_mounts(VMSpecs& vm_specs, std::unordered_map& vm_mounts, VirtualMachine* vm); MountHandler::UPtr make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount); From 27991fb52fc8377bc79ea8b667e63b1794e09498 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 18:32:20 +0100 Subject: [PATCH 318/627] [daemon] Extract creation of missing mounts --- src/daemon/daemon.cpp | 7 ++++++- src/daemon/daemon.h | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9eccff131b7..3c8a837fb18 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3131,8 +3131,13 @@ bool mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, { auto& mount_specs = vm_specs.mounts; prune_obsolete_mounts(mount_specs, vm_mounts); + return create_missing_mounts(mount_specs, vm_mounts, vm); +} - // Add handlers for any new mounts +bool mp::Daemon::create_missing_mounts(std::unordered_map& mount_specs, + std::unordered_map& vm_mounts, + mp::VirtualMachine* vm) +{ auto dirty = false; auto specs_it = mount_specs.begin(); while (specs_it != mount_specs.end()) diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index b67ba510430..a19e4deae07 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -157,10 +157,13 @@ public slots: grpc::Status shutdown_vm(VirtualMachine& vm, const std::chrono::milliseconds delay); grpc::Status cancel_vm_shutdown(const VirtualMachine& vm); grpc::Status get_ssh_info_for_vm(VirtualMachine& vm, SSHInfoReply& response); + void init_mounts(const std::string& name); void stop_mounts(const std::string& name); bool update_mounts(VMSpecs& vm_specs, std::unordered_map& vm_mounts, VirtualMachine* vm); + bool create_missing_mounts(std::unordered_map& mount_specs, + std::unordered_map& vm_mounts, VirtualMachine* vm); MountHandler::UPtr make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount); struct AsyncOperationStatus From 01fb85383b48dedcb4eff8fe603b10bb6842eda7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 18:26:37 +0100 Subject: [PATCH 319/627] [daemon] Use create_missing_mounts in init_mounts --- src/daemon/daemon.cpp | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3c8a837fb18..e8e1c876820 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3090,27 +3090,8 @@ void mp::Daemon::init_mounts(const std::string& name) // TODO@ricab this is now { auto& vm_mounts = mounts[name]; auto& vm_spec_mounts = vm_instance_specs[name].mounts; - std::vector mounts_to_remove; - for (const auto& [target, vm_mount] : vm_spec_mounts) - { - if (vm_mounts.find(target) == vm_mounts.end()) - try - { - vm_mounts[target] = make_mount(operative_instances[name].get(), target, vm_mount); - } - catch (const std::exception& e) - { - mpl::log(mpl::Level::warning, category, - fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", vm_mount.source_path, target, name, - e.what())); - mounts_to_remove.push_back(target); - } - } - - for (const auto& mount_target : mounts_to_remove) - vm_spec_mounts.erase(mount_target); - if (!mounts_to_remove.empty()) + if (create_missing_mounts(vm_spec_mounts, vm_mounts, operative_instances[name].get())) persist_instances(); } From 7c4bb97790a69aefd03583b9f50de82cb85380b1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 22:12:30 +0100 Subject: [PATCH 320/627] [daemon] Invert meaning of return Return true when able to create all missing mounts, false if any missing mount failed to create and a mount spec was therefore removed. This should be more intuitive for the reader. --- src/daemon/daemon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e8e1c876820..47fe55de307 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3091,7 +3091,7 @@ void mp::Daemon::init_mounts(const std::string& name) // TODO@ricab this is now auto& vm_mounts = mounts[name]; auto& vm_spec_mounts = vm_instance_specs[name].mounts; - if (create_missing_mounts(vm_spec_mounts, vm_mounts, operative_instances[name].get())) + if (!create_missing_mounts(vm_spec_mounts, vm_mounts, operative_instances[name].get())) persist_instances(); } @@ -3119,7 +3119,7 @@ bool mp::Daemon::create_missing_mounts(std::unordered_map& std::unordered_map& vm_mounts, mp::VirtualMachine* vm) { - auto dirty = false; + auto success = true; auto specs_it = mount_specs.begin(); while (specs_it != mount_specs.end()) { @@ -3137,14 +3137,14 @@ bool mp::Daemon::create_missing_mounts(std::unordered_map& vm->vm_name, e.what())); specs_it = mount_specs.erase(specs_it); // unordered_map so only iterators to erased element invalidated - dirty = true; + success = false; continue; } } ++specs_it; } - return dirty; + return success; } mp::MountHandler::UPtr mp::Daemon::make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount) From e82e8d9c4bac27d7b2f24f491060d1cb334e3aa7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 18:56:01 +0100 Subject: [PATCH 321/627] [daemon] Return result when updating mounts Return whether any mount specs were updated, when updating mounts. --- src/daemon/daemon.cpp | 9 ++++++--- src/daemon/daemon.h | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 47fe55de307..c58d9bc1360 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1215,9 +1215,10 @@ mp::SettingsHandler* register_instance_mod(std::unordered_map& mount_specs, +bool prune_obsolete_mounts(const std::unordered_map& mount_specs, std::unordered_map& vm_mounts) { + auto removed = false; auto handlers_it = vm_mounts.begin(); while (handlers_it != vm_mounts.end()) { @@ -1232,10 +1233,13 @@ void prune_obsolete_mounts(const std::unordered_map& m } handlers_it = vm_mounts.erase(handlers_it); + removed = true; } else ++handlers_it; } + + return removed; } } // namespace @@ -3111,8 +3115,7 @@ bool mp::Daemon::update_mounts(mp::VMSpecs& vm_specs, mp::VirtualMachine* vm) { auto& mount_specs = vm_specs.mounts; - prune_obsolete_mounts(mount_specs, vm_mounts); - return create_missing_mounts(mount_specs, vm_mounts, vm); + return prune_obsolete_mounts(mount_specs, vm_mounts) || !create_missing_mounts(mount_specs, vm_mounts, vm); } bool mp::Daemon::create_missing_mounts(std::unordered_map& mount_specs, diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index a19e4deae07..9f54ae484c2 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -160,10 +160,15 @@ public slots: void init_mounts(const std::string& name); void stop_mounts(const std::string& name); + + // This returns whether any specs were updated (and need persisting) bool update_mounts(VMSpecs& vm_specs, std::unordered_map& vm_mounts, VirtualMachine* vm); + + // This returns whether all required mount handlers were successfully created bool create_missing_mounts(std::unordered_map& mount_specs, std::unordered_map& vm_mounts, VirtualMachine* vm); + MountHandler::UPtr make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount); struct AsyncOperationStatus From 1286fef28a4235139be462634108b9eff59820d5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 19:22:25 +0100 Subject: [PATCH 322/627] [daemon] Avoid unnecessarily persisting instances When restoring a snapshot, persist instance specs only if they were modified. --- src/daemon/daemon.cpp | 7 +++++-- src/daemon/vm_specs.h | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index c58d9bc1360..707eaffd34a 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2547,6 +2547,7 @@ try assert(spec_it != vm_instance_specs.end() && "missing instance specs"); auto& vm_specs = spec_it->second; + // Auto snapshot const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { @@ -2559,14 +2560,16 @@ try /* sticky = */ true); } + // Actually restore snapshot reply_msg(server, "Restoring snapshot"); + auto old_specs = vm_specs; vm_ptr->restore_snapshot(vm_dir, request->snapshot(), vm_specs); auto mounts_it = mounts.find(instance_name); assert(mounts_it != mounts.end() && "uninitialized mounts"); - update_mounts(vm_specs, mounts_it->second, vm_ptr); // ignore return, we're going to persist anyway - persist_instances(); + if (update_mounts(vm_specs, mounts_it->second, vm_ptr) || vm_specs != old_specs) + persist_instances(); server->Write(reply); } diff --git a/src/daemon/vm_specs.h b/src/daemon/vm_specs.h index 0fc46851785..af638990a36 100644 --- a/src/daemon/vm_specs.h +++ b/src/daemon/vm_specs.h @@ -53,6 +53,12 @@ inline bool operator==(const VMSpecs& a, const VMSpecs& b) 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); } + +inline bool operator!=(const VMSpecs& a, const VMSpecs& b) // TODO drop in C++20 +{ + return !(a == b); +} + } // namespace multipass #endif // MULTIPASS_VM_SPECS_H From ccb4ea18cb6129c47dd930c14dd9574705752a09 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 3 Jul 2023 22:38:35 +0100 Subject: [PATCH 323/627] [daemon] Avoid flag in loop iteration To simplify the body of the loop. --- src/daemon/daemon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 707eaffd34a..998fc2afd03 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3125,9 +3125,9 @@ bool mp::Daemon::create_missing_mounts(std::unordered_map& std::unordered_map& vm_mounts, mp::VirtualMachine* vm) { - auto success = true; + auto initial_mount_count = mount_specs.size(); auto specs_it = mount_specs.begin(); - while (specs_it != mount_specs.end()) + while (specs_it != mount_specs.end()) // TODO@C++20 replace with erase_if over mount_specs { const auto& [target, mount_spec] = *specs_it; if (vm_mounts.find(target) == vm_mounts.end()) @@ -3143,14 +3143,14 @@ bool mp::Daemon::create_missing_mounts(std::unordered_map& vm->vm_name, e.what())); specs_it = mount_specs.erase(specs_it); // unordered_map so only iterators to erased element invalidated - success = false; continue; } } ++specs_it; } - return success; + assert(mount_specs.size() <= initial_mount_count); + return mount_specs.size() != initial_mount_count; } mp::MountHandler::UPtr mp::Daemon::make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount) From e25dc58ebd72cfdf69e2f530999396cd521bc93c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 11 Jul 2023 19:31:23 +0100 Subject: [PATCH 324/627] [qemu] Look for whitespace-terminated snapshot tag When looking for existing snapshot names, look for whitespace-terminated names only, to avoid matching on prefixes. --- .../backends/shared/qemu_img_utils/qemu_img_utils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 2bc6b9dce0f..e3222126976 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -100,5 +101,6 @@ bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, const process_state.failure_message(), process->read_all_standard_error())); } - return process->read_all_standard_output().contains(snapshot_tag); + QRegularExpression regex{QString{R"(%1\s)"}.arg(snapshot_tag)}; + return QString{process->read_all_standard_output()}.contains(regex); } From a8c0ca1711df6a239d3b0b40ae9d26928bfd2488 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 11 Jul 2023 19:13:58 +0100 Subject: [PATCH 325/627] [qemu] Avoid intermediate strings Avoid intermediate strings when looking for existing snapshot names. --- src/platform/backends/qemu/qemu_snapshot.cpp | 2 +- .../backends/shared/qemu_img_utils/qemu_img_utils.cpp | 4 ++-- src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index aaca4329dba..e97af0a6970 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -84,7 +84,7 @@ void mp::QemuSnapshot::capture_impl() // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) - if (backend::instance_image_has_snapshot(image_path, tag.toUtf8())) + if (backend::instance_image_has_snapshot(image_path, tag)) throw std::runtime_error{fmt::format( "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index e3222126976..8de556c9a04 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -89,7 +89,7 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) } } -bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, const char* snapshot_tag) +bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, QString snapshot_tag) { auto process = mp::platform::make_process( std::make_unique(QStringList{"snapshot", "-l", image_path}, image_path)); @@ -101,6 +101,6 @@ bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, const process_state.failure_message(), process->read_all_standard_error())); } - QRegularExpression regex{QString{R"(%1\s)"}.arg(snapshot_tag)}; + QRegularExpression regex{snapshot_tag.append(R"(\s)")}; return QString{process->read_all_standard_output()}.contains(regex); } diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index 4dfff18dad9..fc5c42bf839 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -28,7 +28,7 @@ namespace backend { void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); -bool instance_image_has_snapshot(const Path& image_path, const char* snapshot_tag); +bool instance_image_has_snapshot(const Path& image_path, QString snapshot_tag); } // namespace backend } // namespace multipass From 1f1001e359c134c6661951417a61203725c74a58 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 12 Jun 2023 21:19:39 -0700 Subject: [PATCH 326/627] [rpc] add fields for prompting user --- src/rpc/multipass.proto | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index dd3ef68172a..eaff29c967e 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -513,4 +513,6 @@ message RestoreRequest { message RestoreReply { string log_line = 1; string reply_message = 2; + string instance = 3; + bool confirm_destruction = 4; } From f4f5e9bd25d0dd5a5f3e4542b2a71719d22eebe3 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 12 Jun 2023 22:22:47 -0700 Subject: [PATCH 327/627] [cli] prompt user from streaming callback --- src/client/cli/cmd/restore.cpp | 56 +++++++++++++++++++--------------- src/client/cli/cmd/restore.h | 2 +- src/daemon/daemon.cpp | 23 +++++++++++--- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 5f2da5b70b8..629857fe12c 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -53,8 +53,35 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) return standard_failure_handler_for(name(), cerr, status); }; - return dispatch(&RpcMethod::restore, request, on_success, on_failure, - make_reply_spinner_callback(spinner, cerr)); + auto streaming_callback = [this, + &spinner](mp::RestoreReply& reply, + grpc::ClientReaderWriterInterface* client) { + if (!reply.log_line().empty()) + spinner.print(cerr, reply.log_line()); + + if (const auto& msg = reply.reply_message(); !msg.empty()) + { + spinner.stop(); + spinner.start(msg); + } + + if (reply.confirm_destruction()) + { + spinner.stop(); + RestoreRequest request; + + if (term->is_live()) + request.set_destructive(confirm_destruction(reply.instance())); + else + throw std::runtime_error("Unable to query client for confirmation. Use '--destructive' to " + "automatically discard current machine state"); + + client->Write(request); + spinner.start(); + } + }; + + return dispatch(&RpcMethod::restore, request, on_success, on_failure, streaming_callback); } std::string cmd::Restore::name() const @@ -104,40 +131,19 @@ mp::ParseCode cmd::Restore::parse_args(mp::ArgParser* parser) if (tokens.size() != 2 || tokens[0].isEmpty() || tokens[1].isEmpty()) { cerr << "Invalid format. Please specify the instance to restore and snapshot to use in the form " - "..\n"; + "..\n"; return ParseCode::CommandLineError; } request.set_instance(tokens[0].toStdString()); request.set_snapshot(tokens[1].toStdString()); request.set_destructive(parser->isSet(destructive)); - - if (!parser->isSet(destructive)) - { - if (term->is_live()) - { - try - { - request.set_destructive(confirm_destruction(tokens[0])); - } - catch (const mp::PromptException& e) - { - std::cerr << e.what() << std::endl; - return ParseCode::CommandLineError; - } - } - else - { - return ParseCode::CommandFail; - } - } - request.set_verbosity_level(parser->verbosityLevel()); return ParseCode::Ok; } -bool cmd::Restore::confirm_destruction(const QString& instance_name) +bool cmd::Restore::confirm_destruction(const std::string& instance_name) { static constexpr auto prompt_text = "Do you want to take a snapshot of {} before discarding its current state? (Yes/no)"; diff --git a/src/client/cli/cmd/restore.h b/src/client/cli/cmd/restore.h index 6d7b6bbc152..a2ab3b763ae 100644 --- a/src/client/cli/cmd/restore.h +++ b/src/client/cli/cmd/restore.h @@ -34,7 +34,7 @@ class Restore : public Command private: ParseCode parse_args(ArgParser* parser); - bool confirm_destruction(const QString& instance_name); + bool confirm_destruction(const std::string& instance_name); RestoreRequest request; }; } // namespace multipass::cmd diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 998fc2afd03..33593d71e11 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2551,13 +2551,26 @@ try const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { - reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); + RestoreReply reply{}; + reply.set_instance(instance_name); + reply.set_confirm_destruction(true); + if (!server->Write(reply)) + throw std::runtime_error("Cannot request confirmation from client. Aborting..."); - const auto snapshot = - vm_ptr->take_snapshot(vm_dir, vm_specs, "", fmt::format("Before restoring {}", request->snapshot())); + RestoreRequest client_response; + if (!server->Read(&client_response)) + throw std::runtime_error("Cannot get confirmation from client. Aborting..."); - reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), - /* sticky = */ true); + if (!client_response.destructive()) + { + reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); + + const auto snapshot = vm_ptr->take_snapshot(vm_dir, vm_specs, "", + fmt::format("Before restoring {}", request->snapshot())); + + reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), + /* sticky = */ true); + } } // Actually restore snapshot From cd5a733cbd0493bbe1129ac527e2eb6e246b619d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 12 Jul 2023 12:52:32 +0100 Subject: [PATCH 328/627] [tests] Adapt to whitespace-ending snapshot tags Fix failing tests by adapting to the QEMU backend now expecting the snapshot tag column to end in whitespace. --- tests/qemu/test_qemu_backend.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 01e7ea4b4dd..db6508d67c5 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -49,8 +49,11 @@ namespace mpt = multipass::test; using namespace testing; namespace -{ // copied from QemuVirtualMachine implementation +{ +// copied from QemuVirtualMachine implementation constexpr auto suspend_tag = "suspend"; +// we need a whitespace to terminate the tag column in the fake output of qemu-img +const QByteArray fake_snapshot_list_with_suspend_tag = QByteArray{suspend_tag} + " "; } // namespace struct QemuBackend : public mpt::TestWithMockedBinPath @@ -88,7 +91,7 @@ struct QemuBackend : public mpt::TestWithMockedBinPath mp::ProcessState exit_state; exit_state.exit_code = 0; ON_CALL(*process, execute(_)).WillByDefault(Return(exit_state)); - ON_CALL(*process, read_all_standard_output()).WillByDefault(Return(suspend_tag)); + ON_CALL(*process, read_all_standard_output()).WillByDefault(Return(fake_snapshot_list_with_suspend_tag)); } else if (process->program() == "iptables") { @@ -475,8 +478,6 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image_uses_metad TEST_F(QemuBackend, verify_qemu_arguments_from_metadata_are_used) { - constexpr auto suspend_tag = "suspend"; - EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { return std::move(mock_qemu_platform); }); @@ -487,7 +488,7 @@ TEST_F(QemuBackend, verify_qemu_arguments_from_metadata_are_used) mp::ProcessState exit_state; exit_state.exit_code = 0; EXPECT_CALL(*process, execute(_)).WillOnce(Return(exit_state)); - EXPECT_CALL(*process, read_all_standard_output()).WillOnce(Return(suspend_tag)); + EXPECT_CALL(*process, read_all_standard_output()).WillOnce(Return(fake_snapshot_list_with_suspend_tag)); } }; From c7d31a0e96ec651d30bb0d5b22851c958cb50465 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 12 Jun 2023 22:23:06 -0700 Subject: [PATCH 329/627] [tests] update cli tests --- tests/test_cli_client.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index cde384a79a5..60a1d2f1cec 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -3283,6 +3283,10 @@ TEST_F(RestoreCommandClient, restoreCmdConfirmsDesruction) server->Read(&request); EXPECT_FALSE(request.destructive()); + + mp::RestoreReply reply; + reply.set_confirm_destruction(true); + server->Write(reply); return grpc::Status{}; }); @@ -3294,7 +3298,14 @@ TEST_F(RestoreCommandClient, restoreCmdNotDestructiveNotLiveTermFails) { EXPECT_CALL(mock_terminal, cin_is_live()).WillOnce(Return(false)); - EXPECT_EQ(setup_client_and_run({"restore", "foo.snapshot1"}, mock_terminal), mp::ReturnCode::CommandFail); + EXPECT_CALL(mock_daemon, restore(_, _)).WillOnce([](auto, auto* server) { + mp::RestoreReply reply; + reply.set_confirm_destruction(true); + server->Write(reply); + return grpc::Status{}; + }); + + EXPECT_THROW(setup_client_and_run({"restore", "foo.snapshot1"}, mock_terminal), std::runtime_error); } // authenticate cli tests From aac961ff5c001a37b48a425e26bed104955c34e9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 12 Jun 2023 22:25:12 -0700 Subject: [PATCH 330/627] [daemon] fail on nonexistent snapshot before restoring --- src/daemon/daemon.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 33593d71e11..3a597b2fb83 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2547,7 +2547,17 @@ try assert(spec_it != vm_instance_specs.end() && "missing instance specs"); auto& vm_specs = spec_it->second; - // Auto snapshot + // TODO@snapshots need a way to query the existence of a snapshot before consuming it + try + { + auto snapshot = vm_ptr->get_snapshot(request->snapshot()); + } + catch (const std::out_of_range&) + { + return status_promise->set_value( + grpc::Status{grpc::NOT_FOUND, fmt::format("snapshot \"{}\" does not exist", request->snapshot())}); + } + const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { From 1b51ded1e5f4412dc06f8e6180fdabc98d5bc77e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 10 Jul 2023 22:55:16 -0700 Subject: [PATCH 331/627] [grpc] rename message parameter to match boolean flag --- src/client/cli/cmd/restore.cpp | 2 +- src/daemon/daemon.cpp | 1 + src/rpc/multipass.proto | 2 +- tests/test_cli_client.cpp | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 629857fe12c..be0c25a01a9 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -65,7 +65,7 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) spinner.start(msg); } - if (reply.confirm_destruction()) + if (reply.confirm_destructive()) { spinner.stop(); RestoreRequest request; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3a597b2fb83..ea072e1895c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2564,6 +2564,7 @@ try RestoreReply reply{}; reply.set_instance(instance_name); reply.set_confirm_destruction(true); + confirm_action.set_confirm_destructive(true); if (!server->Write(reply)) throw std::runtime_error("Cannot request confirmation from client. Aborting..."); diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index eaff29c967e..e5cdec5b4a3 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -514,5 +514,5 @@ message RestoreReply { string log_line = 1; string reply_message = 2; string instance = 3; - bool confirm_destruction = 4; + bool confirm_destructive = 3; } diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 60a1d2f1cec..3423a14302d 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -3285,7 +3285,7 @@ TEST_F(RestoreCommandClient, restoreCmdConfirmsDesruction) EXPECT_FALSE(request.destructive()); mp::RestoreReply reply; - reply.set_confirm_destruction(true); + reply.set_confirm_destructive(true); server->Write(reply); return grpc::Status{}; }); @@ -3300,7 +3300,7 @@ TEST_F(RestoreCommandClient, restoreCmdNotDestructiveNotLiveTermFails) EXPECT_CALL(mock_daemon, restore(_, _)).WillOnce([](auto, auto* server) { mp::RestoreReply reply; - reply.set_confirm_destruction(true); + reply.set_confirm_destructive(true); server->Write(reply); return grpc::Status{}; }); From 8ed29b8c1c6414cacd5b2fcd8c49c473ee359dc9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 10 Jul 2023 23:00:20 -0700 Subject: [PATCH 332/627] [daemon] missing snapshot exception is now caught higher up rendering previous code useless --- src/daemon/daemon.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index ea072e1895c..78a9b00e4cd 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2547,16 +2547,8 @@ try assert(spec_it != vm_instance_specs.end() && "missing instance specs"); auto& vm_specs = spec_it->second; - // TODO@snapshots need a way to query the existence of a snapshot before consuming it - try - { - auto snapshot = vm_ptr->get_snapshot(request->snapshot()); - } - catch (const std::out_of_range&) - { - return status_promise->set_value( - grpc::Status{grpc::NOT_FOUND, fmt::format("snapshot \"{}\" does not exist", request->snapshot())}); - } + // Only need to check if the snapshot exists so the result is discarded + vm_ptr->get_snapshot(request->snapshot()); const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) @@ -2600,6 +2592,10 @@ try status_promise->set_value(status); } +catch (const mp::NoSuchSnapshot& e) +{ + status_promise->set_value(grpc::Status{grpc::StatusCode::NOT_FOUND, e.what(), ""}); +} catch (const std::exception& e) { status_promise->set_value(grpc::Status(grpc::StatusCode::INTERNAL, e.what(), "")); From b5c72aa16e0fec4a21b5151e0efe4b142bab780e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 10 Jul 2023 23:39:27 -0700 Subject: [PATCH 333/627] [cli] fix variable shadowing --- src/client/cli/cmd/restore.cpp | 6 +++--- src/daemon/daemon.cpp | 6 ++---- src/rpc/multipass.proto | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index be0c25a01a9..fabc343b223 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -68,15 +68,15 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) if (reply.confirm_destructive()) { spinner.stop(); - RestoreRequest request; + RestoreRequest client_response; if (term->is_live()) - request.set_destructive(confirm_destruction(reply.instance())); + client_response.set_destructive(confirm_destruction(request.instance())); else throw std::runtime_error("Unable to query client for confirmation. Use '--destructive' to " "automatically discard current machine state"); - client->Write(request); + client->Write(client_response); spinner.start(); } }; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 78a9b00e4cd..2e24b91004c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2553,11 +2553,9 @@ try const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { - RestoreReply reply{}; - reply.set_instance(instance_name); - reply.set_confirm_destruction(true); + RestoreReply confirm_action{}; confirm_action.set_confirm_destructive(true); - if (!server->Write(reply)) + if (!server->Write(confirm_action)) throw std::runtime_error("Cannot request confirmation from client. Aborting..."); RestoreRequest client_response; diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index e5cdec5b4a3..4de76bf73fb 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -513,6 +513,5 @@ message RestoreRequest { message RestoreReply { string log_line = 1; string reply_message = 2; - string instance = 3; bool confirm_destructive = 3; } From b7816342e0cee6e419c38efef2ce9bcad15ef8b2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 19 Jul 2023 12:34:16 +0100 Subject: [PATCH 334/627] [qemu] Use Path where appropriate in snapshots --- src/platform/backends/qemu/qemu_snapshot.cpp | 6 +++--- src/platform/backends/qemu/qemu_snapshot.h | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index e97af0a6970..916b0c64a64 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -36,19 +36,19 @@ namespace const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char that can't be part of the name, to avoid confusion */ -std::unique_ptr make_capture_spec(const QString& tag, const QString& image_path) +std::unique_ptr make_capture_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, /* src_img = */ "", image_path); } -std::unique_ptr make_restore_spec(const QString& tag, const QString& image_path) +std::unique_ptr make_restore_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-a", tag, image_path}, /* src_img = */ "", image_path); } -std::unique_ptr make_delete_spec(const QString& tag, const QString& image_path) +std::unique_ptr make_delete_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-d", tag, image_path}, /* src_img = */ "", image_path); diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 49adf86f3a7..2424690193b 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -19,8 +19,11 @@ #define MULTIPASS_QEMU_SNAPSHOT_H #include "qemu_virtual_machine.h" + #include +#include + namespace multipass { class QemuVirtualMachine; @@ -43,7 +46,7 @@ class QemuSnapshot : public BaseSnapshot private: VirtualMachineDescription& desc; - const QString& image_path; + const Path& image_path; }; } // namespace multipass From 98f59fe8971d264e6906db949336bc976c12d3b1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 14 Jul 2023 18:51:22 +0100 Subject: [PATCH 335/627] [daemon] Fail info and delete w/ bad snapshot args Fail info and delete requests when at least one of the arguments refers to a snapshot that does not exist. --- src/daemon/daemon.cpp | 62 ++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 2e24b91004c..ce44e78d7aa 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1151,8 +1151,13 @@ bool is_ipv4_valid(const std::string& ipv4) return true; } +struct SnapshotPick +{ + std::unordered_set pick; + bool all; +}; using InstanceSnapshotPairs = google::protobuf::RepeatedPtrField; -using InstanceSnapshotsMap = std::unordered_map>; +using InstanceSnapshotsMap = std::unordered_map; InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& instances_snapshots) { InstanceSnapshotsMap instance_snapshots_map; @@ -1161,12 +1166,12 @@ InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& ins { const auto& instance = it.instance_name(); const auto& snapshot = it.snapshot_name(); + auto& snapshot_pick = instance_snapshots_map[instance]; if (snapshot.empty()) - instance_snapshots_map[instance].clear(); - else if (const auto& entry = instance_snapshots_map.find(instance); - entry == instance_snapshots_map.end() || !entry->second.empty()) - instance_snapshots_map[instance].insert(snapshot); + snapshot_pick.all = true; + else + snapshot_pick.pick.insert(snapshot); } return instance_snapshots_map; @@ -1743,25 +1748,26 @@ try // clang-format on timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); }; - if (const auto& it = instance_snapshots_map.find(name); - it == instance_snapshots_map.end() || it->second.empty()) - { - for (const auto& snapshot : vm.view_snapshots()) - get_snapshot_info(snapshot); - } - else + const auto& it = instance_snapshots_map.find(name); + const auto& [pick, all] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + + try { - for (const auto& snapshot : it->second) + if (all) { - try - { - get_snapshot_info(vm.get_snapshot(snapshot)); - } - catch (const NoSuchSnapshot& e) - { - add_fmt_to(errors, e.what()); - } + for (const auto& snapshot : pick) + vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately + + for (const auto& snapshot : vm.view_snapshots()) + get_snapshot_info(snapshot); } + else + for (const auto& snapshot : pick) + get_snapshot_info(vm.get_snapshot(snapshot)); + } + catch (const NoSuchSnapshot& e) + { + add_fmt_to(errors, e.what()); } return grpc_status_for(errors); @@ -2251,19 +2257,21 @@ try // clang-format on { const auto& instance_name = vm_it->first; - auto contained_in_snapshots_map = instance_snapshots_map.count(instance_name); - assert(contained_in_snapshots_map || !request->instances_snapshots_size()); - - if (!contained_in_snapshots_map || - instance_snapshots_map[instance_name].empty()) // we're asked to delete the VM + auto snapshot_pick_it = instance_snapshots_map.find(instance_name); + const auto& [pick, all] = snapshot_pick_it == instance_snapshots_map.end() ? SnapshotPick{{}, true} + : snapshot_pick_it->second; + if (all) // we're asked to delete the VM { + for (const auto& snapshot : pick) // TODO@ricab extract + vm_it->second->get_snapshot( + snapshot); // verify validity of any snapshot name requested separately instances_dirty |= delete_vm(vm_it, purge, response); } else // we're asked to delete snapshots { assert(purge && "precondition: snapshots can only be purged"); - for (const auto& snapshot_name : instance_snapshots_map[instance_name]) + for (const auto& snapshot_name : pick) vm_it->second->delete_snapshot(instance_directory(instance_name, *config), snapshot_name); } } From c1c84a5b6637898d0d267ae6b5d03e24864fd203 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 19 Jul 2023 12:47:08 +0100 Subject: [PATCH 336/627] [shared] Use Path where appropriate in the base VM --- .../backends/shared/base_virtual_machine.cpp | 29 +++++++++---------- .../backends/shared/base_virtual_machine.h | 17 ++++++----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e11d44def62..f6d526cfee8 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -54,7 +54,7 @@ void assert_vm_stopped(St state) assert(state == St::off || state == St::stopped); } -QString derive_head_path(const QDir& snapshot_dir) +mp::Path derive_head_path(const QDir& snapshot_dir) { return snapshot_dir.filePath(head_filename); } @@ -84,7 +84,7 @@ QFileInfo find_snapshot_file(const QDir& snapshot_dir, const std::string& snapsh } void update_parents_rollback_helper(const std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) + std::unordered_map& updated_snapshot_paths) { for (auto [snapshot, snapshot_filepath] : updated_snapshot_paths) { @@ -232,7 +232,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn } } -bool BaseVirtualMachine::updated_deleted_head(std::shared_ptr& snapshot, const QString& head_path) +bool BaseVirtualMachine::updated_deleted_head(std::shared_ptr& snapshot, const Path& head_path) { if (head_snapshot == snapshot) { @@ -244,14 +244,14 @@ bool BaseVirtualMachine::updated_deleted_head(std::shared_ptr& snapsho return false; } -auto BaseVirtualMachine::make_deleted_head_rollback(const QString& head_path, const bool& wrote_head) +auto BaseVirtualMachine::make_deleted_head_rollback(const Path& head_path, const bool& wrote_head) { return sg::make_scope_guard([this, old_head = head_snapshot, &head_path, &wrote_head]() mutable noexcept { deleted_head_rollback_helper(head_path, wrote_head, old_head); }); } -void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, +void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, const bool& wrote_head, std::shared_ptr& old_head) { if (head_snapshot != old_head) @@ -264,9 +264,8 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const QString& head_path, } } -auto BaseVirtualMachine::make_parent_update_rollback( - const std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) const +auto BaseVirtualMachine::make_parent_update_rollback(const std::shared_ptr& deleted_parent, + std::unordered_map& updated_snapshot_paths) const { return sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); @@ -313,7 +312,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s } void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) + std::unordered_map& updated_snapshot_paths) { auto new_parent = deleted_parent->get_parent(); for (auto& [ignore, other] : snapshots) @@ -423,7 +422,7 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) } } -auto BaseVirtualMachine::make_head_file_rollback(const QString& head_path, QFile& head_file) const +auto BaseVirtualMachine::make_head_file_rollback(const Path& head_path, QFile& head_file) const { return sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), existed = head_file.exists()]() noexcept { @@ -431,8 +430,8 @@ auto BaseVirtualMachine::make_head_file_rollback(const QString& head_path, QFile }); } -void BaseVirtualMachine::head_file_rollback_helper(const QString& head_path, QFile& head_file, - const std::string& old_head, bool existed) const +void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, QFile& head_file, const std::string& old_head, + bool existed) const { // best effort, ignore returns if (!existed) @@ -471,7 +470,7 @@ void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const head_file_rollback.dismiss(); } -void BaseVirtualMachine::persist_head_snapshot_name(const QString& head_path) const +void BaseVirtualMachine::persist_head_snapshot_name(const Path& head_path) const { auto head_name = head_snapshot ? head_snapshot->get_name() : ""; MP_UTILS.make_file_with_content(head_path.toStdString(), head_name, yes_overwrite); @@ -482,7 +481,7 @@ std::string BaseVirtualMachine::generate_snapshot_name() const return fmt::format("snapshot{}", snapshot_count + 1); } -auto BaseVirtualMachine::make_restore_rollback(const QString& head_path, VMSpecs& specs) +auto BaseVirtualMachine::make_restore_rollback(const Path& head_path, VMSpecs& specs) { return sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_helper, this, head_path, old_head, old_specs, @@ -490,7 +489,7 @@ auto BaseVirtualMachine::make_restore_rollback(const QString& head_path, VMSpecs }); } -void BaseVirtualMachine::restore_rollback_helper(const QString& head_path, const std::shared_ptr& old_head, +void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, const std::shared_ptr& old_head, const VMSpecs& old_specs, VMSpecs& specs) { // best effort only diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 98bb6e6c1cb..543fa692335 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -88,21 +89,21 @@ class BaseVirtualMachine : public VirtualMachine auto make_take_snapshot_rollback(SnapshotMap::iterator it); void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count); - auto make_head_file_rollback(const QString& head_path, QFile& head_file) const; - void head_file_rollback_helper(const QString& head_path, QFile& head_file, const std::string& old_head, + auto make_head_file_rollback(const Path& head_path, QFile& head_file) const; + void head_file_rollback_helper(const Path& head_path, QFile& head_file, const std::string& old_head, bool existed) const; void persist_head_snapshot(const QDir& snapshot_dir) const; - void persist_head_snapshot_name(const QString& head_path) const; + void persist_head_snapshot_name(const Path& head_path) const; std::string generate_snapshot_name() const; - auto make_restore_rollback(const QString& head_path, VMSpecs& specs); - void restore_rollback_helper(const QString& head_path, const std::shared_ptr& old_head, + auto make_restore_rollback(const Path& head_path, VMSpecs& specs); + void restore_rollback_helper(const Path& head_path, const std::shared_ptr& old_head, const VMSpecs& old_specs, VMSpecs& specs); - bool updated_deleted_head(std::shared_ptr& snapshot, const QString& head_path); - auto make_deleted_head_rollback(const QString& head_path, const bool& wrote_head); - void deleted_head_rollback_helper(const QString& head_path, const bool& wrote_head, + bool updated_deleted_head(std::shared_ptr& snapshot, const Path& head_path); + auto make_deleted_head_rollback(const Path& head_path, const bool& wrote_head); + void deleted_head_rollback_helper(const Path& head_path, const bool& wrote_head, std::shared_ptr& old_head); void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, From 8a9f6970a372c6c83d6c44543e1a9ae582f426a6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 14 Jul 2023 19:13:49 +0100 Subject: [PATCH 337/627] [daemon] Verify all snapshot args before deleting --- src/daemon/daemon.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index ce44e78d7aa..0140c98f273 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1177,6 +1177,16 @@ InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& ins return instance_snapshots_map; } +void verify_snapshot_picks(const InstanceSelectionReport& report, + const std::unordered_map& snapshot_picks) +{ + for (const auto& selection : {report.deleted_selection, report.operative_selection}) + for (const auto& vm_it : selection) + if (auto pick_it = snapshot_picks.find(vm_it->first); pick_it != snapshot_picks.end()) + for (const auto& snapshot_name : pick_it->second.pick) + vm_it->second->get_snapshot(snapshot_name); // throws if it doesn't exist +} + void add_aliases(google::protobuf::RepeatedPtrField* container, const std::string& remote_name, const mp::VMImageInfo& info, const std::string& default_remote) { @@ -2248,7 +2258,9 @@ try // clang-format on { const bool purge = request->purge(); auto instances_dirty = false; + auto instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); + verify_snapshot_picks(instance_selection, instance_snapshots_map); // avoid deleting if any snapshot is missing // start with deleted instances, to avoid iterator invalidation when moving instances there for (const auto& selection : {instance_selection.deleted_selection, instance_selection.operative_selection}) @@ -2261,12 +2273,7 @@ try // clang-format on const auto& [pick, all] = snapshot_pick_it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : snapshot_pick_it->second; if (all) // we're asked to delete the VM - { - for (const auto& snapshot : pick) // TODO@ricab extract - vm_it->second->get_snapshot( - snapshot); // verify validity of any snapshot name requested separately instances_dirty |= delete_vm(vm_it, purge, response); - } else // we're asked to delete snapshots { assert(purge && "precondition: snapshots can only be purged"); From b13f65358ae329c93ad261743abc98264d625926 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 14 Jul 2023 19:23:21 +0100 Subject: [PATCH 338/627] [daemon] Remove obsolete TODO --- src/daemon/daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 0140c98f273..a6bf368331a 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3126,7 +3126,7 @@ grpc::Status mp::Daemon::get_ssh_info_for_vm(VirtualMachine& vm, SSHInfoReply& r return grpc::Status::OK; } -void mp::Daemon::init_mounts(const std::string& name) // TODO@ricab this is now a particular case of update_mounts +void mp::Daemon::init_mounts(const std::string& name) { auto& vm_mounts = mounts[name]; auto& vm_spec_mounts = vm_instance_specs[name].mounts; From 05625bae357198071679aab8caee1dd555c86800 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 21 Jun 2023 10:10:48 -0700 Subject: [PATCH 339/627] [utils] add custom sort func for instances and snapshot details --- include/multipass/cli/format_utils.h | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index bf74f4b923c..1570fce6d9a 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -46,6 +46,9 @@ Instances sorted(const Instances& instances); template Snapshots sort_snapshots(const Snapshots& snapshots); +template +Details sort_instances_and_snapshots(const Details& details); + void filter_aliases(google::protobuf::RepeatedPtrField& aliases); // Computes the column width needed to display all the elements of a range [begin, end). get_width is a function @@ -109,6 +112,36 @@ Snapshots multipass::format::sort_snapshots(const Snapshots& snapshots) return ret; } +template +Details multipass::format::sort_instances_and_snapshots(const Details& details) +{ + using google::protobuf::util::TimeUtil; + if (details.empty()) + return details; + + auto ret = details; + const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString(); + std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) { + if (a.has_instance_info() && b.has_snapshot_info()) + return true; + else if (a.has_snapshot_info() && b.has_instance_info()) + return false; + + if (a.name() == petenv_name && b.name() != petenv_name) + return true; + else if (a.name() != petenv_name && b.name() == petenv_name) + return false; + + if (a.has_instance_info()) + return a.name() < b.name(); + else + return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp()); + }); + + return ret; +} + namespace fmt { template <> From 6604aa285d13e9cc87ca1c612d21c2c4c06578f5 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 21 Jun 2023 10:24:03 -0700 Subject: [PATCH 340/627] [formatter] print snapshot info with table formatter --- src/client/cli/formatter/table_formatter.cpp | 201 ++++++++++++------- 1 file changed, 130 insertions(+), 71 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index db57e5254d4..3d45ae54ae4 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -53,91 +53,150 @@ std::string to_usage(const std::string& usage, const std::string& total) return fmt::format("{} out of {}", mp::MemorySize{usage}.human_readable(), mp::MemorySize{total}.human_readable()); } -std::string generate_instance_info_report(const mp::InfoReply& reply) +std::string generate_snapshot_details(const mp::DetailedInfoItem& item) { fmt::memory_buffer buf; + const auto& fundamentals = item.snapshot_info().fundamentals(); + + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshot:", fundamentals.snapshot_name()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Instance:", item.name()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Size:", item.snapshot_info().size().empty() ? "--" : item.snapshot_info().size()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "CPU(s):", item.cpu_count()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Disk space:", item.disk_total()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Memory size:", item.memory_total()); + + auto mount_paths = item.mount_info().mount_paths(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); + for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) + { + if (mount != mount_paths.cbegin()) + fmt::format_to(std::back_inserter(buf), "{:<16}", ""); + fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), + item.mount_info().longest_path_len(), mount->target_path()); + } - for (const auto& info : mp::format::sorted(reply.detailed_report().details())) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Created:", google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Parent:", fundamentals.parent().empty() ? "--" : fundamentals.parent()); + + auto children = item.snapshot_info().children(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Children:", children.empty() ? "--\n" : ""); + for (auto child = children.cbegin(); child != children.cend(); ++child) { - const auto& instance_details = info.instance_info(); + if (child != children.cbegin()) + fmt::format_to(std::back_inserter(buf), "{:<16}", ""); + fmt::format_to(std::back_inserter(buf), "{}\n", *child); + } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", info.name()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "State:", mp::format::status_string_for(info.instance_status())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Comment:", fundamentals.comment().empty() ? "--" : fundamentals.comment()); - int ipv4_size = instance_details.ipv4_size(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); + return fmt::to_string(buf); +} - for (int i = 1; i < ipv4_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); +std::string generate_instance_details(const mp::DetailedInfoItem& item) +{ + fmt::memory_buffer buf; + const auto& instance_details = item.instance_info(); + + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", item.name()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "State:", mp::format::status_string_for(item.instance_status())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); + + int ipv4_size = instance_details.ipv4_size(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv4:", ipv4_size ? instance_details.ipv4(0) : "--"); + + for (int i = 1; i < ipv4_size; ++i) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv4(i)); + + if (int ipv6_size = instance_details.ipv6_size()) + { + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); + + for (int i = 1; i < ipv6_size; ++i) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); + } - if (int ipv6_size = instance_details.ipv6_size()) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Release:", instance_details.current_release().empty() ? "--" : instance_details.current_release()); + fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); + if (instance_details.id().empty()) + fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); + else + fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), + !instance_details.image_release().empty() + ? fmt::format(" (Ubuntu {})", instance_details.image_release()) + : ""); + + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "CPU(s):", item.cpu_count().empty() ? "--" : item.cpu_count()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Load:", instance_details.load().empty() ? "--" : instance_details.load()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Disk usage:", to_usage(instance_details.disk_usage(), item.disk_total())); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", + "Memory usage:", to_usage(instance_details.memory_usage(), item.memory_total())); + + auto mount_paths = item.mount_info().mount_paths(); + fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); + + for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) + { + if (mount != mount_paths.cbegin()) + fmt::format_to(std::back_inserter(buf), "{:<16}", ""); + fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), + item.mount_info().longest_path_len(), mount->target_path()); + + auto mount_maps = mount->mount_maps(); + auto uid_mappings_size = mount_maps.uid_mappings_size(); + + for (auto i = 0; i < uid_mappings_size; ++i) { - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "IPv6:", instance_details.ipv6(0)); + auto uid_map_pair = mount_maps.uid_mappings(i); + auto host_uid = uid_map_pair.host_id(); + auto instance_uid = uid_map_pair.instance_id(); + + fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", (i == 0) ? 29 : 0, + std::to_string(host_uid), + (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), + (i == uid_mappings_size - 1) ? "\n" : ", "); + } - for (int i = 1; i < ipv6_size; ++i) - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); + for (auto gid_mapping = mount_maps.gid_mappings().cbegin(); gid_mapping != mount_maps.gid_mappings().cend(); + ++gid_mapping) + { + auto host_gid = gid_mapping->host_id(); + auto instance_gid = gid_mapping->instance_id(); + + fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, std::to_string(host_gid), + (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), + (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", + (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); } + } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Release:", - instance_details.current_release().empty() ? "--" : instance_details.current_release()); - fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); - if (instance_details.id().empty()) - fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); + return fmt::to_string(buf); +} + +std::string generate_instance_info_report(const mp::InfoReply& reply) +{ + fmt::memory_buffer buf; + + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) + { + if (info.has_instance_info()) + { + fmt::format_to(std::back_inserter(buf), generate_instance_details(info)); + } else - fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), - !instance_details.image_release().empty() - ? fmt::format(" (Ubuntu {})", instance_details.image_release()) - : ""); - - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "CPU(s):", info.cpu_count().empty() ? "--" : info.cpu_count()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Load:", instance_details.load().empty() ? "--" : instance_details.load()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Disk usage:", to_usage(instance_details.disk_usage(), info.disk_total())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Memory usage:", to_usage(instance_details.memory_usage(), info.memory_total())); - - auto mount_paths = info.mount_info().mount_paths(); - fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); - - for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) { - if (mount != mount_paths.cbegin()) - fmt::format_to(std::back_inserter(buf), "{:<16}", ""); - fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), - info.mount_info().longest_path_len(), mount->target_path()); - - auto mount_maps = mount->mount_maps(); - auto uid_mappings_size = mount_maps.uid_mappings_size(); - - for (auto i = 0; i < uid_mappings_size; ++i) - { - auto uid_map_pair = mount_maps.uid_mappings(i); - auto host_uid = uid_map_pair.host_id(); - auto instance_uid = uid_map_pair.instance_id(); - - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", (i == 0) ? 29 : 0, - std::to_string(host_uid), - (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), - (i == uid_mappings_size - 1) ? "\n" : ", "); - } - - for (auto gid_mapping = mount_maps.gid_mappings().cbegin(); gid_mapping != mount_maps.gid_mappings().cend(); - ++gid_mapping) - { - auto host_gid = gid_mapping->host_id(); - auto instance_gid = gid_mapping->instance_id(); - - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, std::to_string(host_gid), - (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), - (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", - (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); - } + assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); + fmt::format_to(std::back_inserter(buf), generate_snapshot_details(info)); } fmt::format_to(std::back_inserter(buf), "\n"); From fcb75f6518cb5a596f7b42aee688a43c26bac5ff Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 21 Jun 2023 10:25:00 -0700 Subject: [PATCH 341/627] [tests] test snapshot details table output and sorting --- tests/test_output_formatter.cpp | 354 +++++++++++++++++++++++++++++++- 1 file changed, 344 insertions(+), 10 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 34c68612cb4..318ae750798 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -260,6 +260,202 @@ auto construct_multiple_instances_info_reply() return info_reply; } +auto construct_single_snapshot_info_reply() +{ + mp::InfoReply info_reply; + + auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("bogus-instance"); + info_entry->set_cpu_count("2"); + info_entry->set_disk_total("4.9GiB"); + info_entry->set_memory_total("0.9GiB"); + fundamentals->set_snapshot_name("snapshot2"); + fundamentals->set_parent("snapshot1"); + info_entry->mutable_snapshot_info()->add_children("snapshot3"); + info_entry->mutable_snapshot_info()->add_children("snapshot4"); + + auto mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user/source"); + mount_entry->set_target_path("source"); + mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user"); + mount_entry->set_target_path("Home"); + + google::protobuf::Timestamp timestamp; + timestamp.set_seconds(63108020); + timestamp.set_nanos(21000000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return info_reply; +} + +auto construct_multiple_snapshots_info_reply() +{ + mp::InfoReply info_reply; + + auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("messier-87"); + info_entry->set_cpu_count("1"); + info_entry->set_disk_total("1024GiB"); + info_entry->set_memory_total("128GiB"); + fundamentals->set_snapshot_name("black-hole"); + fundamentals->set_comment("Captured by EHT"); + + google::protobuf::Timestamp timestamp; + timestamp.set_seconds(1554897599); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + info_entry = info_reply.mutable_detailed_report()->add_details(); + fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("bogus-instance"); + info_entry->set_cpu_count("2"); + info_entry->set_disk_total("4.9GiB"); + info_entry->set_memory_total("0.9GiB"); + fundamentals->set_snapshot_name("snapshot2"); + fundamentals->set_parent("snapshot1"); + info_entry->mutable_snapshot_info()->add_children("snapshot3"); + info_entry->mutable_snapshot_info()->add_children("snapshot4"); + + auto mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user/source"); + mount_entry->set_target_path("source"); + mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user"); + mount_entry->set_target_path("Home"); + + timestamp.set_seconds(63108020); + timestamp.set_nanos(21000000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return info_reply; +} + +auto construct_mixed_instance_and_snapshot_info_reply() +{ + mp::InfoReply info_reply; + + auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("bogus-instance"); + info_entry->set_cpu_count("2"); + info_entry->set_disk_total("4.9GiB"); + info_entry->set_memory_total("0.9GiB"); + fundamentals->set_snapshot_name("snapshot2"); + fundamentals->set_parent("snapshot1"); + info_entry->mutable_snapshot_info()->add_children("snapshot3"); + info_entry->mutable_snapshot_info()->add_children("snapshot4"); + + auto mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user/source"); + mount_entry->set_target_path("source"); + mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user"); + mount_entry->set_target_path("Home"); + + google::protobuf::Timestamp timestamp; + timestamp.set_seconds(63108020); + timestamp.set_nanos(21000000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry->set_name("bombastic"); + info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); + info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); + info_entry->mutable_instance_info()->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); + info_entry->mutable_instance_info()->set_num_snapshots(3); + + return info_reply; +} + +auto construct_multiple_mixed_instances_and_snapshots_info_reply() +{ + mp::InfoReply info_reply; + + auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("bogus-instance"); + info_entry->set_cpu_count("2"); + info_entry->set_disk_total("4.9GiB"); + info_entry->set_memory_total("0.9GiB"); + fundamentals->set_snapshot_name("snapshot2"); + fundamentals->set_parent("snapshot1"); + info_entry->mutable_snapshot_info()->add_children("snapshot3"); + info_entry->mutable_snapshot_info()->add_children("snapshot4"); + + auto mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user/source"); + mount_entry->set_target_path("source"); + mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry->set_source_path("/home/user"); + mount_entry->set_target_path("Home"); + + google::protobuf::Timestamp timestamp; + timestamp.set_seconds(63108020); + timestamp.set_nanos(21000000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry->set_name("bombastic"); + info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); + info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); + info_entry->mutable_instance_info()->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); + info_entry->mutable_instance_info()->set_num_snapshots(3); + + info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry->set_name("bogus-instance"); + info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); + info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); + info_entry->mutable_instance_info()->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); + + auto mount_info = info_entry->mutable_mount_info(); + mount_info->set_longest_path_len(17); + + mount_entry = mount_info->add_mount_paths(); + mount_entry->set_source_path("/home/user/source"); + mount_entry->set_target_path("source"); + + auto uid_map_pair = mount_entry->mutable_mount_maps()->add_uid_mappings(); + uid_map_pair->set_host_id(1000); + uid_map_pair->set_instance_id(501); + + auto gid_map_pair = mount_entry->mutable_mount_maps()->add_gid_mappings(); + gid_map_pair->set_host_id(1000); + gid_map_pair->set_instance_id(501); + + info_entry->set_cpu_count("4"); + info_entry->mutable_instance_info()->set_load("0.03 0.10 0.15"); + info_entry->mutable_instance_info()->set_memory_usage("38797312"); + info_entry->set_memory_total("1610612736"); + info_entry->mutable_instance_info()->set_disk_usage("1932735284"); + info_entry->set_disk_total("6764573492"); + info_entry->mutable_instance_info()->set_current_release("Ubuntu 16.04.3 LTS"); + info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); + info_entry->mutable_instance_info()->set_num_snapshots(1); + + info_entry = info_reply.mutable_detailed_report()->add_details(); + fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("messier-87"); + info_entry->set_cpu_count("1"); + info_entry->set_disk_total("1024GiB"); + info_entry->set_memory_total("128GiB"); + fundamentals->set_snapshot_name("black-hole"); + fundamentals->set_comment("Captured by EHT"); + + timestamp.set_seconds(1554897599); + timestamp.set_nanos(0); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return info_reply; +} + auto construct_single_snapshot_overview_info_reply() { mp::InfoReply info_reply; @@ -339,6 +535,10 @@ auto add_petenv_to_reply(mp::InfoReply& reply) entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); entry->mutable_instance_info()->set_image_release("18.10"); entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); + + entry = reply.mutable_detailed_report()->add_details(); + entry->set_name(petenv_name()); + entry->mutable_snapshot_info()->mutable_fundamentals()->set_snapshot_name("snapshot1"); } else { @@ -581,6 +781,11 @@ const auto empty_info_reply = construct_empty_info_reply(); const auto empty_snapshot_overview_reply = construct_empty_snapshot_overview_reply(); const auto single_instance_info_reply = construct_single_instance_info_reply(); const auto multiple_instances_info_reply = construct_multiple_instances_info_reply(); +const auto single_snapshot_info_reply = construct_single_snapshot_info_reply(); +const auto multiple_snapshots_info_reply = construct_multiple_snapshots_info_reply(); +const auto mixed_instance_and_snapshot_info_reply = construct_mixed_instance_and_snapshot_info_reply(); +const auto multiple_mixed_instances_and_snapshots_info_reply = + construct_multiple_mixed_instances_and_snapshots_info_reply(); const auto single_snapshot_overview_info_reply = construct_single_snapshot_overview_info_reply(); const auto multiple_snapshot_overview_info_reply = construct_multiple_snapshot_overview_info_reply(); @@ -628,7 +833,7 @@ const std::vector orderable_list_info_formatter_outputs{ " /home/user/test_dir => test_dir\n" " UID map: 1000:1000\n" " GID map: 1000:1000\n", - "table_info_single"}, + "table_info_single_instance"}, {&table_formatter, &multiple_instances_info_reply, "Name: bogus-instance\n" "State: Running\n" @@ -654,7 +859,124 @@ const std::vector orderable_list_info_formatter_outputs{ "Disk usage: --\n" "Memory usage: --\n" "Mounts: --\n", - "table_info_multiple"}, + "table_info_multiple_instances"}, + {&table_formatter, &single_snapshot_info_reply, + "Snapshot: snapshot2\n" + "Instance: bogus-instance\n" + "Size: --\n" + "CPU(s): 2\n" + "Disk space: 4.9GiB\n" + "Memory size: 0.9GiB\n" + "Mounts: /home/user/source => source\n" + " /home/user => Home\n" + "Created: 1972-01-01T10:00:20.021Z\n" + "Parent: snapshot1\n" + "Children: snapshot3\n" + " snapshot4\n" + "Comment: --\n", + "table_info_single_snapshot"}, + {&table_formatter, &multiple_snapshots_info_reply, + "Snapshot: snapshot2\n" + "Instance: bogus-instance\n" + "Size: --\n" + "CPU(s): 2\n" + "Disk space: 4.9GiB\n" + "Memory size: 0.9GiB\n" + "Mounts: /home/user/source => source\n" + " /home/user => Home\n" + "Created: 1972-01-01T10:00:20.021Z\n" + "Parent: snapshot1\n" + "Children: snapshot3\n" + " snapshot4\n" + "Comment: --\n\n" + "Snapshot: black-hole\n" + "Instance: messier-87\n" + "Size: --\n" + "CPU(s): 1\n" + "Disk space: 1024GiB\n" + "Memory size: 128GiB\n" + "Mounts: --\n" + "Created: 2019-04-10T11:59:59Z\n" + "Parent: --\n" + "Children: --\n" + "Comment: Captured by EHT\n", + "table_info_multiple_snapshots"}, + {&table_formatter, &mixed_instance_and_snapshot_info_reply, + "Name: bombastic\n" + "State: Stopped\n" + "Snapshots: 3\n" + "IPv4: --\n" + "Release: --\n" + "Image hash: ab5191cc1725 (Ubuntu 18.04 LTS)\n" + "CPU(s): --\n" + "Load: --\n" + "Disk usage: --\n" + "Memory usage: --\n" + "Mounts: --\n\n" + "Snapshot: snapshot2\n" + "Instance: bogus-instance\n" + "Size: --\n" + "CPU(s): 2\n" + "Disk space: 4.9GiB\n" + "Memory size: 0.9GiB\n" + "Mounts: /home/user/source => source\n" + " /home/user => Home\n" + "Created: 1972-01-01T10:00:20.021Z\n" + "Parent: snapshot1\n" + "Children: snapshot3\n" + " snapshot4\n" + "Comment: --\n", + "table_info_mixed_instance_and_snapshot"}, + {&table_formatter, &multiple_mixed_instances_and_snapshots_info_reply, + "Name: bogus-instance\n" + "State: Running\n" + "Snapshots: 1\n" + "IPv4: 10.21.124.56\n" + "Release: Ubuntu 16.04.3 LTS\n" + "Image hash: 1797c5c82016 (Ubuntu 16.04 LTS)\n" + "CPU(s): 4\n" + "Load: 0.03 0.10 0.15\n" + "Disk usage: 1.8GiB out of 6.3GiB\n" + "Memory usage: 37.0MiB out of 1.5GiB\n" + "Mounts: /home/user/source => source\n" + " UID map: 1000:501\n" + " GID map: 1000:501\n\n" + "Name: bombastic\n" + "State: Stopped\n" + "Snapshots: 3\n" + "IPv4: --\n" + "Release: --\n" + "Image hash: ab5191cc1725 (Ubuntu 18.04 LTS)\n" + "CPU(s): --\n" + "Load: --\n" + "Disk usage: --\n" + "Memory usage: --\n" + "Mounts: --\n\n" + "Snapshot: snapshot2\n" + "Instance: bogus-instance\n" + "Size: --\n" + "CPU(s): 2\n" + "Disk space: 4.9GiB\n" + "Memory size: 0.9GiB\n" + "Mounts: /home/user/source => source\n" + " /home/user => Home\n" + "Created: 1972-01-01T10:00:20.021Z\n" + "Parent: snapshot1\n" + "Children: snapshot3\n" + " snapshot4\n" + "Comment: --\n\n" + "Snapshot: black-hole\n" + "Instance: messier-87\n" + "Size: --\n" + "CPU(s): 1\n" + "Disk space: 1024GiB\n" + "Memory size: 128GiB\n" + "Mounts: --\n" + "Created: 2019-04-10T11:59:59Z\n" + "Parent: --\n" + "Children: --\n" + "Comment: Captured by EHT\n", + "table_info_multiple_mixed_instances_and_snapshots"}, {&table_formatter, &single_snapshot_overview_info_reply, "Instance Snapshot Parent Comment\n" "foo snapshot1 -- This is a sample comment\n", @@ -699,7 +1021,7 @@ const std::vector orderable_list_info_formatter_outputs{ "LTS,1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac,16.04 LTS,0.45 0.51 " "0.15,1288490188,5153960756,60817408,1503238554,/home/user/foo => foo;/home/user/test_dir " "=> test_dir;,\"10.168.32.2,200.3.123.29\";,1,0\n", - "csv_info_single"}, + "csv_info_single_instance"}, {&csv_formatter, &multiple_instances_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nbogus-instance,Running,10.21.124.56,,Ubuntu 16.04.3 " @@ -707,7 +1029,7 @@ const std::vector orderable_list_info_formatter_outputs{ "0.15,1932735284,6764573492,38797312,1610612736,/home/user/source => " "source;,\"10.21.124.56\";,4,1\nbombastic,Stopped,,,," "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,\"\";,,3\n", - "csv_info_multiple"}, + "csv_info_multiple_instances"}, {&csv_formatter, &single_snapshot_overview_info_reply, "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,This is a sample comment\n", "csv_snapshot_overview_single"}, {&csv_formatter, &multiple_snapshot_overview_info_reply, @@ -800,7 +1122,7 @@ const std::vector orderable_list_info_formatter_outputs{ " gid_mappings:\n" " - \"1000:1000\"\n" " source_path: /home/user/test_dir\n", - "yaml_info_single"}, + "yaml_info_single_instance"}, {&yaml_formatter, &multiple_instances_info_reply, "errors:\n" " - ~\n" @@ -848,7 +1170,7 @@ const std::vector orderable_list_info_formatter_outputs{ " ipv4:\n" " []\n" " mounts: ~\n", - "yaml_info_multiple"}, + "yaml_info_multiple_instances"}, {&yaml_formatter, &single_snapshot_overview_info_reply, "errors:\n" " - ~\n" @@ -992,7 +1314,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " }\n" "}\n", - "json_info_single"}, + "json_info_single_instance"}, {&json_formatter, &multiple_instances_info_reply, "{\n" " \"errors\": [\n" @@ -1057,7 +1379,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " }\n" "}\n", - "json_info_multiple"}, + "json_info_multiple_instances"}, {&json_formatter, &single_snapshot_overview_info_reply, "{\n" " \"errors\": [\n" @@ -1626,9 +1948,21 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) output = formatter->format(reply_copy); if (dynamic_cast(formatter)) - regex = fmt::format("(Name:[[:space:]]+{0}.+|Instance[[:print:]]*\n{0}.+)", petenv_name()); + { + if (input->has_detailed_report()) + regex = fmt::format("(Name:[[:space:]]+{0}.+)" + "(Snapshot:[[:print:]]*\nInstance:[[:space:]]+{0}.+)", + petenv_name()); + else + regex = fmt::format("Instance[[:print:]]*\n{}.+", petenv_name()); + } else if (dynamic_cast(formatter)) - regex = fmt::format("(Name[[:print:]]*\n{0},.*|Instance[[:print:]]*\n{0},.*)", petenv_name()); + { + if (input->has_detailed_report()) + regex = fmt::format("Name[[:print:]]*\n{},.*", petenv_name()); + else + regex = fmt::format("Instance[[:print:]]*\n{},.*", petenv_name()); + } else if (dynamic_cast(formatter)) regex = fmt::format("(errors:[[:space:]]+-[[:space:]]+~[[:space:]]+)?{}:.*", petenv_name()); else From bf4665f15f2cf04c080b7c34284efd71897ee9d3 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 24 Jul 2023 12:31:03 -0700 Subject: [PATCH 342/627] [utils] sort snapshots first by instance name --- include/multipass/cli/format_utils.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 1570fce6d9a..345bca2e3ad 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -135,8 +135,13 @@ Details multipass::format::sort_instances_and_snapshots(const Details& details) if (a.has_instance_info()) return a.name() < b.name(); else - return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) < - TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp()); + { + if (a.name() == b.name()) + return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp()); + + return a.name() < b.name(); + } }); return ret; From 3f636b3c63ed6cd024dd3b32ae20c5c8fd088094 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 24 Jul 2023 12:32:12 -0700 Subject: [PATCH 343/627] [utils] TODO@snapshots DRY remove repeated sorting logic --- include/multipass/cli/format_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 345bca2e3ad..00f83da5535 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -85,6 +85,7 @@ Instances multipass::format::sorted(const Instances& instances) return ret; } +// TODO@snapshots DRY template Snapshots multipass::format::sort_snapshots(const Snapshots& snapshots) { From 3c51a426d36f8b8f8694aa5c12965afa6d77abd5 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 24 Jul 2023 12:44:14 -0700 Subject: [PATCH 344/627] [formatter] only display snapshot size when populated --- src/client/cli/formatter/table_formatter.cpp | 6 ++++-- tests/test_output_formatter.cpp | 8 ++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 3d45ae54ae4..17bd7f6b553 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -60,8 +60,10 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshot:", fundamentals.snapshot_name()); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Instance:", item.name()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Size:", item.snapshot_info().size().empty() ? "--" : item.snapshot_info().size()); + + if (!item.snapshot_info().size().empty()) + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Size:", item.snapshot_info().size()); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "CPU(s):", item.cpu_count()); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Disk space:", item.disk_total()); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Memory size:", item.memory_total()); diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 318ae750798..d42629207c4 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -273,6 +273,7 @@ auto construct_single_snapshot_info_reply() info_entry->set_memory_total("0.9GiB"); fundamentals->set_snapshot_name("snapshot2"); fundamentals->set_parent("snapshot1"); + info_entry->mutable_snapshot_info()->set_size("128MiB"); info_entry->mutable_snapshot_info()->add_children("snapshot3"); info_entry->mutable_snapshot_info()->add_children("snapshot4"); @@ -863,7 +864,7 @@ const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &single_snapshot_info_reply, "Snapshot: snapshot2\n" "Instance: bogus-instance\n" - "Size: --\n" + "Size: 128MiB\n" "CPU(s): 2\n" "Disk space: 4.9GiB\n" "Memory size: 0.9GiB\n" @@ -878,7 +879,6 @@ const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &multiple_snapshots_info_reply, "Snapshot: snapshot2\n" "Instance: bogus-instance\n" - "Size: --\n" "CPU(s): 2\n" "Disk space: 4.9GiB\n" "Memory size: 0.9GiB\n" @@ -891,7 +891,6 @@ const std::vector orderable_list_info_formatter_outputs{ "Comment: --\n\n" "Snapshot: black-hole\n" "Instance: messier-87\n" - "Size: --\n" "CPU(s): 1\n" "Disk space: 1024GiB\n" "Memory size: 128GiB\n" @@ -915,7 +914,6 @@ const std::vector orderable_list_info_formatter_outputs{ "Mounts: --\n\n" "Snapshot: snapshot2\n" "Instance: bogus-instance\n" - "Size: --\n" "CPU(s): 2\n" "Disk space: 4.9GiB\n" "Memory size: 0.9GiB\n" @@ -954,7 +952,6 @@ const std::vector orderable_list_info_formatter_outputs{ "Mounts: --\n\n" "Snapshot: snapshot2\n" "Instance: bogus-instance\n" - "Size: --\n" "CPU(s): 2\n" "Disk space: 4.9GiB\n" "Memory size: 0.9GiB\n" @@ -967,7 +964,6 @@ const std::vector orderable_list_info_formatter_outputs{ "Comment: --\n\n" "Snapshot: black-hole\n" "Instance: messier-87\n" - "Size: --\n" "CPU(s): 1\n" "Disk space: 1024GiB\n" "Memory size: 128GiB\n" From e03bfe22302327e7adb84d28036cce34316414bf Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 24 Jul 2023 15:05:14 -0700 Subject: [PATCH 345/627] [utils] TODO@snapshots format info comment using terminal width --- src/client/cli/formatter/table_formatter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 17bd7f6b553..cdcdcdc3879 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -92,6 +92,7 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) fmt::format_to(std::back_inserter(buf), "{}\n", *child); } + // TODO@snapshots split and align string if it extends onto several lines fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Comment:", fundamentals.comment().empty() ? "--" : fundamentals.comment()); From 91f055ab7169d9bff5dab71d9252f5b7ebed913a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 25 Jul 2023 08:41:38 -0700 Subject: [PATCH 346/627] [formatter] align newlines in comment field to match existing alignment --- src/client/cli/formatter/table_formatter.cpp | 9 +++++++-- tests/test_output_formatter.cpp | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index cdcdcdc3879..bfceca58a06 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -22,6 +22,8 @@ #include #include +#include + namespace mp = multipass; namespace @@ -93,8 +95,11 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) } // TODO@snapshots split and align string if it extends onto several lines - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Comment:", fundamentals.comment().empty() ? "--" : fundamentals.comment()); + std::regex newline("(\r\n|\n)"); + fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Comment:", + fundamentals.comment().empty() + ? "--" + : std::regex_replace(fundamentals.comment(), newline, "$&" + std::string(16, ' '))); return fmt::to_string(buf); } diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index d42629207c4..6359865db3a 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -273,6 +273,7 @@ auto construct_single_snapshot_info_reply() info_entry->set_memory_total("0.9GiB"); fundamentals->set_snapshot_name("snapshot2"); fundamentals->set_parent("snapshot1"); + fundamentals->set_comment("This is a comment with some\nnew\r\nlines."); info_entry->mutable_snapshot_info()->set_size("128MiB"); info_entry->mutable_snapshot_info()->add_children("snapshot3"); info_entry->mutable_snapshot_info()->add_children("snapshot4"); @@ -874,7 +875,9 @@ const std::vector orderable_list_info_formatter_outputs{ "Parent: snapshot1\n" "Children: snapshot3\n" " snapshot4\n" - "Comment: --\n", + "Comment: This is a comment with some\n" + " new\r\n" + " lines.\n", "table_info_single_snapshot"}, {&table_formatter, &multiple_snapshots_info_reply, "Snapshot: snapshot2\n" From fd4f2081a073f71228691a9694489848d25d146d Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 12 Jul 2023 21:32:39 -0700 Subject: [PATCH 347/627] [daemon] populate snapshot details --- src/daemon/daemon.cpp | 173 ++++++++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 56 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a6bf368331a..acdf5fd23bc 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1628,59 +1628,33 @@ try // clang-format on mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, server}; InfoReply response; + InstanceSnapshotsMap instance_snapshots_map; // Need to 'touch' a report in the response so formatters know what to do with an otherwise empty response request->snapshot_overview() ? (void)response.mutable_snapshot_overview() : (void)response.mutable_detailed_report(); bool have_mounts = false; bool deleted = false; - auto fetch_instance_info = [&](VirtualMachine& vm) { - const auto& name = vm.vm_name; - auto info = response.mutable_detailed_report()->add_details(); - auto instance_info = info->mutable_instance_info(); - auto present_state = vm.current_state(); - info->set_name(name); - if (deleted) - { - info->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); - } - else - { - info->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); - } - - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); - auto original_release = vm_image.original_release; - if (!vm_image.id.empty() && original_release.empty()) - { - try - { - auto vm_image_info = config->image_hosts.back()->info_for_full_hash(vm_image.id); - original_release = vm_image_info.release_title.toStdString(); - } - catch (const std::exception& e) - { - mpl::log(mpl::Level::warning, category, fmt::format("Cannot fetch image information: {}", e.what())); - } - } + auto populate_snapshot_fundamentals = [&](std::shared_ptr snapshot, auto& fundamentals) { + fundamentals->set_snapshot_name(snapshot->get_name()); + fundamentals->set_parent(snapshot->get_parent_name()); + fundamentals->set_comment(snapshot->get_comment()); - instance_info->set_num_snapshots(vm.get_num_snapshots()); - instance_info->set_image_release(original_release); - instance_info->set_id(vm_image.id); - - auto vm_specs = vm_instance_specs[name]; - - auto mount_info = info->mutable_mount_info(); + auto timestamp = fundamentals->mutable_creation_timestamp(); + timestamp->set_seconds(snapshot->get_creation_timestamp().toSecsSinceEpoch()); + timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); + }; + auto populate_mount_info = [&](const auto& mounts, auto& mount_info) { mount_info->set_longest_path_len(0); - if (!vm_specs.mounts.empty()) + if (!mounts.empty()) have_mounts = true; if (MP_SETTINGS.get_as(mp::mounts_key)) { - for (const auto& mount : vm_specs.mounts) + for (const auto& mount : mounts) { if (mount.second.source_path.size() > mount_info->longest_path_len()) { @@ -1705,6 +1679,43 @@ try // clang-format on } } } + }; + + auto populate_instance_info = [&](VirtualMachine& vm) { + const auto& name = vm.vm_name; + auto info = response.mutable_detailed_report()->add_details(); + auto instance_info = info->mutable_instance_info(); + auto present_state = vm.current_state(); + info->set_name(name); + if (deleted) + info->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); + else + info->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); + + auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto original_release = vm_image.original_release; + + if (!vm_image.id.empty() && original_release.empty()) + { + try + { + auto vm_image_info = config->image_hosts.back()->info_for_full_hash(vm_image.id); + original_release = vm_image_info.release_title.toStdString(); + } + catch (const std::exception& e) + { + mpl::log(mpl::Level::warning, category, fmt::format("Cannot fetch image information: {}", e.what())); + } + } + + instance_info->set_num_snapshots(vm.get_num_snapshots()); + instance_info->set_image_release(original_release); + instance_info->set_id(vm_image.id); + + auto vm_specs = vm_instance_specs[name]; + + auto mount_info = info->mutable_mount_info(); + populate_mount_info(vm_specs.mounts, mount_info); if (!request->no_runtime_information() && mp::utils::is_running(present_state)) { @@ -1739,24 +1750,61 @@ try // clang-format on return grpc::Status::OK; }; - InstanceSnapshotsMap instance_snapshots_map; - auto fetch_snapshot_overview = [&](VirtualMachine& vm) { + auto populate_snapshot_info = [&](std::shared_ptr snapshot, const std::string& vm_name) { + auto info = response.mutable_detailed_report()->add_details(); + auto snapshot_info = info->mutable_snapshot_info(); + auto fundamentals = snapshot_info->mutable_fundamentals(); + + info->set_name(vm_name); + info->mutable_instance_status()->set_status(grpc_instance_status_for(snapshot->get_state())); + info->set_memory_total(snapshot->get_mem_size().human_readable()); + info->set_disk_total(snapshot->get_disk_space().human_readable()); + info->set_cpu_count(std::to_string(snapshot->get_num_cores())); + + auto mount_info = info->mutable_mount_info(); + populate_mount_info(snapshot->get_mounts(), mount_info); + + // TODO@snapshots get snapshot size once available + // TODO@snapshots get snapshot children once available + + populate_snapshot_fundamentals(snapshot, fundamentals); + }; + + auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; - auto get_snapshot_info = [&](std::shared_ptr snapshot) { - auto overview = response.mutable_snapshot_overview()->add_overview(); - auto fundamentals = overview->mutable_fundamentals(); + const auto& it = instance_snapshots_map.find(name); + const auto& [pick, all] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + + try + { + if (all) + { + populate_instance_info(vm); + for (const auto& snapshot : pick) + vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately + + for (const auto& snapshot : vm.view_snapshots()) + populate_snapshot_info(snapshot, name); + } + else + { + for (const auto& snapshot : pick) + populate_snapshot_info(vm.get_snapshot(snapshot), name); + } + } + catch (const NoSuchSnapshot& e) + { + add_fmt_to(errors, e.what()); + } - overview->set_instance_name(name); - fundamentals->set_snapshot_name(snapshot->get_name()); - fundamentals->set_parent(snapshot->get_parent_name()); - fundamentals->set_comment(snapshot->get_comment()); + return grpc_status_for(errors); + }; - auto timestamp = fundamentals->mutable_creation_timestamp(); - timestamp->set_seconds(snapshot->get_creation_timestamp().toSecsSinceEpoch()); - timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); - }; + auto fetch_snapshot_overview = [&](VirtualMachine& vm) { + fmt::memory_buffer errors; + const auto& name = vm.vm_name; const auto& it = instance_snapshots_map.find(name); const auto& [pick, all] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; @@ -1769,11 +1817,25 @@ try // clang-format on vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately for (const auto& snapshot : vm.view_snapshots()) - get_snapshot_info(snapshot); + { + auto overview = response.mutable_snapshot_overview()->add_overview(); + auto fundamentals = overview->mutable_fundamentals(); + + overview->set_instance_name(name); + populate_snapshot_fundamentals(snapshot, fundamentals); + } } else + { for (const auto& snapshot : pick) - get_snapshot_info(vm.get_snapshot(snapshot)); + { + auto overview = response.mutable_snapshot_overview()->add_overview(); + auto fundamentals = overview->mutable_fundamentals(); + + overview->set_instance_name(name); + populate_snapshot_fundamentals(vm.get_snapshot(snapshot), fundamentals); + } + } } catch (const NoSuchSnapshot& e) { @@ -1791,9 +1853,8 @@ try // clang-format on { instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); - // TODO@snapshots change cmd logic to include detailed snapshot info output - auto cmd = - request->snapshot_overview() ? std::function(fetch_snapshot_overview) : std::function(fetch_instance_info); + auto cmd = request->snapshot_overview() ? std::function(fetch_snapshot_overview) + : std::function(fetch_detailed_report); if ((status = cmd_vms(instance_selection.operative_selection, cmd)).ok()) { From 26f3eed7210bb4148af2b865ab263b2ebe03e231 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 10:41:35 -0700 Subject: [PATCH 348/627] [daemon] only get instance and not snapshot details on `info ` --- src/daemon/daemon.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index acdf5fd23bc..93cd979c0ee 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1784,15 +1784,10 @@ try // clang-format on populate_instance_info(vm); for (const auto& snapshot : pick) vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately - - for (const auto& snapshot : vm.view_snapshots()) - populate_snapshot_info(snapshot, name); - } - else - { - for (const auto& snapshot : pick) - populate_snapshot_info(vm.get_snapshot(snapshot), name); } + + for (const auto& snapshot : pick) + populate_snapshot_info(vm.get_snapshot(snapshot), name); } catch (const NoSuchSnapshot& e) { From 665028d16b53de2068d4ec20120a34e282215302 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 27 Jul 2023 12:55:27 +0100 Subject: [PATCH 349/627] [vm] Fix missing braces in error msg formatting --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f6d526cfee8..b0a70e59bca 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -207,7 +207,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn assert_vm_stopped(state); // precondition if (snapshot_count > max_snapshots) - throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded", max_snapshots)}; + throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", max_snapshots)}; snapshot_name = name.empty() ? generate_snapshot_name() : name; const auto [it, success] = snapshots.try_emplace(snapshot_name, nullptr); From af8e674b762b2094c4b49c669b2a438acd49c94d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 28 Jul 2023 10:33:11 +0100 Subject: [PATCH 350/627] [qemu] Link backend against `scope_guard` Fixes a build failure on macOS. --- src/platform/backends/qemu/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/backends/qemu/CMakeLists.txt b/src/platform/backends/qemu/CMakeLists.txt index 8ba5e722ea3..868ffa540af 100644 --- a/src/platform/backends/qemu/CMakeLists.txt +++ b/src/platform/backends/qemu/CMakeLists.txt @@ -39,6 +39,7 @@ target_link_libraries(qemu_backend logger qemu_img_utils qemu_platform_detail + scope_guard utils Qt6::Core) From 25b0c1b62e8b0817b2444042d6da78ddb31e7de2 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 26 Jul 2023 10:57:03 -0700 Subject: [PATCH 351/627] [vm] add function to compute snapshot children --- include/multipass/virtual_machine.h | 1 + src/daemon/daemon.cpp | 10 ++++++---- src/platform/backends/shared/base_virtual_machine.cpp | 11 +++++++++++ src/platform/backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 5 +++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 0322a68f1f8..019a13da2bd 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -96,6 +96,7 @@ class VirtualMachine : private DisabledCopyMove virtual void delete_snapshot(const QDir& snapshot_dir, const std::string& name) = 0; virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; + virtual std::vector get_children(const std::shared_ptr parent) const = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 93cd979c0ee..a78bfc85561 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1750,12 +1750,12 @@ try // clang-format on return grpc::Status::OK; }; - auto populate_snapshot_info = [&](std::shared_ptr snapshot, const std::string& vm_name) { + auto populate_snapshot_info = [&](VirtualMachine& vm, std::shared_ptr snapshot) { auto info = response.mutable_detailed_report()->add_details(); auto snapshot_info = info->mutable_snapshot_info(); auto fundamentals = snapshot_info->mutable_fundamentals(); - info->set_name(vm_name); + info->set_name(vm.vm_name); info->mutable_instance_status()->set_status(grpc_instance_status_for(snapshot->get_state())); info->set_memory_total(snapshot->get_mem_size().human_readable()); info->set_disk_total(snapshot->get_disk_space().human_readable()); @@ -1765,7 +1765,9 @@ try // clang-format on populate_mount_info(snapshot->get_mounts(), mount_info); // TODO@snapshots get snapshot size once available - // TODO@snapshots get snapshot children once available + + for (const auto& child : vm.get_children(snapshot)) + snapshot_info->add_children(child); populate_snapshot_fundamentals(snapshot, fundamentals); }; @@ -1787,7 +1789,7 @@ try // clang-format on } for (const auto& snapshot : pick) - populate_snapshot_info(vm.get_snapshot(snapshot), name); + populate_snapshot_info(vm, vm.get_snapshot(snapshot)); } catch (const NoSuchSnapshot& e) { diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f6d526cfee8..c956c21c81f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -356,6 +356,17 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) load_generic_snapshot_info(snapshot_dir); } +std::vector BaseVirtualMachine::get_children(const std::shared_ptr parent) const +{ + std::vector children; + + for (const auto& snapshot : view_snapshots()) + if (snapshot->get_parent_name() == parent->get_name()) + children.push_back(snapshot->get_name()); + + return children; +} + void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) { try diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 543fa692335..f537c8ffcc1 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -69,6 +69,7 @@ class BaseVirtualMachine : public VirtualMachine void delete_snapshot(const QDir& snapshot_dir, const std::string& name) override; void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; void load_snapshots(const QDir& snapshot_dir) override; + std::vector get_children(const std::shared_ptr parent) const override; protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 760b6d3c164..033a5ba291c 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -75,6 +75,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, delete_snapshot, (const QDir& snapshot_dir, const std::string& name), (override)); MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); + MOCK_METHOD(std::vector, get_children, (const std::shared_ptr), (const, override)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 191ecc703f0..c5ab522d2e7 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -156,6 +156,11 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } + std::vector get_children(const std::shared_ptr) const override + { + return {}; + } + StubSnapshot snapshot; }; } // namespace test From 322b25ff4ca582f7bad0e3ee37ba5e23380c76fa Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 26 Jul 2023 13:45:56 -0700 Subject: [PATCH 352/627] [daemon] refactor lambdas into helper functions --- src/daemon/daemon.cpp | 280 +++++++++++++++++++++--------------------- src/daemon/daemon.h | 3 + 2 files changed, 145 insertions(+), 138 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a78bfc85561..4cc59c53d6c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1257,6 +1257,76 @@ bool prune_obsolete_mounts(const std::unordered_map& m return removed; } +void populate_snapshot_fundamentals(std::shared_ptr snapshot, + mp::SnapshotFundamentals* fundamentals) +{ + fundamentals->set_snapshot_name(snapshot->get_name()); + fundamentals->set_parent(snapshot->get_parent_name()); + fundamentals->set_comment(snapshot->get_comment()); + + auto timestamp = fundamentals->mutable_creation_timestamp(); + timestamp->set_seconds(snapshot->get_creation_timestamp().toSecsSinceEpoch()); + timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); +} + +void populate_mount_info(const std::unordered_map& mounts, mp::MountInfo* mount_info, + bool& have_mounts) +{ + mount_info->set_longest_path_len(0); + + if (!mounts.empty()) + have_mounts = true; + + if (MP_SETTINGS.get_as(mp::mounts_key)) + { + for (const auto& mount : mounts) + { + if (mount.second.source_path.size() > mount_info->longest_path_len()) + mount_info->set_longest_path_len(mount.second.source_path.size()); + + auto entry = mount_info->add_mount_paths(); + entry->set_source_path(mount.second.source_path); + entry->set_target_path(mount.first); + + for (const auto& uid_mapping : mount.second.uid_mappings) + { + auto uid_pair = entry->mutable_mount_maps()->add_uid_mappings(); + uid_pair->set_host_id(uid_mapping.first); + uid_pair->set_instance_id(uid_mapping.second); + } + for (const auto& gid_mapping : mount.second.gid_mappings) + { + auto gid_pair = entry->mutable_mount_maps()->add_gid_mappings(); + gid_pair->set_host_id(gid_mapping.first); + gid_pair->set_instance_id(gid_mapping.second); + } + } + } +} + +void populate_snapshot_info(mp::VirtualMachine& vm, std::shared_ptr snapshot, + mp::DetailedInfoItem* info, bool& have_mounts) +{ + auto snapshot_info = info->mutable_snapshot_info(); + auto fundamentals = snapshot_info->mutable_fundamentals(); + + info->set_name(vm.vm_name); + info->mutable_instance_status()->set_status(grpc_instance_status_for(snapshot->get_state())); + info->set_memory_total(snapshot->get_mem_size().human_readable()); + info->set_disk_total(snapshot->get_disk_space().human_readable()); + info->set_cpu_count(std::to_string(snapshot->get_num_cores())); + + auto mount_info = info->mutable_mount_info(); + populate_mount_info(snapshot->get_mounts(), mount_info, have_mounts); + + // TODO@snapshots get snapshot size once available + + for (const auto& child : vm.get_children(snapshot)) + snapshot_info->add_children(child); + + populate_snapshot_fundamentals(snapshot, fundamentals); +} + } // namespace mp::Daemon::Daemon(std::unique_ptr the_config) @@ -1636,142 +1706,6 @@ try // clang-format on bool have_mounts = false; bool deleted = false; - auto populate_snapshot_fundamentals = [&](std::shared_ptr snapshot, auto& fundamentals) { - fundamentals->set_snapshot_name(snapshot->get_name()); - fundamentals->set_parent(snapshot->get_parent_name()); - fundamentals->set_comment(snapshot->get_comment()); - - auto timestamp = fundamentals->mutable_creation_timestamp(); - timestamp->set_seconds(snapshot->get_creation_timestamp().toSecsSinceEpoch()); - timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); - }; - - auto populate_mount_info = [&](const auto& mounts, auto& mount_info) { - mount_info->set_longest_path_len(0); - - if (!mounts.empty()) - have_mounts = true; - - if (MP_SETTINGS.get_as(mp::mounts_key)) - { - for (const auto& mount : mounts) - { - if (mount.second.source_path.size() > mount_info->longest_path_len()) - { - mount_info->set_longest_path_len(mount.second.source_path.size()); - } - - auto entry = mount_info->add_mount_paths(); - entry->set_source_path(mount.second.source_path); - entry->set_target_path(mount.first); - - for (const auto& uid_mapping : mount.second.uid_mappings) - { - auto uid_pair = entry->mutable_mount_maps()->add_uid_mappings(); - uid_pair->set_host_id(uid_mapping.first); - uid_pair->set_instance_id(uid_mapping.second); - } - for (const auto& gid_mapping : mount.second.gid_mappings) - { - auto gid_pair = entry->mutable_mount_maps()->add_gid_mappings(); - gid_pair->set_host_id(gid_mapping.first); - gid_pair->set_instance_id(gid_mapping.second); - } - } - } - }; - - auto populate_instance_info = [&](VirtualMachine& vm) { - const auto& name = vm.vm_name; - auto info = response.mutable_detailed_report()->add_details(); - auto instance_info = info->mutable_instance_info(); - auto present_state = vm.current_state(); - info->set_name(name); - if (deleted) - info->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); - else - info->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); - - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); - auto original_release = vm_image.original_release; - - if (!vm_image.id.empty() && original_release.empty()) - { - try - { - auto vm_image_info = config->image_hosts.back()->info_for_full_hash(vm_image.id); - original_release = vm_image_info.release_title.toStdString(); - } - catch (const std::exception& e) - { - mpl::log(mpl::Level::warning, category, fmt::format("Cannot fetch image information: {}", e.what())); - } - } - - instance_info->set_num_snapshots(vm.get_num_snapshots()); - instance_info->set_image_release(original_release); - instance_info->set_id(vm_image.id); - - auto vm_specs = vm_instance_specs[name]; - - auto mount_info = info->mutable_mount_info(); - populate_mount_info(vm_specs.mounts, mount_info); - - if (!request->no_runtime_information() && mp::utils::is_running(present_state)) - { - mp::SSHSession session{vm.ssh_hostname(), vm.ssh_port(), vm_specs.ssh_username, *config->ssh_key_provider}; - - instance_info->set_load(mpu::run_in_ssh_session(session, "cat /proc/loadavg | cut -d ' ' -f1-3")); - instance_info->set_memory_usage( - mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $3}'")); - info->set_memory_total(mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $2}'")); - instance_info->set_disk_usage( - mpu::run_in_ssh_session(session, "df -t ext4 -t vfat --total -B1 --output=used | tail -n 1")); - info->set_disk_total( - mpu::run_in_ssh_session(session, "df -t ext4 -t vfat --total -B1 --output=size | tail -n 1")); - info->set_cpu_count(mpu::run_in_ssh_session(session, "nproc")); - - std::string management_ip = vm.management_ipv4(*config->ssh_key_provider); - auto all_ipv4 = vm.get_all_ipv4(*config->ssh_key_provider); - - if (is_ipv4_valid(management_ip)) - instance_info->add_ipv4(management_ip); - else if (all_ipv4.empty()) - instance_info->add_ipv4("N/A"); - - for (const auto& extra_ipv4 : all_ipv4) - if (extra_ipv4 != management_ip) - instance_info->add_ipv4(extra_ipv4); - - auto current_release = - mpu::run_in_ssh_session(session, "cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2"); - instance_info->set_current_release(!current_release.empty() ? current_release : original_release); - } - return grpc::Status::OK; - }; - - auto populate_snapshot_info = [&](VirtualMachine& vm, std::shared_ptr snapshot) { - auto info = response.mutable_detailed_report()->add_details(); - auto snapshot_info = info->mutable_snapshot_info(); - auto fundamentals = snapshot_info->mutable_fundamentals(); - - info->set_name(vm.vm_name); - info->mutable_instance_status()->set_status(grpc_instance_status_for(snapshot->get_state())); - info->set_memory_total(snapshot->get_mem_size().human_readable()); - info->set_disk_total(snapshot->get_disk_space().human_readable()); - info->set_cpu_count(std::to_string(snapshot->get_num_cores())); - - auto mount_info = info->mutable_mount_info(); - populate_mount_info(snapshot->get_mounts(), mount_info); - - // TODO@snapshots get snapshot size once available - - for (const auto& child : vm.get_children(snapshot)) - snapshot_info->add_children(child); - - populate_snapshot_fundamentals(snapshot, fundamentals); - }; - auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; @@ -1783,13 +1717,15 @@ try // clang-format on { if (all) { - populate_instance_info(vm); + populate_instance_info(vm, response.mutable_detailed_report()->add_details(), + request->no_runtime_information(), deleted, have_mounts); for (const auto& snapshot : pick) vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately } for (const auto& snapshot : pick) - populate_snapshot_info(vm, vm.get_snapshot(snapshot)); + populate_snapshot_info(vm, vm.get_snapshot(snapshot), response.mutable_detailed_report()->add_details(), + have_mounts); } catch (const NoSuchSnapshot& e) { @@ -3445,3 +3381,71 @@ void mp::Daemon::reply_msg(grpc::ServerReaderWriterInterface* se server->Write(reply); } + +void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem* info, bool runtime_info, bool deleted, + bool& have_mounts) +{ + const auto& name = vm.vm_name; + auto instance_info = info->mutable_instance_info(); + auto present_state = vm.current_state(); + info->set_name(name); + if (deleted) + info->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); + else + info->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); + + auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto original_release = vm_image.original_release; + + if (!vm_image.id.empty() && original_release.empty()) + { + try + { + auto vm_image_info = config->image_hosts.back()->info_for_full_hash(vm_image.id); + original_release = vm_image_info.release_title.toStdString(); + } + catch (const std::exception& e) + { + mpl::log(mpl::Level::warning, category, fmt::format("Cannot fetch image information: {}", e.what())); + } + } + + instance_info->set_num_snapshots(vm.get_num_snapshots()); + instance_info->set_image_release(original_release); + instance_info->set_id(vm_image.id); + + auto vm_specs = vm_instance_specs[name]; + + auto mount_info = info->mutable_mount_info(); + populate_mount_info(vm_specs.mounts, mount_info, have_mounts); + + if (!runtime_info && mp::utils::is_running(present_state)) + { + mp::SSHSession session{vm.ssh_hostname(), vm.ssh_port(), vm_specs.ssh_username, *config->ssh_key_provider}; + + instance_info->set_load(mpu::run_in_ssh_session(session, "cat /proc/loadavg | cut -d ' ' -f1-3")); + instance_info->set_memory_usage(mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $3}'")); + info->set_memory_total(mpu::run_in_ssh_session(session, "free -b | grep 'Mem:' | awk '{printf $2}'")); + instance_info->set_disk_usage( + mpu::run_in_ssh_session(session, "df -t ext4 -t vfat --total -B1 --output=used | tail -n 1")); + info->set_disk_total( + mpu::run_in_ssh_session(session, "df -t ext4 -t vfat --total -B1 --output=size | tail -n 1")); + info->set_cpu_count(mpu::run_in_ssh_session(session, "nproc")); + + std::string management_ip = vm.management_ipv4(*config->ssh_key_provider); + auto all_ipv4 = vm.get_all_ipv4(*config->ssh_key_provider); + + if (is_ipv4_valid(management_ip)) + instance_info->add_ipv4(management_ip); + else if (all_ipv4.empty()) + instance_info->add_ipv4("N/A"); + + for (const auto& extra_ipv4 : all_ipv4) + if (extra_ipv4 != management_ip) + instance_info->add_ipv4(extra_ipv4); + + auto current_release = + mpu::run_in_ssh_session(session, "cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2"); + instance_info->set_current_release(!current_release.empty() ? current_release : original_release); + } +} diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 9f54ae484c2..efcf43b79f2 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -195,6 +195,9 @@ public slots: template void reply_msg(grpc::ServerReaderWriterInterface* server, std::string&& msg, bool sticky = false); + void populate_instance_info(VirtualMachine& vm, DetailedInfoItem* info, bool runtime_info, bool deleted, + bool& have_mounts); + std::unique_ptr config; std::unordered_map vm_instance_specs; InstanceTable operative_instances; From b014ee39b074d411b716adaa040980e975097877 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 22 Aug 2023 13:03:21 -0700 Subject: [PATCH 353/627] resolve function and variable name semantics --- include/multipass/virtual_machine.h | 2 +- src/daemon/daemon.cpp | 24 ++++++++----------- .../backends/shared/base_virtual_machine.cpp | 4 ++-- .../backends/shared/base_virtual_machine.h | 2 +- tests/mock_virtual_machine.h | 2 +- tests/stub_virtual_machine.h | 2 +- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 019a13da2bd..c30c268ab25 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -96,7 +96,7 @@ class VirtualMachine : private DisabledCopyMove virtual void delete_snapshot(const QDir& snapshot_dir, const std::string& name) = 0; virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots(const QDir& snapshot_dir) = 0; - virtual std::vector get_children(const std::shared_ptr parent) const = 0; + virtual std::vector get_childrens_names(const Snapshot* parent) const = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 4cc59c53d6c..a9ec30a2fa8 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1154,7 +1154,7 @@ bool is_ipv4_valid(const std::string& ipv4) struct SnapshotPick { std::unordered_set pick; - bool all; + bool all_or_none; }; using InstanceSnapshotPairs = google::protobuf::RepeatedPtrField; using InstanceSnapshotsMap = std::unordered_map; @@ -1169,7 +1169,7 @@ InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& ins auto& snapshot_pick = instance_snapshots_map[instance]; if (snapshot.empty()) - snapshot_pick.all = true; + snapshot_pick.all_or_none = true; else snapshot_pick.pick.insert(snapshot); } @@ -1321,7 +1321,7 @@ void populate_snapshot_info(mp::VirtualMachine& vm, std::shared_ptradd_children(child); populate_snapshot_fundamentals(snapshot, fundamentals); @@ -1711,17 +1711,13 @@ try // clang-format on const auto& name = vm.vm_name; const auto& it = instance_snapshots_map.find(name); - const auto& [pick, all] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; try { - if (all) - { + if (all_or_none) populate_instance_info(vm, response.mutable_detailed_report()->add_details(), request->no_runtime_information(), deleted, have_mounts); - for (const auto& snapshot : pick) - vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately - } for (const auto& snapshot : pick) populate_snapshot_info(vm, vm.get_snapshot(snapshot), response.mutable_detailed_report()->add_details(), @@ -1740,11 +1736,11 @@ try // clang-format on const auto& name = vm.vm_name; const auto& it = instance_snapshots_map.find(name); - const auto& [pick, all] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; try { - if (all) + if (all_or_none) { for (const auto& snapshot : pick) vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately @@ -3382,8 +3378,8 @@ void mp::Daemon::reply_msg(grpc::ServerReaderWriterInterface* se server->Write(reply); } -void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem* info, bool runtime_info, bool deleted, - bool& have_mounts) +void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem* info, bool no_runtime_info, + bool deleted, bool& have_mounts) { const auto& name = vm.vm_name; auto instance_info = info->mutable_instance_info(); @@ -3419,7 +3415,7 @@ void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem auto mount_info = info->mutable_mount_info(); populate_mount_info(vm_specs.mounts, mount_info, have_mounts); - if (!runtime_info && mp::utils::is_running(present_state)) + if (!no_runtime_info && mp::utils::is_running(present_state)) { mp::SSHSession session{vm.ssh_hostname(), vm.ssh_port(), vm_specs.ssh_username, *config->ssh_key_provider}; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c956c21c81f..ff999a5f300 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -356,12 +356,12 @@ void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) load_generic_snapshot_info(snapshot_dir); } -std::vector BaseVirtualMachine::get_children(const std::shared_ptr parent) const +std::vector BaseVirtualMachine::get_childrens_names(const Snapshot* parent) const { std::vector children; for (const auto& snapshot : view_snapshots()) - if (snapshot->get_parent_name() == parent->get_name()) + if (snapshot->get_parent().get() == parent) children.push_back(snapshot->get_name()); return children; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index f537c8ffcc1..85a487994b3 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -69,7 +69,7 @@ class BaseVirtualMachine : public VirtualMachine void delete_snapshot(const QDir& snapshot_dir, const std::string& name) override; void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; void load_snapshots(const QDir& snapshot_dir) override; - std::vector get_children(const std::shared_ptr parent) const override; + std::vector get_childrens_names(const Snapshot* parent) const override; protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 033a5ba291c..1d34679a582 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -75,7 +75,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, delete_snapshot, (const QDir& snapshot_dir, const std::string& name), (override)); MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); - MOCK_METHOD(std::vector, get_children, (const std::shared_ptr), (const, override)); + MOCK_METHOD(std::vector, get_childrens_names, (const Snapshot*), (const, override)); }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index c5ab522d2e7..c39b44c4a83 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -156,7 +156,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } - std::vector get_children(const std::shared_ptr) const override + std::vector get_childrens_names(const Snapshot*) const override { return {}; } From 8684c4677ffc7c31358d4910e6af633938141474 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 23 Aug 2023 09:11:53 -0700 Subject: [PATCH 354/627] [daemon] factor out common code to helper function --- src/daemon/daemon.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a9ec30a2fa8..5c072b240e2 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1269,6 +1269,15 @@ void populate_snapshot_fundamentals(std::shared_ptr snapshot timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); } +void populate_snapshot_overview(const std::string& instance_name, std::shared_ptr snapshot, + mp::SnapshotOverviewInfoItem* overview) +{ + auto fundamentals = overview->mutable_fundamentals(); + + overview->set_instance_name(instance_name); + populate_snapshot_fundamentals(snapshot, fundamentals); +} + void populate_mount_info(const std::unordered_map& mounts, mp::MountInfo* mount_info, bool& have_mounts) { @@ -1746,24 +1755,13 @@ try // clang-format on vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately for (const auto& snapshot : vm.view_snapshots()) - { - auto overview = response.mutable_snapshot_overview()->add_overview(); - auto fundamentals = overview->mutable_fundamentals(); - - overview->set_instance_name(name); - populate_snapshot_fundamentals(snapshot, fundamentals); - } + populate_snapshot_overview(name, snapshot, response.mutable_snapshot_overview()->add_overview()); } else { for (const auto& snapshot : pick) - { - auto overview = response.mutable_snapshot_overview()->add_overview(); - auto fundamentals = overview->mutable_fundamentals(); - - overview->set_instance_name(name); - populate_snapshot_fundamentals(vm.get_snapshot(snapshot), fundamentals); - } + populate_snapshot_overview(name, vm.get_snapshot(snapshot), + response.mutable_snapshot_overview()->add_overview()); } } catch (const NoSuchSnapshot& e) From a521ba1fc58719d2d0b10fdda38c3ce82d04af79 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 25 Aug 2023 12:36:23 +0100 Subject: [PATCH 355/627] [daemon] Extract repeated argument derivation --- src/daemon/daemon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 5c072b240e2..50935571d74 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1746,6 +1746,7 @@ try // clang-format on const auto& it = instance_snapshots_map.find(name); const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + auto overview = response.mutable_snapshot_overview()->add_overview(); try { @@ -1755,13 +1756,12 @@ try // clang-format on vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately for (const auto& snapshot : vm.view_snapshots()) - populate_snapshot_overview(name, snapshot, response.mutable_snapshot_overview()->add_overview()); + populate_snapshot_overview(name, snapshot, overview); } else { for (const auto& snapshot : pick) - populate_snapshot_overview(name, vm.get_snapshot(snapshot), - response.mutable_snapshot_overview()->add_overview()); + populate_snapshot_overview(name, vm.get_snapshot(snapshot), overview); } } catch (const NoSuchSnapshot& e) From a8b3c1af48e8e333ebbb9509eba5bd836799b896 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 25 Aug 2023 12:38:10 +0100 Subject: [PATCH 356/627] [snapshot] Renamed method for consistency with VM --- include/multipass/snapshot.h | 2 +- src/daemon/daemon.cpp | 2 +- src/platform/backends/shared/base_snapshot.cpp | 2 +- src/platform/backends/shared/base_snapshot.h | 4 ++-- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- tests/stub_snapshot.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 4e764d7772a..c220e344829 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -40,7 +40,7 @@ class Snapshot : private DisabledCopyMove virtual std::string get_name() const = 0; virtual std::string get_comment() const = 0; - virtual std::string get_parent_name() const = 0; + virtual std::string get_parents_name() const = 0; virtual QDateTime get_creation_timestamp() const = 0; virtual std::shared_ptr get_parent() const = 0; virtual std::shared_ptr get_parent() = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 50935571d74..9cdbf1de450 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1261,7 +1261,7 @@ void populate_snapshot_fundamentals(std::shared_ptr snapshot mp::SnapshotFundamentals* fundamentals) { fundamentals->set_snapshot_name(snapshot->get_name()); - fundamentals->set_parent(snapshot->get_parent_name()); + fundamentals->set_parent(snapshot->get_parents_name()); fundamentals->set_comment(snapshot->get_comment()); auto timestamp = fundamentals->mutable_creation_timestamp(); diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 4bc58724845..939c2aebb03 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -152,7 +152,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); snapshot.insert("state", static_cast(state)); snapshot.insert("metadata", metadata); - snapshot.insert("parent", QString::fromStdString(get_parent_name())); + snapshot.insert("parent", QString::fromStdString(get_parents_name())); // Extract mount serialization QJsonArray json_mounts; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 852e5aa6553..411c952c956 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -41,7 +41,7 @@ class BaseSnapshot : public Snapshot std::string get_name() const override; std::string get_comment() const override; QDateTime get_creation_timestamp() const override; - std::string get_parent_name() const override; + std::string get_parents_name() const override; std::shared_ptr get_parent() const override; std::shared_ptr get_parent() override; @@ -115,7 +115,7 @@ inline QDateTime multipass::BaseSnapshot::get_creation_timestamp() const return creation_timestamp; } -inline std::string multipass::BaseSnapshot::get_parent_name() const +inline std::string multipass::BaseSnapshot::get_parents_name() const { std::unique_lock lock{mutex}; auto par = parent; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index ff999a5f300..f41072de64d 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -387,7 +387,7 @@ template void BaseVirtualMachine::log_latest_snapshot(LockT lock) const { auto num_snapshots = static_cast(snapshots.size()); - auto parent_name = head_snapshot->get_parent_name(); + auto parent_name = head_snapshot->get_parents_name(); assert(num_snapshots <= snapshot_count && "can't have more snapshots than were ever taken"); @@ -435,7 +435,7 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) auto BaseVirtualMachine::make_head_file_rollback(const Path& head_path, QFile& head_file) const { - return sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parent_name(), + return sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parents_name(), existed = head_file.exists()]() noexcept { head_file_rollback_helper(head_path, head_file, old_head, existed); }); diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 015c7da8e22..4546d07c777 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -42,7 +42,7 @@ struct StubSnapshot : public Snapshot return QDateTime{}; } - std::string get_parent_name() const override + std::string get_parents_name() const override { return {}; } From 1dd04f288f04f1c7ae01d06b957f979d533e799e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 6 Sep 2023 18:20:24 +0100 Subject: [PATCH 357/627] Revert "[daemon] Extract repeated argument derivation" This reverts commit f570fd99315a80a1a503ab07cd8cbde86c45e710, since one snapshot overview needs to be added per snapshot. --- src/daemon/daemon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9cdbf1de450..2b8e1cc92f1 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1746,7 +1746,6 @@ try // clang-format on const auto& it = instance_snapshots_map.find(name); const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; - auto overview = response.mutable_snapshot_overview()->add_overview(); try { @@ -1756,12 +1755,13 @@ try // clang-format on vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately for (const auto& snapshot : vm.view_snapshots()) - populate_snapshot_overview(name, snapshot, overview); + populate_snapshot_overview(name, snapshot, response.mutable_snapshot_overview()->add_overview()); } else { for (const auto& snapshot : pick) - populate_snapshot_overview(name, vm.get_snapshot(snapshot), overview); + populate_snapshot_overview(name, vm.get_snapshot(snapshot), + response.mutable_snapshot_overview()->add_overview()); } } catch (const NoSuchSnapshot& e) From 4d773bec955284b571285d80a5b0f1f40abaca32 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 28 Jul 2023 00:46:25 -0700 Subject: [PATCH 358/627] [formatter] truncate snapshot comment on newline --- src/client/cli/formatter/table_formatter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index bfceca58a06..ead3faebe1b 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -28,6 +28,8 @@ namespace mp = multipass; namespace { +const std::regex newline("(\r\n|\n)"); + std::string format_images(const google::protobuf::RepeatedPtrField& images_info, std::string type) { @@ -95,7 +97,6 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) } // TODO@snapshots split and align string if it extends onto several lines - std::regex newline("(\r\n|\n)"); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Comment:", fundamentals.comment().empty() ? "--" @@ -238,7 +239,6 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) const auto parent_column_width = mp::format::column_width( overview.begin(), overview.end(), [](const auto& item) -> int { return item.fundamentals().parent().length(); }, parent_col_header.length()); - const auto max_comment_column_width = 50; const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; @@ -247,7 +247,13 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) for (const auto& item : mp::format::sort_snapshots(overview)) { + size_t max_comment_column_width = 50; + std::smatch match; auto snapshot = item.fundamentals(); + + if (std::regex_search(snapshot.comment().begin(), snapshot.comment().end(), match, newline)) + max_comment_column_width = std::min((size_t)(match.position(1)) + 1, max_comment_column_width); + fmt::format_to(std::back_inserter(buf), row_format, item.instance_name(), name_column_width, snapshot.snapshot_name(), snapshot_column_width, snapshot.parent().empty() ? "--" : snapshot.parent(), parent_column_width, From 72ff17a42c91439807ab09816b2e6507141fcd5e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 28 Jul 2023 00:46:55 -0700 Subject: [PATCH 359/627] [formatter] double quote comment field in csv to escape newlines --- src/client/cli/formatter/csv_formatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 53841f722fa..0f32c0a1e53 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -89,7 +89,7 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) for (const auto& item : mp::format::sort_snapshots(reply.snapshot_overview().overview())) { const auto& snapshot = item.fundamentals(); - fmt::format_to(std::back_inserter(buf), "{},{},{},{}\n", item.instance_name(), snapshot.snapshot_name(), + fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.instance_name(), snapshot.snapshot_name(), snapshot.parent(), snapshot.comment()); } From 62df244b105c3c819b4260d59c37a2775bb11222 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 18 Aug 2023 18:24:49 +0100 Subject: [PATCH 360/627] [snapshots] Move snapshot name template, for reuse Move snapshot name template and derivation to the BaseSnapshot, in order to reuse in other backends. --- src/platform/backends/qemu/qemu_snapshot.cpp | 8 -------- src/platform/backends/qemu/qemu_snapshot.h | 3 --- src/platform/backends/shared/base_snapshot.cpp | 8 ++++++++ src/platform/backends/shared/base_snapshot.h | 2 ++ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 916b0c64a64..f42e811cb51 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -33,9 +33,6 @@ namespace mpp = mp::platform; namespace { -const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char - that can't be part of the name, to avoid confusion */ - std::unique_ptr make_capture_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, @@ -108,8 +105,3 @@ void mp::QemuSnapshot::apply_impl() checked_exec_qemu_img(make_restore_spec(derive_tag(), image_path)); rollback.dismiss(); } - -QString mp::QemuSnapshot::derive_tag() const -{ - return snapshot_template.arg(get_name().c_str()); -} diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 2424690193b..81480a1eb1b 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -41,9 +41,6 @@ class QemuSnapshot : public BaseSnapshot void erase_impl() override; void apply_impl() override; -private: - QString derive_tag() const; - private: VirtualMachineDescription& desc; const Path& image_path; diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 939c2aebb03..bedda7074ca 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -29,6 +29,9 @@ namespace mp = multipass; namespace { +const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char + that can't be part of the name, to avoid confusion */ + std::unordered_map load_mounts(const QJsonArray& json) { std::unordered_map mounts; @@ -197,3 +200,8 @@ QJsonObject multipass::BaseSnapshot::serialize() const return ret; } + +QString multipass::BaseSnapshot::derive_tag() const +{ + return snapshot_template.arg(get_name().c_str()); +} diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 411c952c956..c85e2b1ea5e 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -69,6 +69,8 @@ class BaseSnapshot : public Snapshot virtual void erase_impl() = 0; virtual void apply_impl() = 0; + QString derive_tag() const; + private: struct InnerJsonTag { From 1b6cd801740014cece4b347562ec5496d55a563c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 28 Jul 2023 00:47:16 -0700 Subject: [PATCH 361/627] [tests] update tests to account for newlines in snapshot comments --- tests/test_output_formatter.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 6359865db3a..22a2f19bc57 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -521,7 +521,7 @@ auto construct_multiple_snapshot_overview_info_reply() fundamentals = snapshot_entry->mutable_fundamentals(); snapshot_entry->set_instance_name("prosperous-spadefish"); fundamentals->set_snapshot_name("snapshot2"); - fundamentals->set_comment("Before restoring snap1"); + fundamentals->set_comment("Before restoring snap1\nContains a newline that\r\nshould be truncated"); timestamp.set_seconds(1671840000); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); @@ -985,7 +985,7 @@ const std::vector orderable_list_info_formatter_outputs{ "hale-roller pristine -- A first snapshot\n" "hale-roller rocking pristine A very long comment that should be truncated by t…\n" "hale-roller rolling pristine Loaded with stuff\n" - "prosperous-spadefish snapshot2 -- Before restoring snap1\n" + "prosperous-spadefish snapshot2 -- Before restoring snap1…\n" "prosperous-spadefish snapshot10 snapshot2 --\n", "table_snapshot_overview_multiple"}, @@ -1030,12 +1030,13 @@ const std::vector orderable_list_info_formatter_outputs{ "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,\"\";,,3\n", "csv_info_multiple_instances"}, {&csv_formatter, &single_snapshot_overview_info_reply, - "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,This is a sample comment\n", "csv_snapshot_overview_single"}, + "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,\"This is a sample comment\"\n", "csv_snapshot_overview_single"}, {&csv_formatter, &multiple_snapshot_overview_info_reply, - "Instance,Snapshot,Parent,Comment\nhale-roller,pristine,,A first " - "snapshot\nhale-roller,rocking,pristine,A very long comment that should be truncated by the table " - "formatter\nhale-roller,rolling,pristine,Loaded with stuff\nprosperous-spadefish,snapshot2,,Before restoring " - "snap1\nprosperous-spadefish,snapshot10,snapshot2,\n", + "Instance,Snapshot,Parent,Comment\nhale-roller,pristine,,\"A first " + "snapshot\"\nhale-roller,rocking,pristine,\"A very long comment that should be truncated by the table " + "formatter\"\nhale-roller,rolling,pristine,\"Loaded with stuff\"\nprosperous-spadefish,snapshot2,,\"Before " + "restoring snap1\nContains a newline that\r\nshould be " + "truncated\"\nprosperous-spadefish,snapshot10,snapshot2,\"\"\n", "csv_snapshot_overview_multiple"}, {&yaml_formatter, &empty_list_reply, "\n", "yaml_list_empty"}, @@ -1194,7 +1195,7 @@ const std::vector orderable_list_info_formatter_outputs{ "prosperous-spadefish:\n" " - snapshot2:\n" " - parent: ~\n" - " comment: Before restoring snap1\n" + " comment: \"Before restoring snap1\\nContains a newline that\\r\\nshould be truncated\"\n" " - snapshot10:\n" " - parent: snapshot2\n" " comment: ~\n", @@ -1418,7 +1419,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " \"parent\": \"snapshot2\"\n" " },\n" " \"snapshot2\": {\n" - " \"comment\": \"Before restoring snap1\",\n" + " \"comment\": \"Before restoring snap1\\nContains a newline that\\r\\nshould be truncated\",\n" " \"parent\": \"\"\n" " }\n" " }\n" From 08a6b416575ee63d559b5ae07fe79605a3328af1 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 17 Jul 2023 19:17:51 -0700 Subject: [PATCH 362/627] [formatter] snapshot details in yaml formatter --- src/client/cli/formatter/yaml_formatter.cpp | 190 ++++++++++------- tests/test_output_formatter.cpp | 219 +++++++++++++++++++- 2 files changed, 333 insertions(+), 76 deletions(-) diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 0b3b011e265..4e7afaddb12 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -58,92 +58,140 @@ std::map format_images(const ImageInfo& images_info) return images_node; } -std::string generate_instance_info_report(const mp::InfoReply& reply) +YAML::Node generate_snapshot_details(const mp::DetailedInfoItem& item) { - YAML::Node info_node; + const auto& snapshot_details = item.snapshot_info(); + const auto& fundamentals = snapshot_details.fundamentals(); + YAML::Node snapshot_node; - info_node["errors"].push_back(YAML::Null); + snapshot_node["size"] = snapshot_details.size().empty() ? YAML::Node() : YAML::Node(snapshot_details.size()); + snapshot_node["cpu_count"] = item.cpu_count(); + snapshot_node["disk_space"] = item.disk_total(); + snapshot_node["memory_size"] = item.memory_total(); - for (const auto& info : mp::format::sorted(reply.detailed_report().details())) + YAML::Node mounts; + for (const auto& mount : item.mount_info().mount_paths()) { - const auto& instance_details = info.instance_info(); - YAML::Node instance_node; + YAML::Node mount_node; + mount_node["source_path"] = mount.source_path(); + mounts[mount.target_path()] = mount_node; + } + snapshot_node["mounts"] = mounts; - instance_node["state"] = mp::format::status_string_for(info.instance_status()); - instance_node["snapshots"] = instance_details.num_snapshots(); - instance_node["image_hash"] = instance_details.id(); - instance_node["image_release"] = instance_details.image_release(); - instance_node["release"] = - instance_details.current_release().empty() ? YAML::Node() : YAML::Node(instance_details.current_release()); - instance_node["cpu_count"] = info.cpu_count().empty() ? YAML::Node() : YAML::Node(info.cpu_count()); + snapshot_node["created"] = google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp()); + snapshot_node["parent"] = fundamentals.parent().empty() ? YAML::Node() : YAML::Node(fundamentals.parent()); + + snapshot_node["children"] = YAML::Node(YAML::NodeType::Sequence); + for (const auto& child : snapshot_details.children()) + snapshot_node["children"].push_back(child); + + snapshot_node["comment"] = fundamentals.comment().empty() ? YAML::Node() : YAML::Node(fundamentals.comment()); + + return snapshot_node; +} + +YAML::Node generate_instance_details(const mp::DetailedInfoItem& item) +{ + const auto& instance_details = item.instance_info(); + YAML::Node instance_node; + + instance_node["state"] = mp::format::status_string_for(item.instance_status()); + instance_node["snapshot_count"] = instance_details.num_snapshots(); + instance_node["image_hash"] = instance_details.id(); + instance_node["image_release"] = instance_details.image_release(); + instance_node["release"] = + instance_details.current_release().empty() ? YAML::Node() : YAML::Node(instance_details.current_release()); + instance_node["cpu_count"] = item.cpu_count().empty() ? YAML::Node() : YAML::Node(item.cpu_count()); + + if (!instance_details.load().empty()) + { + // The VM returns load info in the default C locale + auto current_loc = std::locale(); + std::locale::global(std::locale("C")); + auto loads = mp::utils::split(instance_details.load(), " "); + for (const auto& entry : loads) + instance_node["load"].push_back(entry); + std::locale::global(current_loc); + } + + YAML::Node disk; + disk["used"] = instance_details.disk_usage().empty() ? YAML::Node() : YAML::Node(instance_details.disk_usage()); + disk["total"] = item.disk_total().empty() ? YAML::Node() : YAML::Node(item.disk_total()); + + // TODO: disk name should come from daemon + YAML::Node disk_node; + disk_node["sda1"] = disk; + instance_node["disks"].push_back(disk_node); + + YAML::Node memory; + memory["usage"] = instance_details.memory_usage().empty() ? YAML::Node() + : YAML::Node(std::stoll(instance_details.memory_usage())); + memory["total"] = item.memory_total().empty() ? YAML::Node() : YAML::Node(std::stoll(item.memory_total())); + instance_node["memory"] = memory; + + instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); + for (const auto& ip : instance_details.ipv4()) + instance_node["ipv4"].push_back(ip); - if (!instance_details.load().empty()) + YAML::Node mounts; + for (const auto& mount : item.mount_info().mount_paths()) + { + YAML::Node mount_node; + + for (const auto& uid_mapping : mount.mount_maps().uid_mappings()) { - // The VM returns load info in the default C locale - auto current_loc = std::locale(); - std::locale::global(std::locale("C")); - auto loads = mp::utils::split(instance_details.load(), " "); - for (const auto& entry : loads) - instance_node["load"].push_back(entry); - std::locale::global(current_loc); + auto host_uid = uid_mapping.host_id(); + auto instance_uid = uid_mapping.instance_id(); + + mount_node["uid_mappings"].push_back( + fmt::format("{}:{}", std::to_string(host_uid), + (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); } + for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) + { + auto host_gid = gid_mapping.host_id(); + auto instance_gid = gid_mapping.instance_id(); - YAML::Node disk; - disk["used"] = instance_details.disk_usage().empty() ? YAML::Node() : YAML::Node(instance_details.disk_usage()); - disk["total"] = info.disk_total().empty() ? YAML::Node() : YAML::Node(info.disk_total()); + mount_node["gid_mappings"].push_back( + fmt::format("{}:{}", std::to_string(host_gid), + (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); + } - // TODO: disk name should come from daemon - YAML::Node disk_node; - disk_node["sda1"] = disk; - instance_node["disks"].push_back(disk_node); + mount_node["source_path"] = mount.source_path(); + mounts[mount.target_path()] = mount_node; + } + instance_node["mounts"] = mounts; - YAML::Node memory; - memory["usage"] = instance_details.memory_usage().empty() - ? YAML::Node() - : YAML::Node(std::stoll(instance_details.memory_usage())); - memory["total"] = info.memory_total().empty() ? YAML::Node() : YAML::Node(std::stoll(info.memory_total())); - instance_node["memory"] = memory; + return instance_node; +} - instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); - for (const auto& ip : instance_details.ipv4()) - instance_node["ipv4"].push_back(ip); +YAML::Node generate_instance_info_report(const mp::InfoReply& reply) +{ + YAML::Node info_node; + + info_node["errors"].push_back(YAML::Null); - YAML::Node mounts; - for (const auto& mount : info.mount_info().mount_paths()) + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) + { + if (info.has_instance_info()) { - YAML::Node mount_node; - - for (const auto& uid_mapping : mount.mount_maps().uid_mappings()) - { - auto host_uid = uid_mapping.host_id(); - auto instance_uid = uid_mapping.instance_id(); - - mount_node["uid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_uid), - (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); - } - for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) - { - auto host_gid = gid_mapping.host_id(); - auto instance_gid = gid_mapping.instance_id(); - - mount_node["gid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_gid), - (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); - } - - mount_node["source_path"] = mount.source_path(); - mounts[mount.target_path()] = mount_node; + info_node[info.name()].push_back(generate_instance_details(info)); } - instance_node["mounts"] = mounts; + else + { + assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); - info_node[info.name()].push_back(instance_node); + YAML::Node snapshot_node; + snapshot_node[info.snapshot_info().fundamentals().snapshot_name()] = generate_snapshot_details(info); + + info_node[info.name()][0]["snapshots"].push_back(snapshot_node); + } } - return mpu::emit_yaml(info_node); + return info_node; } -std::string generate_snapshot_overview_report(const mp::InfoReply& reply) +YAML::Node generate_snapshot_overview_report(const mp::InfoReply& reply) { YAML::Node info_node; @@ -162,25 +210,25 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) info_node[item.instance_name()].push_back(instance_node); } - return mpu::emit_yaml(info_node); + return info_node; } } // namespace std::string mp::YamlFormatter::format(const InfoReply& reply) const { - std::string output; + YAML::Node info; if (reply.has_detailed_report()) { - output = generate_instance_info_report(reply); + info = generate_instance_info_report(reply); } else { assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - output = generate_snapshot_overview_report(reply); + info = generate_snapshot_overview_report(reply); } - return output; + return mpu::emit_yaml(info); } std::string mp::YamlFormatter::format(const ListReply& reply) const diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 6359865db3a..ad88c1caf7e 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -403,6 +403,19 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() timestamp.set_nanos(21000000); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + info_entry = info_reply.mutable_detailed_report()->add_details(); + fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); + + info_entry->set_name("bogus-instance"); + info_entry->set_cpu_count("2"); + info_entry->set_disk_total("4.9GiB"); + info_entry->set_memory_total("0.9GiB"); + fundamentals->set_snapshot_name("snapshot1"); + + timestamp.set_seconds(63107999); + timestamp.set_nanos(21000000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + info_entry = info_reply.mutable_detailed_report()->add_details(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); @@ -439,7 +452,7 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() info_entry->set_disk_total("6764573492"); info_entry->mutable_instance_info()->set_current_release("Ubuntu 16.04.3 LTS"); info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); - info_entry->mutable_instance_info()->set_num_snapshots(1); + info_entry->mutable_instance_info()->set_num_snapshots(2); info_entry = info_reply.mutable_detailed_report()->add_details(); fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); @@ -931,7 +944,7 @@ const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &multiple_mixed_instances_and_snapshots_info_reply, "Name: bogus-instance\n" "State: Running\n" - "Snapshots: 1\n" + "Snapshots: 2\n" "IPv4: 10.21.124.56\n" "Release: Ubuntu 16.04.3 LTS\n" "Image hash: 1797c5c82016 (Ubuntu 16.04 LTS)\n" @@ -953,6 +966,17 @@ const std::vector orderable_list_info_formatter_outputs{ "Disk usage: --\n" "Memory usage: --\n" "Mounts: --\n\n" + "Snapshot: snapshot1\n" + "Instance: bogus-instance\n" + "Size: --\n" + "CPU(s): 2\n" + "Disk space: 4.9GiB\n" + "Memory size: 0.9GiB\n" + "Mounts: --\n" + "Created: 1972-01-01T09:59:59.021Z\n" + "Parent: --\n" + "Children: --\n" + "Comment: --\n\n" "Snapshot: snapshot2\n" "Instance: bogus-instance\n" "CPU(s): 2\n" @@ -1089,7 +1113,7 @@ const std::vector orderable_list_info_formatter_outputs{ " - ~\n" "foo:\n" " - state: Running\n" - " snapshots: 0\n" + " snapshot_count: 0\n" " image_hash: 1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac\n" " image_release: 16.04 LTS\n" " release: Ubuntu 16.04.3 LTS\n" @@ -1127,7 +1151,7 @@ const std::vector orderable_list_info_formatter_outputs{ " - ~\n" "bogus-instance:\n" " - state: Running\n" - " snapshots: 1\n" + " snapshot_count: 1\n" " image_hash: 1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac\n" " image_release: 16.04 LTS\n" " release: Ubuntu 16.04.3 LTS\n" @@ -1154,7 +1178,7 @@ const std::vector orderable_list_info_formatter_outputs{ " source_path: /home/user/source\n" "bombastic:\n" " - state: Stopped\n" - " snapshots: 3\n" + " snapshot_count: 3\n" " image_hash: ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\n" " image_release: 18.04 LTS\n" " release: ~\n" @@ -1170,6 +1194,191 @@ const std::vector orderable_list_info_formatter_outputs{ " []\n" " mounts: ~\n", "yaml_info_multiple_instances"}, + {&yaml_formatter, &single_snapshot_info_reply, + "errors:\n" + " - ~\n" + "bogus-instance:\n" + " - snapshots:\n" + " - snapshot2:\n" + " size: 128MiB\n" + " cpu_count: 2\n" + " disk_space: 4.9GiB\n" + " memory_size: 0.9GiB\n" + " mounts:\n" + " source:\n" + " source_path: /home/user/source\n" + " Home:\n" + " source_path: /home/user\n" + " created: \"1972-01-01T10:00:20.021Z\"\n" + " parent: snapshot1\n" + " children:\n" + " - snapshot3\n" + " - snapshot4\n" + " comment: \"This is a comment with some\\nnew\\r\\nlines.\"\n", + "yaml_info_single_snapshot_info_reply"}, + {&yaml_formatter, &multiple_snapshots_info_reply, + "errors:\n" + " - ~\n" + "bogus-instance:\n" + " - snapshots:\n" + " - snapshot2:\n" + " size: ~\n" + " cpu_count: 2\n" + " disk_space: 4.9GiB\n" + " memory_size: 0.9GiB\n" + " mounts:\n" + " source:\n" + " source_path: /home/user/source\n" + " Home:\n" + " source_path: /home/user\n" + " created: \"1972-01-01T10:00:20.021Z\"\n" + " parent: snapshot1\n" + " children:\n" + " - snapshot3\n" + " - snapshot4\n" + " comment: ~\n" + "messier-87:\n" + " - snapshots:\n" + " - black-hole:\n" + " size: ~\n" + " cpu_count: 1\n" + " disk_space: 1024GiB\n" + " memory_size: 128GiB\n" + " mounts: ~\n" + " created: \"2019-04-10T11:59:59Z\"\n" + " parent: ~\n" + " children:\n" + " []\n" + " comment: Captured by EHT\n", + "yaml_info_multiple_snapshots_info_reply"}, + {&yaml_formatter, &mixed_instance_and_snapshot_info_reply, + "errors:\n" + " - ~\n" + "bombastic:\n" + " - state: Stopped\n" + " snapshot_count: 3\n" + " image_hash: ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\n" + " image_release: 18.04 LTS\n" + " release: ~\n" + " cpu_count: ~\n" + " disks:\n" + " - sda1:\n" + " used: ~\n" + " total: ~\n" + " memory:\n" + " usage: ~\n" + " total: ~\n" + " ipv4:\n" + " []\n" + " mounts: ~\n" + "bogus-instance:\n" + " - snapshots:\n" + " - snapshot2:\n" + " size: ~\n" + " cpu_count: 2\n" + " disk_space: 4.9GiB\n" + " memory_size: 0.9GiB\n" + " mounts:\n" + " source:\n" + " source_path: /home/user/source\n" + " Home:\n" + " source_path: /home/user\n" + " created: \"1972-01-01T10:00:20.021Z\"\n" + " parent: snapshot1\n" + " children:\n" + " - snapshot3\n" + " - snapshot4\n" + " comment: ~\n", + "yaml_info_mixed_instance_and_snapshot_info_reply"}, + {&yaml_formatter, &multiple_mixed_instances_and_snapshots_info_reply, + "errors:\n" + " - ~\n" + "bogus-instance:\n" + " - state: Running\n" + " snapshot_count: 2\n" + " image_hash: 1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac\n" + " image_release: 16.04 LTS\n" + " release: Ubuntu 16.04.3 LTS\n" + " cpu_count: 4\n" + " load:\n" + " - 0.03\n" + " - 0.10\n" + " - 0.15\n" + " disks:\n" + " - sda1:\n" + " used: 1932735284\n" + " total: 6764573492\n" + " memory:\n" + " usage: 38797312\n" + " total: 1610612736\n" + " ipv4:\n" + " - 10.21.124.56\n" + " mounts:\n" + " source:\n" + " uid_mappings:\n" + " - \"1000:501\"\n" + " gid_mappings:\n" + " - \"1000:501\"\n" + " source_path: /home/user/source\n" + " snapshots:\n" + " - snapshot1:\n" + " size: ~\n" + " cpu_count: 2\n" + " disk_space: 4.9GiB\n" + " memory_size: 0.9GiB\n" + " mounts: ~\n" + " created: \"1972-01-01T09:59:59.021Z\"\n" + " parent: ~\n" + " children:\n" + " []\n" + " comment: ~\n" + " - snapshot2:\n" + " size: ~\n" + " cpu_count: 2\n" + " disk_space: 4.9GiB\n" + " memory_size: 0.9GiB\n" + " mounts:\n" + " source:\n" + " source_path: /home/user/source\n" + " Home:\n" + " source_path: /home/user\n" + " created: \"1972-01-01T10:00:20.021Z\"\n" + " parent: snapshot1\n" + " children:\n" + " - snapshot3\n" + " - snapshot4\n" + " comment: ~\n" + "bombastic:\n" + " - state: Stopped\n" + " snapshot_count: 3\n" + " image_hash: ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\n" + " image_release: 18.04 LTS\n" + " release: ~\n" + " cpu_count: ~\n" + " disks:\n" + " - sda1:\n" + " used: ~\n" + " total: ~\n" + " memory:\n" + " usage: ~\n" + " total: ~\n" + " ipv4:\n" + " []\n" + " mounts: ~\n" + "messier-87:\n" + " - snapshots:\n" + " - black-hole:\n" + " size: ~\n" + " cpu_count: 1\n" + " disk_space: 1024GiB\n" + " memory_size: 128GiB\n" + " mounts: ~\n" + " created: \"2019-04-10T11:59:59Z\"\n" + " parent: ~\n" + " children:\n" + " []\n" + " comment: Captured by EHT\n", + "yaml_info_multiple_mixed_instances_and_snapshots"}, {&yaml_formatter, &single_snapshot_overview_info_reply, "errors:\n" " - ~\n" From fa77699357f2ea93e327dafc3cc0e761a2222473 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 00:36:21 -0700 Subject: [PATCH 363/627] [formatter] add snapshot details output to json formatter --- src/client/cli/formatter/json_formatter.cpp | 249 +++++++++++++------- 1 file changed, 166 insertions(+), 83 deletions(-) diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 8205664a37e..db6be42e3ce 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -22,7 +22,6 @@ #include #include -#include #include namespace mp = multipass; @@ -56,105 +55,189 @@ QJsonObject format_images(const google::protobuf::RepeatedPtrField Date: Fri, 18 Aug 2023 18:29:52 +0100 Subject: [PATCH 364/627] [snapshots] Rename generalized method to derive ID Rename method to derive the ID of a snapshot, to use with the backend ("tag" was a QEMU concept). --- src/platform/backends/qemu/qemu_snapshot.cpp | 6 +++--- src/platform/backends/shared/base_snapshot.cpp | 2 +- src/platform/backends/shared/base_snapshot.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index f42e811cb51..aa23f0f26fa 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -77,7 +77,7 @@ mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, void mp::QemuSnapshot::capture_impl() { - auto tag = derive_tag(); + auto tag = derive_id(); // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) @@ -90,7 +90,7 @@ void mp::QemuSnapshot::capture_impl() void mp::QemuSnapshot::erase_impl() { - checked_exec_qemu_img(make_delete_spec(derive_tag(), image_path)); + checked_exec_qemu_img(make_delete_spec(derive_id(), image_path)); } void mp::QemuSnapshot::apply_impl() @@ -102,6 +102,6 @@ void mp::QemuSnapshot::apply_impl() desc.mem_size = get_mem_size(); desc.disk_space = get_disk_space(); - checked_exec_qemu_img(make_restore_spec(derive_tag(), image_path)); + checked_exec_qemu_img(make_restore_spec(derive_id(), image_path)); rollback.dismiss(); } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index bedda7074ca..f09467069df 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -201,7 +201,7 @@ QJsonObject multipass::BaseSnapshot::serialize() const return ret; } -QString multipass::BaseSnapshot::derive_tag() const +QString multipass::BaseSnapshot::derive_id() const { return snapshot_template.arg(get_name().c_str()); } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index c85e2b1ea5e..126e19183b6 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -69,7 +69,7 @@ class BaseSnapshot : public Snapshot virtual void erase_impl() = 0; virtual void apply_impl() = 0; - QString derive_tag() const; + QString derive_id() const; private: struct InnerJsonTag From 541c03771b1e091f44239dc586d6a70f8f8a063b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 00:37:19 -0700 Subject: [PATCH 365/627] [tests] add tests for snapshot details in json format --- tests/test_output_formatter.cpp | 260 +++++++++++++++++++++++++++++++- 1 file changed, 257 insertions(+), 3 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index ad88c1caf7e..89a016ba9e8 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -1517,7 +1517,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " },\n" " \"release\": \"Ubuntu 16.04.3 LTS\",\n" - " \"snapshots\": \"0\",\n" + " \"snapshot_count\": \"0\",\n" " \"state\": \"Running\"\n" " }\n" " }\n" @@ -1562,7 +1562,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " },\n" " \"release\": \"Ubuntu 16.04.3 LTS\",\n" - " \"snapshots\": \"1\",\n" + " \"snapshot_count\": \"1\",\n" " \"state\": \"Running\"\n" " },\n" " \"bombastic\": {\n" @@ -1582,12 +1582,266 @@ const std::vector non_orderable_list_info_formatter_outputs{ " \"mounts\": {\n" " },\n" " \"release\": \"\",\n" - " \"snapshots\": \"3\",\n" + " \"snapshot_count\": \"3\",\n" " \"state\": \"Stopped\"\n" " }\n" " }\n" "}\n", "json_info_multiple_instances"}, + {&json_formatter, &single_snapshot_info_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"bogus-instance\": {\n" + " \"snapshots\": {\n" + " \"snapshot2\": {\n" + " \"children\": [\n" + " \"snapshot3\",\n" + " \"snapshot4\"\n" + " ],\n" + " \"comment\": \"This is a comment with some\\nnew\\r\\nlines.\",\n" + " \"cpu_count\": \"2\",\n" + " \"created\": \"1972-01-01T10:00:20.021Z\",\n" + " \"disk_space\": \"4.9GiB\",\n" + " \"memory_size\": \"0.9GiB\",\n" + " \"mounts\": {\n" + " \"Home\": {\n" + " \"source_path\": \"/home/user\"\n" + " },\n" + " \"source\": {\n" + " \"source_path\": \"/home/user/source\"\n" + " }\n" + " },\n" + " \"parent\": \"snapshot1\",\n" + " \"size\": \"128MiB\"\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_info_single_snapshot_info_reply"}, + {&json_formatter, &multiple_snapshots_info_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"bogus-instance\": {\n" + " \"snapshots\": {\n" + " \"snapshot2\": {\n" + " \"children\": [\n" + " \"snapshot3\",\n" + " \"snapshot4\"\n" + " ],\n" + " \"comment\": \"\",\n" + " \"cpu_count\": \"2\",\n" + " \"created\": \"1972-01-01T10:00:20.021Z\",\n" + " \"disk_space\": \"4.9GiB\",\n" + " \"memory_size\": \"0.9GiB\",\n" + " \"mounts\": {\n" + " \"Home\": {\n" + " \"source_path\": \"/home/user\"\n" + " },\n" + " \"source\": {\n" + " \"source_path\": \"/home/user/source\"\n" + " }\n" + " },\n" + " \"parent\": \"snapshot1\",\n" + " \"size\": \"\"\n" + " }\n" + " }\n" + " },\n" + " \"messier-87\": {\n" + " \"snapshots\": {\n" + " \"black-hole\": {\n" + " \"children\": [\n" + " ],\n" + " \"comment\": \"Captured by EHT\",\n" + " \"cpu_count\": \"1\",\n" + " \"created\": \"2019-04-10T11:59:59Z\",\n" + " \"disk_space\": \"1024GiB\",\n" + " \"memory_size\": \"128GiB\",\n" + " \"mounts\": {\n" + " },\n" + " \"parent\": \"\",\n" + " \"size\": \"\"\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_info_multiple_snapshots_info_reply"}, + {&json_formatter, &mixed_instance_and_snapshot_info_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"bogus-instance\": {\n" + " \"snapshots\": {\n" + " \"snapshot2\": {\n" + " \"children\": [\n" + " \"snapshot3\",\n" + " \"snapshot4\"\n" + " ],\n" + " \"comment\": \"\",\n" + " \"cpu_count\": \"2\",\n" + " \"created\": \"1972-01-01T10:00:20.021Z\",\n" + " \"disk_space\": \"4.9GiB\",\n" + " \"memory_size\": \"0.9GiB\",\n" + " \"mounts\": {\n" + " \"Home\": {\n" + " \"source_path\": \"/home/user\"\n" + " },\n" + " \"source\": {\n" + " \"source_path\": \"/home/user/source\"\n" + " }\n" + " },\n" + " \"parent\": \"snapshot1\",\n" + " \"size\": \"\"\n" + " }\n" + " }\n" + " },\n" + " \"bombastic\": {\n" + " \"cpu_count\": \"\",\n" + " \"disks\": {\n" + " \"sda1\": {\n" + " }\n" + " },\n" + " \"image_hash\": \"ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\",\n" + " \"image_release\": \"18.04 LTS\",\n" + " \"ipv4\": [\n" + " ],\n" + " \"load\": [\n" + " ],\n" + " \"memory\": {\n" + " },\n" + " \"mounts\": {\n" + " },\n" + " \"release\": \"\",\n" + " \"snapshot_count\": \"3\",\n" + " \"state\": \"Stopped\"\n" + " }\n" + " }\n" + "}\n", + "json_info_mixed_instance_and_snapshot_info_reply"}, + {&json_formatter, &multiple_mixed_instances_and_snapshots_info_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"bogus-instance\": {\n" + " \"cpu_count\": \"4\",\n" + " \"disks\": {\n" + " \"sda1\": {\n" + " \"total\": \"6764573492\",\n" + " \"used\": \"1932735284\"\n" + " }\n" + " },\n" + " \"image_hash\": \"1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac\",\n" + " \"image_release\": \"16.04 LTS\",\n" + " \"ipv4\": [\n" + " \"10.21.124.56\"\n" + " ],\n" + " \"load\": [\n" + " 0.03,\n" + " 0.1,\n" + " 0.15\n" + " ],\n" + " \"memory\": {\n" + " \"total\": 1610612736,\n" + " \"used\": 38797312\n" + " },\n" + " \"mounts\": {\n" + " \"source\": {\n" + " \"gid_mappings\": [\n" + " \"1000:501\"\n" + " ],\n" + " \"source_path\": \"/home/user/source\",\n" + " \"uid_mappings\": [\n" + " \"1000:501\"\n" + " ]\n" + " }\n" + " },\n" + " \"release\": \"Ubuntu 16.04.3 LTS\",\n" + " \"snapshot_count\": \"2\",\n" + " \"snapshots\": {\n" + " \"snapshot1\": {\n" + " \"children\": [\n" + " ],\n" + " \"comment\": \"\",\n" + " \"cpu_count\": \"2\",\n" + " \"created\": \"1972-01-01T09:59:59.021Z\",\n" + " \"disk_space\": \"4.9GiB\",\n" + " \"memory_size\": \"0.9GiB\",\n" + " \"mounts\": {\n" + " },\n" + " \"parent\": \"\",\n" + " \"size\": \"\"\n" + " },\n" + " \"snapshot2\": {\n" + " \"children\": [\n" + " \"snapshot3\",\n" + " \"snapshot4\"\n" + " ],\n" + " \"comment\": \"\",\n" + " \"cpu_count\": \"2\",\n" + " \"created\": \"1972-01-01T10:00:20.021Z\",\n" + " \"disk_space\": \"4.9GiB\",\n" + " \"memory_size\": \"0.9GiB\",\n" + " \"mounts\": {\n" + " \"Home\": {\n" + " \"source_path\": \"/home/user\"\n" + " },\n" + " \"source\": {\n" + " \"source_path\": \"/home/user/source\"\n" + " }\n" + " },\n" + " \"parent\": \"snapshot1\",\n" + " \"size\": \"\"\n" + " }\n" + " },\n" + " \"state\": \"Running\"\n" + " },\n" + " \"bombastic\": {\n" + " \"cpu_count\": \"\",\n" + " \"disks\": {\n" + " \"sda1\": {\n" + " }\n" + " },\n" + " \"image_hash\": \"ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509\",\n" + " \"image_release\": \"18.04 LTS\",\n" + " \"ipv4\": [\n" + " ],\n" + " \"load\": [\n" + " ],\n" + " \"memory\": {\n" + " },\n" + " \"mounts\": {\n" + " },\n" + " \"release\": \"\",\n" + " \"snapshot_count\": \"3\",\n" + " \"state\": \"Stopped\"\n" + " },\n" + " \"messier-87\": {\n" + " \"snapshots\": {\n" + " \"black-hole\": {\n" + " \"children\": [\n" + " ],\n" + " \"comment\": \"Captured by EHT\",\n" + " \"cpu_count\": \"1\",\n" + " \"created\": \"2019-04-10T11:59:59Z\",\n" + " \"disk_space\": \"1024GiB\",\n" + " \"memory_size\": \"128GiB\",\n" + " \"mounts\": {\n" + " },\n" + " \"parent\": \"\",\n" + " \"size\": \"\"\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_info_multiple_mixed_instances_and_snapshots"}, {&json_formatter, &single_snapshot_overview_info_reply, "{\n" " \"errors\": [\n" From 64928e4bc935495ba6d468464cfe57bfefef8f66 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 00:38:50 -0700 Subject: [PATCH 366/627] [tests] rearrange order of info details in order to test merging json objects --- tests/test_output_formatter.cpp | 65 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 89a016ba9e8..37d348eddba 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -380,6 +380,37 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() mp::InfoReply info_reply; auto info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry->set_name("bogus-instance"); + info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); + info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); + info_entry->mutable_instance_info()->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); + + auto mount_info = info_entry->mutable_mount_info(); + mount_info->set_longest_path_len(17); + + auto mount_entry = mount_info->add_mount_paths(); + mount_entry->set_source_path("/home/user/source"); + mount_entry->set_target_path("source"); + + auto uid_map_pair = mount_entry->mutable_mount_maps()->add_uid_mappings(); + uid_map_pair->set_host_id(1000); + uid_map_pair->set_instance_id(501); + + auto gid_map_pair = mount_entry->mutable_mount_maps()->add_gid_mappings(); + gid_map_pair->set_host_id(1000); + gid_map_pair->set_instance_id(501); + + info_entry->set_cpu_count("4"); + info_entry->mutable_instance_info()->set_load("0.03 0.10 0.15"); + info_entry->mutable_instance_info()->set_memory_usage("38797312"); + info_entry->set_memory_total("1610612736"); + info_entry->mutable_instance_info()->set_disk_usage("1932735284"); + info_entry->set_disk_total("6764573492"); + info_entry->mutable_instance_info()->set_current_release("Ubuntu 16.04.3 LTS"); + info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); + info_entry->mutable_instance_info()->set_num_snapshots(2); + + info_entry = info_reply.mutable_detailed_report()->add_details(); auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("bogus-instance"); @@ -391,7 +422,7 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() info_entry->mutable_snapshot_info()->add_children("snapshot3"); info_entry->mutable_snapshot_info()->add_children("snapshot4"); - auto mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); + mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); mount_entry->set_source_path("/home/user/source"); mount_entry->set_target_path("source"); mount_entry = info_entry->mutable_mount_info()->add_mount_paths(); @@ -423,37 +454,6 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() info_entry->mutable_instance_info()->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); info_entry->mutable_instance_info()->set_num_snapshots(3); - info_entry = info_reply.mutable_detailed_report()->add_details(); - info_entry->set_name("bogus-instance"); - info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); - info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); - info_entry->mutable_instance_info()->set_id("1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac"); - - auto mount_info = info_entry->mutable_mount_info(); - mount_info->set_longest_path_len(17); - - mount_entry = mount_info->add_mount_paths(); - mount_entry->set_source_path("/home/user/source"); - mount_entry->set_target_path("source"); - - auto uid_map_pair = mount_entry->mutable_mount_maps()->add_uid_mappings(); - uid_map_pair->set_host_id(1000); - uid_map_pair->set_instance_id(501); - - auto gid_map_pair = mount_entry->mutable_mount_maps()->add_gid_mappings(); - gid_map_pair->set_host_id(1000); - gid_map_pair->set_instance_id(501); - - info_entry->set_cpu_count("4"); - info_entry->mutable_instance_info()->set_load("0.03 0.10 0.15"); - info_entry->mutable_instance_info()->set_memory_usage("38797312"); - info_entry->set_memory_total("1610612736"); - info_entry->mutable_instance_info()->set_disk_usage("1932735284"); - info_entry->set_disk_total("6764573492"); - info_entry->mutable_instance_info()->set_current_release("Ubuntu 16.04.3 LTS"); - info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); - info_entry->mutable_instance_info()->set_num_snapshots(2); - info_entry = info_reply.mutable_detailed_report()->add_details(); fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); @@ -968,7 +968,6 @@ const std::vector orderable_list_info_formatter_outputs{ "Mounts: --\n\n" "Snapshot: snapshot1\n" "Instance: bogus-instance\n" - "Size: --\n" "CPU(s): 2\n" "Disk space: 4.9GiB\n" "Memory size: 0.9GiB\n" From 83fbf482af9a7598f27b13bc0962f4dfeacbfa0e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 02:27:10 -0700 Subject: [PATCH 367/627] [formatter] add snapshot details to csv formatter --- src/client/cli/formatter/csv_formatter.cpp | 55 ++++++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 53841f722fa..52108527729 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -46,7 +46,35 @@ std::string format_images(const google::protobuf::RepeatedPtrField {};", mount->source_path(), mount->target_path()); + + fmt::format_to(std::back_inserter(buf), ",{},{},\"{}\";,\"{}\"\n", + google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp()), + fundamentals.parent(), fmt::join(info.snapshot_info().children(), ","), fundamentals.comment()); + } + + return fmt::to_string(buf); +} + +std::string generate_instance_details(const mp::InfoReply reply) { fmt::memory_buffer buf; @@ -55,8 +83,10 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory " "total,Mounts,AllIPv4,CPU(s),Snapshots\n"); - for (const auto& info : mp::format::sorted(reply.detailed_report().details())) + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) { + assert(info.has_instance_info() && + "outputting instance and snapshot details together is not supported in csv format"); const auto& instance_details = info.instance_info(); fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},{},{},{},{},{},{},{},", info.name(), @@ -69,9 +99,7 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) auto mount_paths = info.mount_info().mount_paths(); for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) - { fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); - } fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), info.cpu_count(), instance_details.num_snapshots()); @@ -80,6 +108,25 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) return fmt::to_string(buf); } +std::string generate_instance_info_report(const mp::InfoReply& reply) +{ + std::string output; + + if (reply.detailed_report().details_size() > 0) + { + if (reply.detailed_report().details()[0].has_instance_info()) + output = generate_instance_details(reply); + else + output = generate_snapshot_details(reply); + } + else + { + output = "\n"; + } + + return output; +} + std::string generate_snapshot_overview_report(const mp::InfoReply& reply) { fmt::memory_buffer buf; From 5f0be2f329b08fac2ed8a11a2e9c3d35e88e6783 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 07:21:03 -0700 Subject: [PATCH 368/627] [tests] add tests for snapshot details in csv format --- tests/test_output_formatter.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 37d348eddba..34ee9af0b90 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -1030,10 +1030,7 @@ const std::vector orderable_list_info_formatter_outputs{ "trusty-190611-1542,Running,,,Ubuntu N/A,\"\"\n", "csv_list_unsorted"}, - {&csv_formatter, &empty_info_reply, - "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " - "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\n", - "csv_info_empty"}, + {&csv_formatter, &empty_info_reply, "\n", "csv_info_empty"}, {&csv_formatter, &empty_snapshot_overview_reply, "Instance,Snapshot,Parent,Comment\n", "csv_snapshot_overview_empty"}, {&csv_formatter, &single_instance_info_reply, @@ -1044,6 +1041,20 @@ const std::vector orderable_list_info_formatter_outputs{ "0.15,1288490188,5153960756,60817408,1503238554,/home/user/foo => foo;/home/user/test_dir " "=> test_dir;,\"10.168.32.2,200.3.123.29\";,1,0\n", "csv_info_single_instance"}, + {&csv_formatter, &single_snapshot_info_reply, + "Snapshot,Instance,Size,CPU(s),Disk space,Memory " + "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,128MiB,2,4.9GiB,0.9GiB,/home/user/source " + "=> " + "source;/home/user => Home;,1972-01-01T10:00:20.021Z,snapshot1,\"snapshot3,snapshot4\";,\"This is a comment with " + "some\nnew\r\nlines.\"\n", + "csv_info_single_snapshot_info_reply"}, + {&csv_formatter, &multiple_snapshots_info_reply, + "Snapshot,Instance,Size,CPU(s),Disk space,Memory " + "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,,2,4.9GiB,0.9GiB,/home/user/source => " + "source;/home/user => " + "Home;,1972-01-01T10:00:20.021Z,snapshot1,\"snapshot3,snapshot4\";,\"\"\nblack-hole,messier-87,,1,1024GiB,128GiB,," + "2019-04-10T11:59:59Z,,\"\";,\"Captured by EHT\"\n", + "csv_info_multiple_snapshot_info_reply"}, {&csv_formatter, &multiple_instances_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nbogus-instance,Running,10.21.124.56,,Ubuntu 16.04.3 " From f37849ff8b1bcd2ba5acef60ebddf8c7f516240a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 21 Jul 2023 23:42:54 -0700 Subject: [PATCH 369/627] [tests] adapt inserting pet details to not mix instance and snapshot details for csv --- tests/test_output_formatter.cpp | 36 +++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 34ee9af0b90..6b82859dd4b 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -541,19 +541,25 @@ auto construct_multiple_snapshot_overview_info_reply() return info_reply; } -auto add_petenv_to_reply(mp::InfoReply& reply) +auto add_petenv_to_reply(mp::InfoReply& reply, bool csv_format, bool snapshots) { if (reply.has_detailed_report()) { - auto entry = reply.mutable_detailed_report()->add_details(); - entry->set_name(petenv_name()); - entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); - entry->mutable_instance_info()->set_image_release("18.10"); - entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); - - entry = reply.mutable_detailed_report()->add_details(); - entry->set_name(petenv_name()); - entry->mutable_snapshot_info()->mutable_fundamentals()->set_snapshot_name("snapshot1"); + if ((csv_format && !snapshots) || !csv_format) + { + auto entry = reply.mutable_detailed_report()->add_details(); + entry->set_name(petenv_name()); + entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); + entry->mutable_instance_info()->set_image_release("18.10"); + entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); + } + + if ((csv_format && snapshots) || !csv_format) + { + auto entry = reply.mutable_detailed_report()->add_details(); + entry->set_name(petenv_name()); + entry->mutable_snapshot_info()->mutable_fundamentals()->set_snapshot_name("snapshot1"); + } } else { @@ -2409,13 +2415,15 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) if (prepend) { - add_petenv_to_reply(reply_copy); + add_petenv_to_reply(reply_copy, dynamic_cast(formatter), + test_name.find("snapshot") != std::string::npos); reply_copy.MergeFrom(*input); } else { reply_copy.CopyFrom(*input); - add_petenv_to_reply(reply_copy); + add_petenv_to_reply(reply_copy, dynamic_cast(formatter), + test_name.find("snapshot") != std::string::npos); } output = formatter->format(reply_copy); @@ -2431,7 +2439,9 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) else if (dynamic_cast(formatter)) { if (input->has_detailed_report()) - regex = fmt::format("Name[[:print:]]*\n{},.*", petenv_name()); + regex = fmt::format("(Name[[:print:]]*\n{0},.*)|" + "(Snapshot[[:print:]]*\n[[:print:]]*,{0},.*)", + petenv_name()); else regex = fmt::format("Instance[[:print:]]*\n{},.*", petenv_name()); } From a96af7fe7921adb872c8f77f7a0dad39a9e1995e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 15 Sep 2023 12:14:10 -0500 Subject: [PATCH 370/627] [cli] enforce no mixing of instances and snapshots with csv formatter --- src/client/cli/cmd/info.cpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 34bb7d9143a..209feb5bee5 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -67,29 +67,42 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) "Retrieve from the daemon only the information obtained without running commands on the instance"); noRuntimeInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); QCommandLineOption formatOption( - "format", "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", - "format", "table"); + format_option_name, + "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", + format_option_name, "table"); QCommandLineOption snapshotOverviewOption("snapshot-overview", "Display info on snapshots"); parser->addOptions({all_option, noRuntimeInfoOption, formatOption, snapshotOverviewOption}); auto status = parser->commandParse(this); - if (status != ParseCode::Ok) - { return status; - } - auto parse_code = check_for_name_and_all_option_conflict(parser, cerr); - if (parse_code != ParseCode::Ok) - return parse_code; + status = handle_format_option(parser, &chosen_formatter, cerr); + + status = check_for_name_and_all_option_conflict(parser, cerr); + if (status != ParseCode::Ok) + return status; + bool instance_found = false, snapshot_found = false; for (const auto& item : add_instance_and_snapshot_names(parser)) + { + if (!item.has_snapshot_name()) + instance_found = true; + else + snapshot_found = true; + request.add_instances_snapshots()->CopyFrom(item); + } + request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); request.set_snapshot_overview(parser->isSet(snapshotOverviewOption)); - status = handle_format_option(parser, &chosen_formatter, cerr); + if (instance_found && snapshot_found && parser->value(format_option_name) == "csv") + { + cerr << "Mixed snapshot and instance arguments are not supported with CSV format\n"; + return ParseCode::CommandLineError; + } return status; } From 9e9d782eb517c8221170cbd12319e550ec899fde Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 15 Sep 2023 12:16:53 -0500 Subject: [PATCH 371/627] [json formatter] assert that keys are not overwritten when merging json objects --- src/client/cli/formatter/json_formatter.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index db6be42e3ce..e94cfadbd00 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -198,7 +198,10 @@ QJsonObject generate_instance_info_report(const mp::InfoReply& reply) { QJsonObject obj = instance_it.value().toObject(); for (const auto& key : instance_details.keys()) + { + assert(obj.find(key) == obj.end() && "key already exists; overwriting"); obj.insert(key, instance_details[key]); + } instance_it.value() = obj; } } @@ -211,11 +214,11 @@ QJsonObject generate_instance_info_report(const mp::InfoReply& reply) // Nothing for the instance so far, so create the "snapshots" node and put snapshot details there if (instance_it == info_obj.end()) { - QJsonObject obj, snapshot_obj; + QJsonObject instance_obj, snapshot_obj; snapshot_obj.insert(QString::fromStdString(info.snapshot_info().fundamentals().snapshot_name()), snapshot_details); - obj.insert("snapshots", snapshot_obj); - info_obj.insert(QString::fromStdString(info.name()), obj); + instance_obj.insert("snapshots", snapshot_obj); + info_obj.insert(QString::fromStdString(info.name()), instance_obj); } // Some instance details already exist else From 073ad746eaf4ecb4a64cc65e1a440e0438387080 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 15 Sep 2023 12:24:05 -0500 Subject: [PATCH 372/627] [csv] - factor out common code for printing mount info - remove optional argument size from being printed - an empty csv file is blank, not a newline - assimilate formatting for having multiple values in a cell - remove unnecessary assertion --- src/client/cli/formatter/csv_formatter.cpp | 42 ++++++++++++---------- tests/test_output_formatter.cpp | 22 ++++++------ 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 52108527729..afa54dc3308 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -46,29 +46,41 @@ std::string format_images(const google::protobuf::RepeatedPtrField {};", mount->source_path(), mount->target_path()); + fmt::format_to(std::back_inserter(buf), "{} => {}", mount->source_path(), mount->target_path()); + + return fmt::to_string(buf); +} + std::string generate_snapshot_details(const mp::InfoReply reply) { fmt::memory_buffer buf; fmt::format_to(std::back_inserter(buf), - "Snapshot,Instance,Size,CPU(s),Disk space,Memory size,Mounts,Created,Parent,Children,Comment\n"); + "Snapshot,Instance,CPU(s),Disk space,Memory size,Mounts,Created,Parent,Children,Comment\n"); for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) { - assert(info.has_snapshot_info() && - "outputting instance and snapshot details together is not supported in csv format"); const auto& fundamentals = info.snapshot_info().fundamentals(); - fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},{},", fundamentals.snapshot_name(), info.name(), - info.snapshot_info().size(), info.cpu_count(), info.disk_total(), info.memory_total()); + fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},", fundamentals.snapshot_name(), info.name(), + info.cpu_count(), info.disk_total(), info.memory_total()); - auto mount_paths = info.mount_info().mount_paths(); - for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) - fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); + fmt::format_to(std::back_inserter(buf), format_mounts(info.mount_info())); - fmt::format_to(std::back_inserter(buf), ",{},{},\"{}\";,\"{}\"\n", + fmt::format_to(std::back_inserter(buf), ",{},{},{},\"{}\"\n", google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp()), - fundamentals.parent(), fmt::join(info.snapshot_info().children(), ","), fundamentals.comment()); + fundamentals.parent(), fmt::join(info.snapshot_info().children(), ";"), fundamentals.comment()); } return fmt::to_string(buf); @@ -97,11 +109,9 @@ std::string generate_instance_details(const mp::InfoReply reply) instance_details.disk_usage(), info.disk_total(), instance_details.memory_usage(), info.memory_total()); - auto mount_paths = info.mount_info().mount_paths(); - for (auto mount = mount_paths.cbegin(); mount != mount_paths.cend(); ++mount) - fmt::format_to(std::back_inserter(buf), "{} => {};", mount->source_path(), mount->target_path()); + fmt::format_to(std::back_inserter(buf), format_mounts(info.mount_info())); - fmt::format_to(std::back_inserter(buf), ",\"{}\";,{},{}\n", fmt::join(instance_details.ipv4(), ","), + fmt::format_to(std::back_inserter(buf), ",{},{},{}\n", fmt::join(instance_details.ipv4(), ";"), info.cpu_count(), instance_details.num_snapshots()); } @@ -119,10 +129,6 @@ std::string generate_instance_info_report(const mp::InfoReply& reply) else output = generate_snapshot_details(reply); } - else - { - output = "\n"; - } return output; } diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 6b82859dd4b..5b11648e8ac 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -1036,7 +1036,7 @@ const std::vector orderable_list_info_formatter_outputs{ "trusty-190611-1542,Running,,,Ubuntu N/A,\"\"\n", "csv_list_unsorted"}, - {&csv_formatter, &empty_info_reply, "\n", "csv_info_empty"}, + {&csv_formatter, &empty_info_reply, "", "csv_info_empty"}, {&csv_formatter, &empty_snapshot_overview_reply, "Instance,Snapshot,Parent,Comment\n", "csv_snapshot_overview_empty"}, {&csv_formatter, &single_instance_info_reply, @@ -1045,29 +1045,29 @@ const std::vector orderable_list_info_formatter_outputs{ "16.04.3 " "LTS,1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac,16.04 LTS,0.45 0.51 " "0.15,1288490188,5153960756,60817408,1503238554,/home/user/foo => foo;/home/user/test_dir " - "=> test_dir;,\"10.168.32.2,200.3.123.29\";,1,0\n", + "=> test_dir,10.168.32.2;200.3.123.29,1,0\n", "csv_info_single_instance"}, {&csv_formatter, &single_snapshot_info_reply, - "Snapshot,Instance,Size,CPU(s),Disk space,Memory " - "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,128MiB,2,4.9GiB,0.9GiB,/home/user/source " + "Snapshot,Instance,CPU(s),Disk space,Memory " + "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,2,4.9GiB,0.9GiB,/home/user/source " "=> " - "source;/home/user => Home;,1972-01-01T10:00:20.021Z,snapshot1,\"snapshot3,snapshot4\";,\"This is a comment with " + "source;/home/user => Home,1972-01-01T10:00:20.021Z,snapshot1,snapshot3;snapshot4,\"This is a comment with " "some\nnew\r\nlines.\"\n", "csv_info_single_snapshot_info_reply"}, {&csv_formatter, &multiple_snapshots_info_reply, - "Snapshot,Instance,Size,CPU(s),Disk space,Memory " - "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,,2,4.9GiB,0.9GiB,/home/user/source => " + "Snapshot,Instance,CPU(s),Disk space,Memory " + "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,2,4.9GiB,0.9GiB,/home/user/source => " "source;/home/user => " - "Home;,1972-01-01T10:00:20.021Z,snapshot1,\"snapshot3,snapshot4\";,\"\"\nblack-hole,messier-87,,1,1024GiB,128GiB,," - "2019-04-10T11:59:59Z,,\"\";,\"Captured by EHT\"\n", + "Home,1972-01-01T10:00:20.021Z,snapshot1,snapshot3;snapshot4,\"\"\nblack-hole,messier-87,1,1024GiB,128GiB,," + "2019-04-10T11:59:59Z,,,\"Captured by EHT\"\n", "csv_info_multiple_snapshot_info_reply"}, {&csv_formatter, &multiple_instances_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nbogus-instance,Running,10.21.124.56,,Ubuntu 16.04.3 " "LTS,1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac,16.04 LTS,0.03 0.10 " "0.15,1932735284,6764573492,38797312,1610612736,/home/user/source => " - "source;,\"10.21.124.56\";,4,1\nbombastic,Stopped,,,," - "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,\"\";,,3\n", + "source,10.21.124.56,4,1\nbombastic,Stopped,,,," + "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,,,3\n", "csv_info_multiple_instances"}, {&csv_formatter, &single_snapshot_overview_info_reply, "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,This is a sample comment\n", "csv_snapshot_overview_single"}, From 944f70bcf7d1c43a983641d8436a6f58c4264349 Mon Sep 17 00:00:00 2001 From: ScottH <59572507+sharder996@users.noreply.github.com> Date: Tue, 19 Sep 2023 08:20:28 -0700 Subject: [PATCH 373/627] [json] edit assert statement Co-authored-by: Ricardo Abreu <6698114+ricab@users.noreply.github.com> Signed-off-by: ScottH <59572507+sharder996@users.noreply.github.com> --- src/client/cli/formatter/json_formatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index e94cfadbd00..2f2acb1d9fe 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -199,7 +199,7 @@ QJsonObject generate_instance_info_report(const mp::InfoReply& reply) QJsonObject obj = instance_it.value().toObject(); for (const auto& key : instance_details.keys()) { - assert(obj.find(key) == obj.end() && "key already exists; overwriting"); + assert(obj.find(key) == obj.end() && "key already exists"); obj.insert(key, instance_details[key]); } instance_it.value() = obj; From 0084b61eb2c2738f354e43864f9c18136bea9dca Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 16:31:36 +0100 Subject: [PATCH 374/627] [vm] Allow restoring snapshots with changed sizes --- src/platform/backends/shared/base_virtual_machine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1a808d88e34..a33464dbf95 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -520,8 +520,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s auto snapshot = get_snapshot(name); - // TODO@snapshots convert into runtime_errors (persisted info could have been tampered with) - assert(specs.disk_space == snapshot->get_disk_space() && "resizing VMs with snapshots isn't yet supported"); + // TODO@snapshots convert into runtime_error (persisted info could have been tampered with) assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); snapshot->apply(); @@ -532,6 +531,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s specs.state = snapshot->get_state(); specs.num_cores = snapshot->get_num_cores(); specs.mem_size = snapshot->get_mem_size(); + specs.disk_space = snapshot->get_disk_space(); specs.mounts = snapshot->get_mounts(); specs.metadata = snapshot->get_metadata(); From e40c6f4363448fd2e7bb60dfdcf14a8934b8da00 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 18:36:13 +0100 Subject: [PATCH 375/627] [qemu] Move qemu-img helper to shared utils --- src/platform/backends/qemu/qemu_snapshot.cpp | 19 +++---------------- .../shared/qemu_img_utils/qemu_img_utils.cpp | 13 +++++++++++++ .../shared/qemu_img_utils/qemu_img_utils.h | 2 ++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index aa23f0f26fa..abe2a3c27c8 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -29,7 +29,6 @@ #include namespace mp = multipass; -namespace mpp = mp::platform; namespace { @@ -50,18 +49,6 @@ std::unique_ptr make_delete_spec(const QString& tag, con return std::make_unique(QStringList{"snapshot", "-d", tag, image_path}, /* src_img = */ "", image_path); } - -void checked_exec_qemu_img(std::unique_ptr spec) -{ - auto process = mpp::make_process(std::move(spec)); - - auto process_state = process->execute(); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), process->read_all_standard_error())); - } -} } // namespace mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, @@ -85,12 +72,12 @@ void mp::QemuSnapshot::capture_impl() throw std::runtime_error{fmt::format( "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; - checked_exec_qemu_img(make_capture_spec(tag, image_path)); + mp::backend::checked_exec_qemu_img(make_capture_spec(tag, image_path)); } void mp::QemuSnapshot::erase_impl() { - checked_exec_qemu_img(make_delete_spec(derive_id(), image_path)); + mp::backend::checked_exec_qemu_img(make_delete_spec(derive_id(), image_path)); } void mp::QemuSnapshot::apply_impl() @@ -102,6 +89,6 @@ void mp::QemuSnapshot::apply_impl() desc.mem_size = get_mem_size(); desc.disk_space = get_disk_space(); - checked_exec_qemu_img(make_restore_spec(derive_id(), image_path)); + mp::backend::checked_exec_qemu_img(make_restore_spec(derive_id(), image_path)); rollback.dismiss(); } diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 8de556c9a04..6d7ee4a95dc 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -30,6 +30,19 @@ #include namespace mp = multipass; +namespace mpp = mp::platform; + +void mp::backend::checked_exec_qemu_img(std::unique_ptr spec) +{ + auto process = mpp::make_process(std::move(spec)); + + auto process_state = process->execute(); + if (!process_state.completed_successfully()) + { + throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", + process_state.failure_message(), process->read_all_standard_error())); + } +} void mp::backend::resize_instance_image(const MemorySize& disk_space, const mp::Path& image_path) { diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index fc5c42bf839..960d2924b82 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -23,9 +23,11 @@ namespace multipass { class MemorySize; +class QemuImgProcessSpec; namespace backend { +void checked_exec_qemu_img(std::unique_ptr spec); void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); bool instance_image_has_snapshot(const Path& image_path, QString snapshot_tag); From 536fc9ec78a7424daac6e4620a40f0cf40ff3d1f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 18:53:16 +0100 Subject: [PATCH 376/627] [qemu] Adapt and reuse qemu-img helper Return the `qemu-img` process from the `checked_exec_qemu_img` and reuse the utility when looking for snapshots. --- .../shared/qemu_img_utils/qemu_img_utils.cpp | 13 ++++--------- .../backends/shared/qemu_img_utils/qemu_img_utils.h | 3 ++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 6d7ee4a95dc..a37cc3bce9a 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -32,7 +32,7 @@ namespace mp = multipass; namespace mpp = mp::platform; -void mp::backend::checked_exec_qemu_img(std::unique_ptr spec) +auto mp::backend::checked_exec_qemu_img(std::unique_ptr spec) -> Process::UPtr { auto process = mpp::make_process(std::move(spec)); @@ -42,6 +42,8 @@ void mp::backend::checked_exec_qemu_img(std::unique_ptr throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", process_state.failure_message(), process->read_all_standard_error())); } + + return process; } void mp::backend::resize_instance_image(const MemorySize& disk_space, const mp::Path& image_path) @@ -104,16 +106,9 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, QString snapshot_tag) { - auto process = mp::platform::make_process( + auto process = checked_exec_qemu_img( std::make_unique(QStringList{"snapshot", "-l", image_path}, image_path)); - auto process_state = process->execute(); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), process->read_all_standard_error())); - } - QRegularExpression regex{snapshot_tag.append(R"(\s)")}; return QString{process->read_all_standard_output()}.contains(regex); } diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index 960d2924b82..8079c9ebe64 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -19,6 +19,7 @@ #define MULTIPASS_QEMU_IMG_UTILS_H #include +#include namespace multipass { @@ -27,7 +28,7 @@ class QemuImgProcessSpec; namespace backend { -void checked_exec_qemu_img(std::unique_ptr spec); +Process::UPtr checked_exec_qemu_img(std::unique_ptr spec); void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); bool instance_image_has_snapshot(const Path& image_path, QString snapshot_tag); From 5b6fa378de9ad7d6b685be77e560c48afa474193 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 19:11:44 +0100 Subject: [PATCH 377/627] [qemu] Further adaptations for reuse of helper --- .../shared/qemu_img_utils/qemu_img_utils.cpp | 17 ++++++----------- .../shared/qemu_img_utils/qemu_img_utils.h | 3 ++- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index a37cc3bce9a..60591a777d3 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -32,14 +32,15 @@ namespace mp = multipass; namespace mpp = mp::platform; -auto mp::backend::checked_exec_qemu_img(std::unique_ptr spec) -> Process::UPtr +auto mp::backend::checked_exec_qemu_img(std::unique_ptr spec, + const std::string& custom_error_prefix) -> Process::UPtr { auto process = mpp::make_process(std::move(spec)); auto process_state = process->execute(); if (!process_state.completed_successfully()) { - throw std::runtime_error(fmt::format("Internal error: qemu-img failed ({}) with output:\n{}", + throw std::runtime_error(fmt::format("{}: qemu-img failed ({}) with output:\n{}", custom_error_prefix, process_state.failure_message(), process->read_all_standard_error())); } @@ -50,16 +51,10 @@ void mp::backend::resize_instance_image(const MemorySize& disk_space, const mp:: { auto disk_size = QString::number(disk_space.in_bytes()); // format documented in `man qemu-img` (look for "size") QStringList qemuimg_parameters{{"resize", image_path, disk_size}}; - auto qemuimg_process = - mp::platform::make_process(std::make_unique(qemuimg_parameters, "", image_path)); - auto process_state = qemuimg_process->execute(mp::image_resize_timeout); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Cannot resize instance image: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), - qemuimg_process->read_all_standard_error())); - } + // TODO@ricab pass in custom timeout: mp::image_resize_timeout + checked_exec_qemu_img(std::make_unique(qemuimg_parameters, "", image_path), + "Cannot resize instance image"); } mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index 8079c9ebe64..5b95fd071e9 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -28,7 +28,8 @@ class QemuImgProcessSpec; namespace backend { -Process::UPtr checked_exec_qemu_img(std::unique_ptr spec); +Process::UPtr checked_exec_qemu_img(std::unique_ptr spec, + const std::string& custom_error_prefix = "Internal error"); void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); bool instance_image_has_snapshot(const Path& image_path, QString snapshot_tag); From ed5878ccb9d62a1c03770be1add713b3f50189eb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 19:17:19 +0100 Subject: [PATCH 378/627] [qemu] Re-instate custom timeout for img resizing --- .../backends/shared/qemu_img_utils/qemu_img_utils.cpp | 8 ++++---- .../backends/shared/qemu_img_utils/qemu_img_utils.h | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 60591a777d3..ec60e2bd45d 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -33,11 +33,12 @@ namespace mp = multipass; namespace mpp = mp::platform; auto mp::backend::checked_exec_qemu_img(std::unique_ptr spec, - const std::string& custom_error_prefix) -> Process::UPtr + const std::string& custom_error_prefix, std::optional timeout) + -> Process::UPtr { auto process = mpp::make_process(std::move(spec)); - auto process_state = process->execute(); + auto process_state = timeout ? process->execute(*timeout) : process->execute(); if (!process_state.completed_successfully()) { throw std::runtime_error(fmt::format("{}: qemu-img failed ({}) with output:\n{}", custom_error_prefix, @@ -52,9 +53,8 @@ void mp::backend::resize_instance_image(const MemorySize& disk_space, const mp:: auto disk_size = QString::number(disk_space.in_bytes()); // format documented in `man qemu-img` (look for "size") QStringList qemuimg_parameters{{"resize", image_path, disk_size}}; - // TODO@ricab pass in custom timeout: mp::image_resize_timeout checked_exec_qemu_img(std::make_unique(qemuimg_parameters, "", image_path), - "Cannot resize instance image"); + "Cannot resize instance image", mp::image_resize_timeout); } mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index 5b95fd071e9..bdf8f095ad0 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -21,6 +21,8 @@ #include #include +#include + namespace multipass { class MemorySize; @@ -29,7 +31,8 @@ class QemuImgProcessSpec; namespace backend { Process::UPtr checked_exec_qemu_img(std::unique_ptr spec, - const std::string& custom_error_prefix = "Internal error"); + const std::string& custom_error_prefix = "Internal error", + std::optional timeout = std::nullopt); void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); bool instance_image_has_snapshot(const Path& image_path, QString snapshot_tag); From ecaffdd26fc44431c67c8c08b3f4c2d7a3d6520d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 19:23:39 +0100 Subject: [PATCH 379/627] [qemu] Reuse qemu-img helper in convertion to qcow --- .../shared/qemu_img_utils/qemu_img_utils.cpp | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index ec60e2bd45d..4a979c56cd2 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -63,17 +63,9 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) // TODO: we could support converting from other the image formats that qemu-img can deal with const auto qcow2_path{image_path + ".qcow2"}; - auto qemuimg_info_spec = - std::make_unique(QStringList{"info", "--output=json", image_path}, image_path); - auto qemuimg_info_process = mp::platform::make_process(std::move(qemuimg_info_spec)); - - auto process_state = qemuimg_info_process->execute(); - if (!process_state.completed_successfully()) - { - throw std::runtime_error(fmt::format("Cannot read image format: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), - qemuimg_info_process->read_all_standard_error())); - } + auto qemuimg_info_process = checked_exec_qemu_img( + std::make_unique(QStringList{"info", "--output=json", image_path}, image_path), + "Cannot read image format"); auto image_info = qemuimg_info_process->read_all_standard_output(); auto image_record = QJsonDocument::fromJson(QString(image_info).toUtf8(), nullptr).object(); @@ -82,15 +74,8 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) { auto qemuimg_convert_spec = std::make_unique( QStringList{"convert", "-p", "-O", "qcow2", image_path, qcow2_path}, image_path, qcow2_path); - auto qemuimg_convert_process = mp::platform::make_process(std::move(qemuimg_convert_spec)); - process_state = qemuimg_convert_process->execute(mp::image_resize_timeout); - - if (!process_state.completed_successfully()) - { - throw std::runtime_error( - fmt::format("Failed to convert image format: qemu-img failed ({}) with output:\n{}", - process_state.failure_message(), qemuimg_convert_process->read_all_standard_error())); - } + auto qemuimg_convert_process = + checked_exec_qemu_img(std::move(qemuimg_convert_spec), "Failed to convert image format"); return qcow2_path; } else From 74eb2e0fa402137c2591d52a624db032ba09c921 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 15 Sep 2023 22:46:47 +0100 Subject: [PATCH 380/627] [qemu] Convert images to qcow2 v3 Convert images to qcow2 v3, AKA compat=1.1, so that disk resizing is supported on images with snapshots. Convert when preparing the source image and, temporarily, when constructing QEMU VMs, to cover existing instances too. This later conversion can be dropped in a while. --- src/platform/backends/qemu/qemu_virtual_machine.cpp | 3 +++ src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 1 + .../backends/shared/qemu_img_utils/qemu_img_utils.cpp | 6 ++++++ .../backends/shared/qemu_img_utils/qemu_img_utils.h | 1 + src/process/qemuimg_process_spec.cpp | 2 +- 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index a3d3e93de8b..d1ce7aced9c 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -208,6 +208,9 @@ mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc monitor{&monitor}, mount_args{mount_args_from_json(monitor.retrieve_metadata_for(vm_name))} { + // convert existing VMs to v3 too (doesn't affect images that are already v3) + mp::backend::amend_to_qcow2_v3(desc.image.image_path); // TODO drop in a couple of releases (going in on v1.13) + QObject::connect( this, &QemuVirtualMachine::on_delete_memory_snapshot, this, [this] { diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 8f5d9c2b937..cbcffe376e9 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -56,6 +56,7 @@ mp::VMImage mp::QemuVirtualMachineFactory::prepare_source_image(const mp::VMImag { VMImage image{source_image}; image.image_path = mp::backend::convert_to_qcow_if_necessary(source_image.image_path); + mp::backend::amend_to_qcow2_v3(image.image_path); return image; } diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 4a979c56cd2..189fa45a727 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -84,6 +84,12 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) } } +void mp::backend::amend_to_qcow2_v3(const multipass::Path& image_path) +{ + checked_exec_qemu_img( + std::make_unique(QStringList{"amend", "-o", "compat=1.1", image_path}, image_path)); +} + bool mp::backend::instance_image_has_snapshot(const mp::Path& image_path, QString snapshot_tag) { auto process = checked_exec_qemu_img( diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h index bdf8f095ad0..6d7b89e0b98 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.h @@ -35,6 +35,7 @@ Process::UPtr checked_exec_qemu_img(std::unique_ptr spec, std::optional timeout = std::nullopt); void resize_instance_image(const MemorySize& disk_space, const multipass::Path& image_path); Path convert_to_qcow_if_necessary(const Path& image_path); +void amend_to_qcow2_v3(const Path& image_path); bool instance_image_has_snapshot(const Path& image_path, QString snapshot_tag); } // namespace backend diff --git a/src/process/qemuimg_process_spec.cpp b/src/process/qemuimg_process_spec.cpp index 03ae5757e48..69c28fa8996 100644 --- a/src/process/qemuimg_process_spec.cpp +++ b/src/process/qemuimg_process_spec.cpp @@ -83,7 +83,7 @@ profile %1 flags=(attach_disconnected) { } if (!source_image.isEmpty()) - images.append(QString(" %1 rk,\n").arg(source_image)); + images.append(QString(" %1 rwk,\n").arg(source_image)); // allow amending to qcow2 v3 if (!target_image.isEmpty()) images.append(QString(" %1 rwk,\n").arg(target_image)); From c84bb13112f9d1a306caa8f549f1f3e837d9d98e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 18 Sep 2023 10:53:54 +0100 Subject: [PATCH 381/627] [tests] Adapt qemu-img tests to AppArmour change --- tests/test_qemuimg_process_spec.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_qemuimg_process_spec.cpp b/tests/test_qemuimg_process_spec.cpp index 4a6f5ed95ae..2527f7b8a06 100644 --- a/tests/test_qemuimg_process_spec.cpp +++ b/tests/test_qemuimg_process_spec.cpp @@ -75,7 +75,7 @@ TEST(TestQemuImgProcessSpec, apparmor_profile_running_as_snap_correct) mp::QemuImgProcessSpec spec({}, source_image); EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1/usr/bin/qemu-img ixr,").arg(snap_dir.path()))); - EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rk,").arg(source_image))); + EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rwk,").arg(source_image))); } TEST(TestQemuImgProcessSpec, apparmor_profile_running_as_snap_with_target_correct) @@ -89,7 +89,7 @@ TEST(TestQemuImgProcessSpec, apparmor_profile_running_as_snap_with_target_correc mp::QemuImgProcessSpec spec({}, source_image, target_image); EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1/usr/bin/qemu-img ixr,").arg(snap_dir.path()))); - EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rk,").arg(source_image))); + EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rwk,").arg(source_image))); EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rwk,").arg(target_image))); } @@ -125,7 +125,7 @@ TEST(TestQemuImgProcessSpec, mp::QemuImgProcessSpec spec({}, source_image); EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1/usr/bin/qemu-img ixr,").arg(snap_dir.path()))); - EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rk,").arg(source_image))); + EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1 rwk,").arg(source_image))); } TEST(TestQemuImgProcessSpec, apparmor_profile_not_running_as_snap_correct) From 58010affe4d0efeb6e0a840510d753ac21c36a7b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 31 May 2023 21:10:29 -0700 Subject: [PATCH 382/627] [image-vault] drive by fix pass by value Signed-off-by: sharder996 --- src/daemon/default_vm_image_vault.cpp | 3 ++- src/daemon/default_vm_image_vault.h | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 85be7bf1836..575ce78289b 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -221,7 +221,8 @@ mp::MemorySize get_image_size(const mp::Path& image_path) } // namespace mp::DefaultVMImageVault::DefaultVMImageVault(std::vector image_hosts, URLDownloader* downloader, - mp::Path cache_dir_path, mp::Path data_dir_path, mp::days days_to_expire) + const mp::Path& cache_dir_path, const mp::Path& data_dir_path, + const mp::days& days_to_expire) : BaseVMImageVault{image_hosts}, url_downloader{downloader}, cache_dir{QDir(cache_dir_path).filePath("vault")}, diff --git a/src/daemon/default_vm_image_vault.h b/src/daemon/default_vm_image_vault.h index 5a008873705..77d9828003a 100644 --- a/src/daemon/default_vm_image_vault.h +++ b/src/daemon/default_vm_image_vault.h @@ -45,8 +45,9 @@ class VaultRecord class DefaultVMImageVault final : public BaseVMImageVault { public: - DefaultVMImageVault(std::vector image_host, URLDownloader* downloader, multipass::Path cache_dir_path, - multipass::Path data_dir_path, multipass::days days_to_expire); + DefaultVMImageVault(std::vector image_host, URLDownloader* downloader, + const multipass::Path& cache_dir_path, const multipass::Path& data_dir_path, + const multipass::days& days_to_expire); ~DefaultVMImageVault(); VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, From 2456cb9b9a9b01755ef2bdc9cc00295de9fc1b9e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 31 May 2023 21:12:17 -0700 Subject: [PATCH 383/627] [image-vault] drive by move functions in unnamed namespace together Signed-off-by: sharder996 --- src/daemon/default_vm_image_vault.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 575ce78289b..2a418ffa92d 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -218,6 +218,18 @@ mp::MemorySize get_image_size(const mp::Path& image_path) return image_size; } + +template +void persist_records(const T& records, const QString& path) +{ + QJsonObject json_records; + for (const auto& record : records) + { + auto key = QString::fromStdString(record.first); + json_records.insert(key, record_to_json(record.second)); + } + mp::write_json(json_records, path); +} } // namespace mp::DefaultVMImageVault::DefaultVMImageVault(std::vector image_hosts, URLDownloader* downloader, @@ -680,21 +692,6 @@ mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, return vm_image; } -namespace -{ -template -void persist_records(const T& records, const QString& path) -{ - QJsonObject json_records; - for (const auto& record : records) - { - auto key = QString::fromStdString(record.first); - json_records.insert(key, record_to_json(record.second)); - } - mp::write_json(json_records, path); -} -} // namespace - void mp::DefaultVMImageVault::persist_instance_records() { persist_records(instance_image_records, data_dir.filePath(instance_db_name)); From bcb82d932652d837fe186a8975630c5d5617f8ed Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 14:38:23 -0700 Subject: [PATCH 384/627] [vm] have vms be aware of where they are stored Signed-off-by: sharder996 --- include/multipass/virtual_machine.h | 9 +- .../libvirt/libvirt_virtual_machine.cpp | 5 +- .../libvirt/libvirt_virtual_machine.h | 3 +- .../backends/lxd/lxd_virtual_machine.cpp | 5 +- .../backends/lxd/lxd_virtual_machine.h | 3 +- .../backends/qemu/qemu_virtual_machine.cpp | 4 +- .../backends/qemu/qemu_virtual_machine.h | 5 +- .../backends/shared/base_virtual_machine.cpp | 7 +- .../backends/shared/base_virtual_machine.h | 6 +- tests/lxd/test_lxd_backend.cpp | 116 ++++++++++-------- tests/qemu/test_qemu_backend.cpp | 9 +- 11 files changed, 105 insertions(+), 67 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index c30c268ab25..3d4f0e94ba6 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -20,6 +20,7 @@ #include "disabled_copy_move.h" #include "ip_address.h" +#include "path.h" #include #include @@ -100,14 +101,18 @@ class VirtualMachine : private DisabledCopyMove VirtualMachine::State state; const std::string vm_name; + const QDir instance_dir; std::condition_variable state_wait; std::mutex state_mutex; std::optional management_ip; bool shutdown_while_starting{false}; protected: - VirtualMachine(VirtualMachine::State state, const std::string& vm_name) : state{state}, vm_name{vm_name} {}; - VirtualMachine(const std::string& vm_name) : VirtualMachine(State::off, vm_name){}; + VirtualMachine(VirtualMachine::State state, const std::string& vm_name, const Path& instance_dir) + : state{state}, vm_name{vm_name}, instance_dir{QDir{instance_dir}} {}; + VirtualMachine(const std::string& vm_name, const Path& instance_dir) + : VirtualMachine(State::off, vm_name, instance_dir){}; + VirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name, ""){}; }; } // namespace multipass #endif // MULTIPASS_VIRTUAL_MACHINE_H diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 33f78ea20b9..545e06c0830 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -275,8 +275,9 @@ std::string management_ipv4_impl(std::optional& management_ip, mp::LibVirtVirtualMachine::LibVirtVirtualMachine(const mp::VirtualMachineDescription& desc, const std::string& bridge_name, mp::VMStatusMonitor& monitor, - const mp::LibvirtWrapper::UPtr& libvirt_wrapper) - : BaseVirtualMachine{desc.vm_name}, + const mp::LibvirtWrapper::UPtr& libvirt_wrapper, + const mp::Path& instance_dir) + : BaseVirtualMachine{desc.vm_name, 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 d10580a14e2..4d0ccf4920b 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -36,7 +36,8 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine using NetworkUPtr = std::unique_ptr; LibVirtVirtualMachine(const VirtualMachineDescription& desc, const std::string& bridge_name, - VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper); + VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper, + const mp::Path& instance_dir); ~LibVirtVirtualMachine(); void start() override; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index d0e16c4f5f8..c19fe5a0c06 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -168,8 +168,9 @@ bool uses_default_id_mappings(const multipass::VMMount& mount) mp::LXDVirtualMachine::LXDVirtualMachine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor, NetworkAccessManager* manager, const QUrl& base_url, - const QString& bridge_name, const QString& storage_pool) - : BaseVirtualMachine{desc.vm_name}, + const QString& bridge_name, const QString& storage_pool, + const mp::Path& instance_dir) + : BaseVirtualMachine{desc.vm_name, 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 f02da3fd539..c956cf6dc1d 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -34,7 +34,8 @@ class LXDVirtualMachine : public BaseVirtualMachine { public: LXDVirtualMachine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor, NetworkAccessManager* manager, - const QUrl& base_url, const QString& bridge_name, const QString& storage_pool); + const QUrl& base_url, const QString& bridge_name, const QString& storage_pool, + const mp::Path& instance_dir); ~LXDVirtualMachine() override; void stop() override; void start() override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index a3d3e93de8b..caa525bc276 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -197,10 +197,10 @@ auto generate_metadata(const QStringList& platform_args, const QStringList& proc } // namespace mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, - VMStatusMonitor& monitor) + VMStatusMonitor& monitor, const mp::Path& instance_dir) : BaseVirtualMachine{mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag) ? State::suspended : State::off, - desc.vm_name}, + desc.vm_name, instance_dir}, desc{desc}, mac_addr{desc.default_mac_address}, username{desc.ssh_username}, diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index ce2af41c5fd..631c395d6d6 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -41,7 +41,8 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine public: using MountArgs = std::unordered_map>; - QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, VMStatusMonitor& monitor); + QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, VMStatusMonitor& monitor, + const Path& instance_dir); ~QemuVirtualMachine(); void start() override; @@ -69,7 +70,7 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine void on_reset_network(); protected: - QemuVirtualMachine(const std::string& name) : BaseVirtualMachine{name} + QemuVirtualMachine(const std::string& name) : BaseVirtualMachine{name, ""} { } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 1a808d88e34..4ddfb1d1a3f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -98,7 +98,12 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted namespace multipass { -BaseVirtualMachine::BaseVirtualMachine(St state, const std::string& vm_name) : VirtualMachine(state, vm_name){}; +BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, + const mp::Path& instance_dir) + : VirtualMachine(state, vm_name, instance_dir){}; + +BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name, const mp::Path& instance_dir) + : VirtualMachine(vm_name, instance_dir){}; BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 85a487994b3..294d0ffd768 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -43,8 +43,8 @@ namespace multipass class BaseVirtualMachine : public VirtualMachine { public: - BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name); - BaseVirtualMachine(const std::string& vm_name); + BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, const mp::Path& instance_dir); + BaseVirtualMachine(const std::string& vm_name, const mp::Path& instance_dir); std::vector get_all_ipv4(const SSHKeyProvider& key_provider) override; std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, @@ -72,6 +72,8 @@ class BaseVirtualMachine : public VirtualMachine std::vector get_childrens_names(const Snapshot* parent) const override; protected: + BaseVirtualMachine(const std::string& vm_name); + virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, diff --git a/tests/lxd/test_lxd_backend.cpp b/tests/lxd/test_lxd_backend.cpp index 4cf7ac838b9..61b525d9489 100644 --- a/tests/lxd/test_lxd_backend.cpp +++ b/tests/lxd/test_lxd_backend.cpp @@ -81,6 +81,7 @@ struct LXDBackend : public Test mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); mpt::TempDir data_dir; + mpt::TempDir instance_dir; std::unique_ptr> mock_network_access_manager; QUrl base_url{"unix:///foo@1.0"}; const QString default_storage_pool{"default"}; @@ -445,8 +446,9 @@ TEST_F(LXDBackend, creates_in_stopped_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_TRUE(vm_created); EXPECT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -493,8 +495,9 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_start) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); machine.start(); @@ -543,8 +546,9 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_shutdown) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); machine.shutdown(); @@ -589,8 +593,8 @@ TEST_F(LXDBackend, machine_persists_internal_stopped_state_on_destruction) { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; ASSERT_EQ(machine.state, mp::VirtualMachine::State::running); } // Simulate multipass exiting by having the vm destruct @@ -646,8 +650,8 @@ TEST_F(LXDBackend, machine_does_not_update_state_in_dtor) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } EXPECT_TRUE(vm_shutdown); @@ -704,8 +708,8 @@ TEST_F(LXDBackend, machineLogsNotFoundExceptionInDtor) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.shutdown(); } @@ -754,8 +758,8 @@ TEST_F(LXDBackend, does_not_call_stop_when_snap_refresh_is_detected) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } EXPECT_FALSE(stop_requested); @@ -800,8 +804,8 @@ TEST_F(LXDBackend, calls_stop_when_snap_refresh_does_not_exist) // create in its own scope so the dtor is called { mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } EXPECT_TRUE(stop_requested); @@ -884,8 +888,9 @@ TEST_F(LXDBackend, posts_expected_data_when_creating_instance) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } TEST_F(LXDBackend, prepare_source_image_does_not_modify) @@ -1097,8 +1102,9 @@ TEST_P(LXDNetworkInfoSuite, returns_expected_network_info) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(mpt::StubSSHKeyProvider()), "10.217.27.168"); EXPECT_TRUE(machine.ipv6().empty()); @@ -1142,8 +1148,9 @@ TEST_F(LXDBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_THROW(machine.ssh_hostname(std::chrono::milliseconds(1)), std::runtime_error); EXPECT_EQ(machine.state, mp::VirtualMachine::State::unknown); @@ -1180,8 +1187,9 @@ TEST_F(LXDBackend, no_ip_address_returns_unknown) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(mpt::StubSSHKeyProvider()), "UNKNOWN"); } @@ -1523,8 +1531,9 @@ TEST_F(LXDBackend, unsupported_suspend_throws) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; MP_EXPECT_THROW_THAT(machine.suspend(), std::runtime_error, mpt::match_what(StrEq("suspend is currently not supported"))); @@ -1556,8 +1565,9 @@ TEST_F(LXDBackend, start_while_frozen_unfreezes) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::info), mpt::MockLogger::make_cstring_matcher(StrEq("pied-piper-valley")), @@ -1584,8 +1594,9 @@ TEST_F(LXDBackend, shutdown_while_stopped_does_nothing_and_logs_debug) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -1616,8 +1627,9 @@ TEST_F(LXDBackend, shutdown_while_frozen_does_nothing_and_logs_info) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, mock_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::suspended); @@ -1667,8 +1679,9 @@ TEST_F(LXDBackend, ensure_vm_running_does_not_throw_starting) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1720,8 +1733,9 @@ TEST_F(LXDBackend, shutdown_while_starting_throws_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1773,8 +1787,9 @@ TEST_F(LXDBackend, start_failure_while_starting_throws_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1827,8 +1842,9 @@ TEST_F(LXDBackend, reboots_while_starting_does_not_throw_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; machine.start(); @@ -1850,8 +1866,9 @@ TEST_F(LXDBackend, current_state_connection_error_logs_warning_and_sets_unknown_ throw mp::LocalSocketConnectionException(exception_message); }); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::warning), mpt::MockLogger::make_cstring_matcher(StrEq("pied-piper-valley")), @@ -1904,8 +1921,9 @@ TEST_P(LXDInstanceStatusTestSuite, lxd_state_returns_expected_VirtualMachine_sta } } - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; EXPECT_EQ(machine.current_state(), expected_state); } @@ -2136,8 +2154,9 @@ TEST_F(LXDBackend, posts_extra_network_devices) auto json_matcher = ResultOf(&extract_devices, devices_matcher); setup_vm_creation_expectations(*mock_network_access_manager, request_data_matcher(json_matcher)); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } TEST_F(LXDBackend, posts_network_data_config_if_available) @@ -2154,8 +2173,9 @@ TEST_F(LXDBackend, posts_network_data_config_if_available) setup_vm_creation_expectations(*mock_network_access_manager, request_data_matcher(json_matcher)); - mp::LXDVirtualMachine machine{default_description, stub_monitor, mock_network_access_manager.get(), base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine machine{ + default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, + default_storage_pool, instance_dir.path()}; } namespace diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index db6508d67c5..7383fb63098 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -80,6 +80,7 @@ struct QemuBackend : public mpt::TestWithMockedBinPath {}, {}}; mpt::TempDir data_dir; + mpt::TempDir instance_dir; const std::string tap_device{"tapfoo"}; const QString bridge_name{"dummy-bridge"}; const std::string subnet{"192.168.64"}; @@ -623,7 +624,7 @@ TEST_F(QemuBackend, ssh_hostname_returns_expected_value) return std::optional{expected_ip}; }); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -638,7 +639,7 @@ TEST_F(QemuBackend, gets_management_ip) EXPECT_CALL(mock_qemu_platform, get_ip_for(_)).WillOnce(Return(expected_ip)); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -652,7 +653,7 @@ TEST_F(QemuBackend, fails_to_get_management_ip_if_dnsmasq_does_not_return_an_ip) EXPECT_CALL(mock_qemu_platform, get_ip_for(_)).WillOnce(Return(std::nullopt)); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; @@ -666,7 +667,7 @@ TEST_F(QemuBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) ON_CALL(mock_qemu_platform, get_ip_for(_)).WillByDefault([](auto...) { return std::nullopt; }); - mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor}; + mp::QemuVirtualMachine machine{default_description, &mock_qemu_platform, stub_monitor, instance_dir.path()}; machine.start(); machine.state = mp::VirtualMachine::State::running; From b7f64e6b5dd9c09c1df329ebfc2ed71b5dec0f86 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 14:48:24 -0700 Subject: [PATCH 385/627] [vm] reference internally stored instance directory location Signed-off-by: sharder996 --- include/multipass/virtual_machine.h | 10 ++-- src/daemon/daemon.cpp | 17 ++----- .../backends/shared/base_virtual_machine.cpp | 46 +++++++++---------- .../backends/shared/base_virtual_machine.h | 18 ++++---- tests/mock_virtual_machine.h | 8 ++-- tests/stub_virtual_machine.h | 9 ++-- 6 files changed, 50 insertions(+), 58 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 3d4f0e94ba6..b547344241f 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -92,11 +92,11 @@ class VirtualMachine : private DisabledCopyMove virtual int get_num_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr get_snapshot(const std::string& name) = 0; - virtual std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, - const std::string& name, const std::string& comment) = 0; - virtual void delete_snapshot(const QDir& snapshot_dir, const std::string& name) = 0; - virtual void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) = 0; - virtual void load_snapshots(const QDir& snapshot_dir) = 0; + virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) = 0; + virtual void delete_snapshot(const std::string& name) = 0; + virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; + virtual void load_snapshots() = 0; virtual std::vector get_childrens_names(const Snapshot* parent) const = 0; VirtualMachine::State state; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9cdbf1de450..3a6525892f6 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -447,11 +447,6 @@ auto fetch_image_for(const std::string& name, const mp::FetchType& fetch_type, m return vault.fetch_image(fetch_type, query, stub_prepare, stub_progress, false, std::nullopt); } -QDir instance_directory(const std::string& instance_name, const mp::DaemonConfig& config) -{ // TODO should we establish a more direct way to get to the instance's directory? - return mp::utils::base_dir(fetch_image_for(instance_name, config.factory->fetch_type(), *config.vault).image_path); -} - auto try_mem_size(const std::string& val) -> std::optional { try @@ -1410,7 +1405,7 @@ mp::Daemon::Daemon(std::unique_ptr the_config) auto& instance_record = spec.deleted ? deleted_instances : operative_instances; auto instance = instance_record[name] = config->factory->create_virtual_machine(vm_desc, *this); - instance->load_snapshots(instance_directory(name, *config)); + instance->load_snapshots(); allocated_mac_addrs = std::move(new_macs); // Add the new macs to the daemon's list only if we got this far @@ -2267,7 +2262,7 @@ try // clang-format on assert(purge && "precondition: snapshots can only be purged"); for (const auto& snapshot_name : pick) - vm_it->second->delete_snapshot(instance_directory(instance_name, *config), snapshot_name); + vm_it->second->delete_snapshot(snapshot_name); } } } @@ -2502,8 +2497,7 @@ try SnapshotReply reply; { - const auto snapshot = vm_ptr->take_snapshot(instance_directory(instance_name, *config), spec_it->second, - snapshot_name, request->comment()); + const auto snapshot = vm_ptr->take_snapshot(spec_it->second, snapshot_name, request->comment()); reply.set_snapshot(snapshot->get_name()); } @@ -2553,7 +2547,6 @@ try // Only need to check if the snapshot exists so the result is discarded vm_ptr->get_snapshot(request->snapshot()); - const auto& vm_dir = instance_directory(instance_name, *config); if (!request->destructive()) { RestoreReply confirm_action{}; @@ -2569,7 +2562,7 @@ try { reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); - const auto snapshot = vm_ptr->take_snapshot(vm_dir, vm_specs, "", + const auto snapshot = vm_ptr->take_snapshot(vm_specs, "", fmt::format("Before restoring {}", request->snapshot())); reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), @@ -2580,7 +2573,7 @@ try // Actually restore snapshot reply_msg(server, "Restoring snapshot"); auto old_specs = vm_specs; - vm_ptr->restore_snapshot(vm_dir, request->snapshot(), vm_specs); + vm_ptr->restore_snapshot(request->snapshot(), vm_specs); auto mounts_it = mounts.find(instance_name); assert(mounts_it != mounts.end() && "uninitialized mounts"); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4ddfb1d1a3f..6652ff2342f 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -202,8 +202,8 @@ auto BaseVirtualMachine::make_take_snapshot_rollback(SnapshotMap::iterator it) }); } -std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, - const std::string& name, const std::string& comment) +std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) { std::string snapshot_name; @@ -228,7 +228,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const QDir& sn ret->capture(); ++snapshot_count; - persist_head_snapshot(snapshot_dir); + persist_head_snapshot(); rollback_on_failure.dismiss(); log_latest_snapshot(std::move(lock)); @@ -277,14 +277,14 @@ auto BaseVirtualMachine::make_parent_update_rollback(const std::shared_ptr& snapshot) +void BaseVirtualMachine::delete_snapshot_helper(std::shared_ptr& snapshot) { // Remove snapshot file QTemporaryDir tmp_dir{}; if (!tmp_dir.isValid()) throw std::runtime_error{"Could not create temporary directory"}; - auto snapshot_fileinfo = find_snapshot_file(snapshot_dir, snapshot->get_name()); + auto snapshot_fileinfo = find_snapshot_file(instance_dir, snapshot->get_name()); auto snapshot_filepath = snapshot_fileinfo.filePath(); auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); @@ -298,7 +298,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s // Update head if deleted auto wrote_head = false; - auto head_path = derive_head_path(snapshot_dir); + auto head_path = derive_head_path(instance_dir); auto rollback_head = make_deleted_head_rollback(head_path, wrote_head); wrote_head = updated_deleted_head(snapshot, head_path); @@ -307,7 +307,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s updated_snapshot_paths.reserve(snapshots.size()); auto rollback_parent_updates = make_parent_update_rollback(snapshot, updated_snapshot_paths); - update_parents(snapshot_dir, snapshot, updated_snapshot_paths); + update_parents(snapshot, updated_snapshot_paths); // Erase the snapshot with the backend and dismiss rollbacks on success snapshot->erase(); @@ -316,7 +316,7 @@ void BaseVirtualMachine::delete_snapshot_helper(const QDir& snapshot_dir, std::s rollback_snapshot_file.dismiss(); } -void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, +void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) { auto new_parent = deleted_parent->get_parent(); @@ -327,14 +327,14 @@ void BaseVirtualMachine::update_parents(const QDir& snapshot_dir, std::shared_pt other->set_parent(new_parent); updated_snapshot_paths[other.get()]; - const auto other_filepath = find_snapshot_file(snapshot_dir, other->get_name()).filePath(); + const auto other_filepath = find_snapshot_file(instance_dir, other->get_name()).filePath(); write_json(other->serialize(), other_filepath); updated_snapshot_paths[other.get()] = other_filepath; } } } -void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::string& name) +void BaseVirtualMachine::delete_snapshot(const std::string& name) { std::unique_lock lock{snapshot_mutex}; @@ -343,22 +343,22 @@ void BaseVirtualMachine::delete_snapshot(const QDir& snapshot_dir, const std::st throw NoSuchSnapshot{vm_name, name}; auto snapshot = it->second; - delete_snapshot_helper(snapshot_dir, snapshot); + delete_snapshot_helper(snapshot); snapshots.erase(it); // doesn't throw mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } -void BaseVirtualMachine::load_snapshots(const QDir& snapshot_dir) +void BaseVirtualMachine::load_snapshots() { std::unique_lock lock{snapshot_mutex}; - auto snapshot_files = MP_FILEOPS.entryInfoList(snapshot_dir, {QString{"*.%1"}.arg(snapshot_extension)}, + auto snapshot_files = MP_FILEOPS.entryInfoList(instance_dir, {QString{"*.%1"}.arg(snapshot_extension)}, QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) load_snapshot_from_file(finfo.filePath()); - load_generic_snapshot_info(snapshot_dir); + load_generic_snapshot_info(); } std::vector BaseVirtualMachine::get_childrens_names(const Snapshot* parent) const @@ -372,13 +372,13 @@ std::vector BaseVirtualMachine::get_childrens_names(const Snapshot* return children; } -void BaseVirtualMachine::load_generic_snapshot_info(const QDir& snapshot_dir) +void BaseVirtualMachine::load_generic_snapshot_info() { try { - snapshot_count = std::stoi(mpu::contents_of(snapshot_dir.filePath(count_filename))); + snapshot_count = std::stoi(mpu::contents_of(instance_dir.filePath(count_filename))); - auto head_name = mpu::contents_of(snapshot_dir.filePath(head_filename)); + auto head_name = mpu::contents_of(instance_dir.filePath(head_filename)); head_snapshot = head_name.empty() ? nullptr : get_snapshot(head_name); } catch (FileOpenFailedException&) @@ -458,16 +458,16 @@ void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, QFile& }); } -void BaseVirtualMachine::persist_head_snapshot(const QDir& snapshot_dir) const +void BaseVirtualMachine::persist_head_snapshot() const { assert(head_snapshot); const auto snapshot_filename = derive_snapshot_filename(derive_index_string(snapshot_count), QString::fromStdString(head_snapshot->get_name())); - auto snapshot_filepath = snapshot_dir.filePath(snapshot_filename); - auto head_path = derive_head_path(snapshot_dir); - auto count_path = snapshot_dir.filePath(count_filename); + auto snapshot_filepath = instance_dir.filePath(snapshot_filename); + auto head_path = derive_head_path(instance_dir); + auto count_path = instance_dir.filePath(count_filename); auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filepath]() noexcept { QFile{snapshot_filepath}.remove(); // best effort, ignore return @@ -518,7 +518,7 @@ void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, const st } } -void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) +void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& specs) { std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition @@ -531,7 +531,7 @@ void BaseVirtualMachine::restore_snapshot(const QDir& snapshot_dir, const std::s snapshot->apply(); - const auto head_path = derive_head_path(snapshot_dir); + const auto head_path = derive_head_path(instance_dir); auto rollback = make_restore_rollback(head_path, specs); specs.state = snapshot->get_state(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 294d0ffd768..04ccd4e3a77 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -64,11 +64,11 @@ class BaseVirtualMachine : public VirtualMachine // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor - std::shared_ptr take_snapshot(const QDir& snapshot_dir, const VMSpecs& specs, - const std::string& name, const std::string& comment) override; - void delete_snapshot(const QDir& snapshot_dir, const std::string& name) override; - void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override; - void load_snapshots(const QDir& snapshot_dir) override; + std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + const std::string& comment) override; + void delete_snapshot(const std::string& name) override; + void restore_snapshot(const std::string& name, VMSpecs& specs) override; + void load_snapshots() override; std::vector get_childrens_names(const Snapshot* parent) const override; protected: @@ -85,7 +85,7 @@ class BaseVirtualMachine : public VirtualMachine template void log_latest_snapshot(LockT lock) const; - void load_generic_snapshot_info(const QDir& snapshot_dir); + void load_generic_snapshot_info(); void load_snapshot_from_file(const QString& filename); void load_snapshot(const QJsonObject& json); @@ -95,7 +95,7 @@ class BaseVirtualMachine : public VirtualMachine auto make_head_file_rollback(const Path& head_path, QFile& head_file) const; void head_file_rollback_helper(const Path& head_path, QFile& head_file, const std::string& old_head, bool existed) const; - void persist_head_snapshot(const QDir& snapshot_dir) const; + void persist_head_snapshot() const; void persist_head_snapshot_name(const Path& head_path) const; std::string generate_snapshot_name() const; @@ -109,12 +109,12 @@ class BaseVirtualMachine : public VirtualMachine void deleted_head_rollback_helper(const Path& head_path, const bool& wrote_head, std::shared_ptr& old_head); - void update_parents(const QDir& snapshot_dir, std::shared_ptr& deleted_parent, + void update_parents(std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths); auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, std::unordered_map& updated_snapshot_paths) const; - void delete_snapshot_helper(const QDir& snapshot_dir, std::shared_ptr& snapshot); + void delete_snapshot_helper(std::shared_ptr& snapshot); private: SnapshotMap snapshots; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 1d34679a582..a3d15955010 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -71,10 +71,10 @@ struct MockVirtualMachineT : public T MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (override)); MOCK_METHOD(std::shared_ptr, take_snapshot, - (const QDir&, const VMSpecs&, const std::string&, const std::string&), (override)); - MOCK_METHOD(void, delete_snapshot, (const QDir& snapshot_dir, const std::string& name), (override)); - MOCK_METHOD(void, restore_snapshot, (const QDir& snapshot_dir, const std::string&, VMSpecs&), (override)); - MOCK_METHOD(void, load_snapshots, (const QDir&), (override)); + (const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); + MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); + MOCK_METHOD(void, load_snapshots, (), (override)); MOCK_METHOD(std::vector, get_childrens_names, (const Snapshot*), (const, override)); }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index c39b44c4a83..7e0b331ac2e 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -138,21 +138,20 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - std::shared_ptr take_snapshot(const QDir&, const VMSpecs&, const std::string&, - const std::string&) override + std::shared_ptr take_snapshot(const VMSpecs&, const std::string&, const std::string&) override { return {}; } - void delete_snapshot(const QDir& snapshot_dir, const std::string&) override + void delete_snapshot(const std::string&) override { } - void restore_snapshot(const QDir& snapshot_dir, const std::string& name, VMSpecs& specs) override + void restore_snapshot(const std::string& name, VMSpecs& specs) override { } - void load_snapshots(const QDir&) override + void load_snapshots() override { } From 4c5799c22dd84ec731c9084ba4e67ef5689768fd Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 14:52:16 -0700 Subject: [PATCH 386/627] [vm factory] drive by fix add const modifier Signed-off-by: sharder996 --- include/multipass/virtual_machine_factory.h | 4 ++-- .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 2 +- .../backends/libvirt/libvirt_virtual_machine_factory.h | 2 +- src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 2 +- src/platform/backends/lxd/lxd_virtual_machine_factory.h | 4 ++-- src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 4 ++-- src/platform/backends/qemu/qemu_virtual_machine_factory.h | 4 ++-- src/platform/backends/shared/base_virtual_machine_factory.h | 2 +- tests/mock_virtual_machine_factory.h | 4 ++-- tests/stub_virtual_machine_factory.h | 4 ++-- tests/test_base_virtual_machine_factory.cpp | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 60380dff712..131c3265fc9 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -59,8 +59,8 @@ class VirtualMachineFactory : private DisabledCopyMove 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; - virtual QString get_backend_directory_name() = 0; - virtual QString get_backend_version_string() = 0; + virtual QString get_backend_directory_name() const = 0; + virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, const days& days_to_expire) = 0; diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 358b4f3e955..9225d0f4b09 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -175,7 +175,7 @@ void mp::LibVirtVirtualMachineFactory::hypervisor_health_check() bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); } -QString mp::LibVirtVirtualMachineFactory::get_backend_version_string() +QString mp::LibVirtVirtualMachineFactory::get_backend_version_string() const { try { diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h index 5959a5eb13b..96b4f2d33d7 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h @@ -41,7 +41,7 @@ class LibVirtVirtualMachineFactory final : public BaseVirtualMachineFactory VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; - QString get_backend_version_string() override; + QString get_backend_version_string() const override; // Making this public makes this modifiable which is necessary for testing LibvirtWrapper::UPtr libvirt_wrapper; diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index e70096cddd1..08199e5a43d 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -205,7 +205,7 @@ void mp::LXDVirtualMachineFactory::hypervisor_health_check() } } -QString mp::LXDVirtualMachineFactory::get_backend_version_string() +QString mp::LXDVirtualMachineFactory::get_backend_version_string() const { auto reply = lxd_request(manager.get(), "GET", base_url); diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.h b/src/platform/backends/lxd/lxd_virtual_machine_factory.h index f904633c8d6..ecdd91c4012 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.h +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.h @@ -41,11 +41,11 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; - QString get_backend_directory_name() override + QString get_backend_directory_name() const override { return "lxd"; }; - QString get_backend_version_string() override; + QString get_backend_version_string() const override; VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, const days& days_to_expire) override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 8f5d9c2b937..4f1b08ef6fd 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -70,7 +70,7 @@ void mp::QemuVirtualMachineFactory::hypervisor_health_check() qemu_platform->platform_health_check(); } -QString mp::QemuVirtualMachineFactory::get_backend_version_string() +QString mp::QemuVirtualMachineFactory::get_backend_version_string() const { auto process = mp::platform::make_process(simple_process_spec(QString("qemu-system-%1").arg(HOST_ARCH), {"--version"})); @@ -109,7 +109,7 @@ QString mp::QemuVirtualMachineFactory::get_backend_version_string() return QString("qemu-unknown"); } -QString mp::QemuVirtualMachineFactory::get_backend_directory_name() +QString mp::QemuVirtualMachineFactory::get_backend_directory_name() const { return qemu_platform->get_directory_name(); } diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index b0e6b918c2b..f11de47c6d2 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -39,8 +39,8 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; - QString get_backend_version_string() override; - QString get_backend_directory_name() override; + QString get_backend_version_string() const override; + QString get_backend_directory_name() const override; std::vector networks() const override; private: diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 0355b412e10..bf4f8ebfe77 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -40,7 +40,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory return FetchType::ImageOnly; }; - QString get_backend_directory_name() override + QString get_backend_directory_name() const override { return {}; }; diff --git a/tests/mock_virtual_machine_factory.h b/tests/mock_virtual_machine_factory.h index b178ea67412..918453e929c 100644 --- a/tests/mock_virtual_machine_factory.h +++ b/tests/mock_virtual_machine_factory.h @@ -40,8 +40,8 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory MOCK_METHOD(VMImage, prepare_source_image, (const VMImage&), (override)); MOCK_METHOD(void, prepare_instance_image, (const VMImage&, const VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); - MOCK_METHOD(QString, get_backend_directory_name, (), (override)); - MOCK_METHOD(QString, get_backend_version_string, (), (override)); + MOCK_METHOD(QString, get_backend_directory_name, (), (const, override)); + MOCK_METHOD(QString, get_backend_version_string, (), (const, override)); MOCK_METHOD(VMImageVault::UPtr, create_image_vault, (std::vector, URLDownloader*, const Path&, const Path&, const days&), (override)); MOCK_METHOD(void, configure, (VirtualMachineDescription&), (override)); diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index 89a42e6e73a..1563ea0277a 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -58,12 +58,12 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory { } - QString get_backend_directory_name() override + QString get_backend_directory_name() const override { return {}; } - QString get_backend_version_string() override + QString get_backend_version_string() const override { return "stub-5678"; } diff --git a/tests/test_base_virtual_machine_factory.cpp b/tests/test_base_virtual_machine_factory.cpp index c0a2fe53985..cab6d46a96a 100644 --- a/tests/test_base_virtual_machine_factory.cpp +++ b/tests/test_base_virtual_machine_factory.cpp @@ -43,7 +43,7 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory MOCK_METHOD(mp::VMImage, prepare_source_image, (const mp::VMImage&), (override)); MOCK_METHOD(void, prepare_instance_image, (const mp::VMImage&, const mp::VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); - MOCK_METHOD(QString, get_backend_version_string, (), (override)); + MOCK_METHOD(QString, get_backend_version_string, (), (const, override)); MOCK_METHOD(void, prepare_networking, (std::vector&), (override)); MOCK_METHOD(std::vector, networks, (), (const, override)); MOCK_METHOD(std::string, create_bridge_with, (const mp::NetworkInterfaceInfo&), (override)); From e7d16b6077169464310501d2d91d44ef6f824687 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 15:00:48 -0700 Subject: [PATCH 387/627] [vm factory] give responsibility of instances dir to vm factory Signed-off-by: sharder996 --- .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 4 +++- src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 4 ++-- src/platform/backends/lxd/lxd_virtual_machine_factory.h | 1 - src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 4 +++- src/platform/backends/shared/base_virtual_machine_factory.cpp | 2 ++ src/platform/backends/shared/base_virtual_machine_factory.h | 3 +++ 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 9225d0f4b09..dd428ccc2ed 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -108,7 +108,9 @@ auto make_libvirt_wrapper(const std::string& libvirt_object_path) mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir, const std::string& libvirt_object_path) - : libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)}, + : BaseVirtualMachineFactory( + MP_UTILS.make_dir(QDir(data_dir, get_backend_directory_name()).filePath("vault"), "instances")), + libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)}, data_dir{data_dir}, bridge_name{enable_libvirt_network(data_dir, libvirt_wrapper)}, libvirt_object_path{libvirt_object_path} diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 08199e5a43d..2df205567dd 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -86,8 +86,8 @@ mp::NetworkInterfaceInfo munch_network(std::map& host_nets, const std::string& bridge_type); + + const Path instances_dir; }; } // namespace multipass From 4c967ff568ae0c07f9fd5a2d712bd4d8379a967c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 15:05:41 -0700 Subject: [PATCH 388/627] [vm factory] provide external access to specific vm instance directory Signed-off-by: sharder996 --- include/multipass/virtual_machine_factory.h | 1 + src/platform/backends/shared/base_virtual_machine_factory.h | 6 ++++++ tests/mock_virtual_machine_factory.h | 1 + tests/stub_virtual_machine_factory.h | 5 +++++ 4 files changed, 13 insertions(+) diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 131c3265fc9..f2144a7608c 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -60,6 +60,7 @@ class VirtualMachineFactory : private DisabledCopyMove virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0; virtual void hypervisor_health_check() = 0; virtual QString get_backend_directory_name() const = 0; + virtual QString get_instance_directory_name(const std::string& name) const = 0; virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_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 5a66750ccf8..334509c3809 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,11 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory return {}; }; + QString get_instance_directory_name(const std::string& name) const override + { + return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); + } + void prepare_networking(std::vector& /*extra_interfaces*/) override { // only certain backends need to do anything to prepare networking diff --git a/tests/mock_virtual_machine_factory.h b/tests/mock_virtual_machine_factory.h index 918453e929c..9139963b62e 100644 --- a/tests/mock_virtual_machine_factory.h +++ b/tests/mock_virtual_machine_factory.h @@ -41,6 +41,7 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory MOCK_METHOD(void, prepare_instance_image, (const VMImage&, const VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); MOCK_METHOD(QString, get_backend_directory_name, (), (const, override)); + MOCK_METHOD(QString, get_instance_directory_name, (const std::string&), (const, override)); MOCK_METHOD(QString, get_backend_version_string, (), (const, override)); MOCK_METHOD(VMImageVault::UPtr, create_image_vault, (std::vector, URLDownloader*, const Path&, const Path&, const days&), (override)); diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index 1563ea0277a..633e8b21ccd 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -63,6 +63,11 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory return {}; } + QString get_instance_directory_name(const std::string& name) const override + { + return {}; + } + QString get_backend_version_string() const override { return "stub-5678"; From ebdf3793c9fb4b20a32abd7ea552b3b5f08c76bd Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 15:09:36 -0700 Subject: [PATCH 389/627] [vm factory] populate vm instance directory on creation Signed-off-by: sharder996 --- .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 3 ++- src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 4 +++- src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index dd428ccc2ed..a07a03c0203 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -128,7 +128,8 @@ mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machin if (bridge_name.empty()) bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); - return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper); + return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper, + MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); } mp::LibVirtVirtualMachineFactory::~LibVirtVirtualMachineFactory() diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 2df205567dd..aa1befc0f2a 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -101,7 +102,8 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co VMStatusMonitor& monitor) { return std::make_unique(desc, monitor, manager.get(), base_url, multipass_bridge_name, - storage_pool); + storage_pool, + MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); } void mp::LXDVirtualMachineFactory::remove_resources_for(const std::string& name) diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 0f1fc893b55..593fcb65efe 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -46,7 +46,8 @@ mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_di mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) { - return std::make_unique(desc, qemu_platform.get(), monitor); + return std::make_unique(desc, qemu_platform.get(), monitor, + MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); } void mp::QemuVirtualMachineFactory::remove_resources_for(const std::string& name) From 0578e6f06c8c5266ec7a9715812db51b0a3f092a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 16 Jun 2023 15:11:30 -0700 Subject: [PATCH 390/627] [vm factory] give responsibility of removing instance dir on deletion to factory Signed-off-by: sharder996 --- src/daemon/daemon.cpp | 2 +- src/daemon/default_vm_image_vault.cpp | 4 ---- .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 3 +++ src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 3 +++ src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 3 +++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3a6525892f6..147c8a0a8c5 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2658,8 +2658,8 @@ void mp::Daemon::persist_instances() void mp::Daemon::release_resources(const std::string& instance) { - config->factory->remove_resources_for(instance); config->vault->remove(instance); + config->factory->remove_resources_for(instance); auto spec_it = vm_instance_specs.find(instance); if (spec_it != cend(vm_instance_specs)) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 2a418ffa92d..aaa034a936e 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -444,10 +444,6 @@ void mp::DefaultVMImageVault::remove(const std::string& name) if (name_entry == instance_image_records.end()) return; - QDir instance_dir{instances_dir}; - if (instance_dir.cd(QString::fromStdString(name))) - instance_dir.removeRecursively(); - instance_image_records.erase(name); persist_instance_records(); } diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index a07a03c0203..2cdeadf7bf0 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -149,6 +149,9 @@ void mp::LibVirtVirtualMachineFactory::remove_resources_for(const std::string& n auto connection = LibVirtVirtualMachine::open_libvirt_connection(libvirt_wrapper); libvirt_wrapper->virDomainUndefine(libvirt_wrapper->virDomainLookupByName(connection.get(), name.c_str())); + + QDir instance_dir{get_instance_directory_name(name)}; + instance_dir.removeRecursively(); } mp::VMImage mp::LibVirtVirtualMachineFactory::prepare_source_image(const VMImage& source_image) diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index aa1befc0f2a..13caf6767f0 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -109,6 +109,9 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co void mp::LXDVirtualMachineFactory::remove_resources_for(const std::string& name) { mpl::log(mpl::Level::trace, category, fmt::format("No resources to remove for \"{}\"", name)); + + QDir instance_dir{get_instance_directory_name(name)}; + instance_dir.removeRecursively(); } auto mp::LXDVirtualMachineFactory::prepare_source_image(const VMImage& source_image) -> VMImage diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 593fcb65efe..16cbd457ac2 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -53,6 +53,9 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(c void mp::QemuVirtualMachineFactory::remove_resources_for(const std::string& name) { qemu_platform->remove_resources_for(name); + + QDir instance_dir{get_instance_directory_name(name)}; + instance_dir.removeRecursively(); } mp::VMImage mp::QemuVirtualMachineFactory::prepare_source_image(const mp::VMImage& source_image) From ea86f50e89d1f21c66192f397585fcd02fb2ddc9 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 19 Jun 2023 03:49:19 -0700 Subject: [PATCH 391/627] [image vault] have image vault reference factory for instance directories Signed-off-by: sharder996 --- include/multipass/vm_image_vault.h | 2 +- src/daemon/daemon.cpp | 16 ++- src/daemon/default_vm_image_vault.cpp | 33 ++--- src/daemon/default_vm_image_vault.h | 12 +- .../backends/lxd/lxd_vm_image_vault.cpp | 3 +- .../backends/lxd/lxd_vm_image_vault.h | 4 +- .../qemu/qemu_virtual_machine_factory.cpp | 5 +- .../shared/base_virtual_machine_factory.h | 4 +- tests/blueprint_test_lambdas.cpp | 8 +- tests/blueprint_test_lambdas.h | 2 +- tests/lxd/test_lxd_image_vault.cpp | 75 +++++----- tests/mock_vm_image_vault.h | 6 +- tests/stub_vm_image_vault.h | 4 +- tests/test_alias_dict.cpp | 2 +- tests/test_daemon.cpp | 28 ++-- tests/test_daemon_launch.cpp | 4 +- tests/test_image_vault.cpp | 135 ++++++++++-------- 17 files changed, 179 insertions(+), 164 deletions(-) diff --git a/include/multipass/vm_image_vault.h b/include/multipass/vm_image_vault.h index 6f31f54faea..f6977697e2c 100644 --- a/include/multipass/vm_image_vault.h +++ b/include/multipass/vm_image_vault.h @@ -82,7 +82,7 @@ class VMImageVault : private DisabledCopyMove virtual ~VMImageVault() = default; virtual VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum) = 0; + const std::optional& checksum, const Path& download_dir) = 0; virtual void remove(const std::string& name) = 0; virtual bool has_record_for(const std::string& name) = 0; virtual void prune_expired_images() = 0; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 147c8a0a8c5..cd36467c919 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -437,14 +437,15 @@ QJsonObject vm_spec_to_json(const mp::VMSpecs& specs) return json; } -auto fetch_image_for(const std::string& name, const mp::FetchType& fetch_type, mp::VMImageVault& vault) +auto fetch_image_for(const std::string& name, mp::VirtualMachineFactory& factory, mp::VMImageVault& vault) { auto stub_prepare = [](const mp::VMImage&) -> mp::VMImage { return {}; }; auto stub_progress = [](int download_type, int progress) { return true; }; mp::Query query{name, "", false, "", mp::Query::Type::Alias, false}; - return vault.fetch_image(fetch_type, query, stub_prepare, stub_progress, false, std::nullopt); + return vault.fetch_image(factory.fetch_type(), query, stub_prepare, stub_progress, false, std::nullopt, + factory.get_instance_directory_name(name)); } auto try_mem_size(const std::string& val) -> std::optional @@ -1378,7 +1379,7 @@ mp::Daemon::Daemon(std::unique_ptr the_config) continue; } - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto vm_image = fetch_image_for(name, *config->factory, *config->vault); if (!vm_image.image_path.isEmpty() && !QFile::exists(vm_image.image_path)) { mpl::log(mpl::Level::warning, category, @@ -1816,7 +1817,7 @@ try // clang-format on entry->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); // FIXME: Set the release to the cached current version when supported - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto vm_image = fetch_image_for(name, *config->factory, *config->vault); auto current_release = vm_image.original_release; if (!vm_image.id.empty() && current_release.empty()) @@ -2917,8 +2918,9 @@ void mp::Daemon::create_vm(const CreateRequest* request, if (!vm_desc.image.id.empty()) checksum = vm_desc.image.id; - auto vm_image = config->vault->fetch_image(fetch_type, query, prepare_action, progress_monitor, - launch_from_blueprint, checksum); + auto vm_image = + config->vault->fetch_image(fetch_type, query, prepare_action, progress_monitor, launch_from_blueprint, + checksum, config->factory->get_instance_directory_name(name)); const auto image_size = config->vault->minimum_image_size_for(vm_image.id); vm_desc.disk_space = compute_final_image_size( @@ -3381,7 +3383,7 @@ void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem else info->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); - auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); + auto vm_image = fetch_image_for(name, *config->factory, *config->vault); auto original_release = vm_image.original_release; if (!vm_image.id.empty() && original_release.empty()) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index aaa034a936e..e0ebb97deab 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -239,7 +239,6 @@ mp::DefaultVMImageVault::DefaultVMImageVault(std::vector image_hos url_downloader{downloader}, cache_dir{QDir(cache_dir_path).filePath("vault")}, data_dir{QDir(data_dir_path).filePath("vault")}, - instances_dir(data_dir.filePath("instances")), images_dir(cache_dir.filePath("images")), days_to_expire{days_to_expire}, prepared_image_records{load_db(cache_dir.filePath(image_db_name))}, @@ -254,7 +253,8 @@ mp::DefaultVMImageVault::~DefaultVMImageVault() mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, - const bool unlock, const std::optional& checksum) + const bool unlock, const std::optional& checksum, + const mp::Path& download_dir) { { std::lock_guard lock{fetch_mutex}; @@ -282,11 +282,11 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (source_image.image_path.endsWith(".xz")) { - source_image.image_path = extract_image_from(query.name, source_image, monitor); + source_image.image_path = extract_image_from(query.name, source_image, monitor, download_dir); } else { - source_image = image_instance_from(query.name, source_image); + source_image = image_instance_from(query.name, source_image, download_dir); } vm_image = prepare(source_image); @@ -327,7 +327,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (last_modified.isValid() && (last_modified.toString().toStdString() == record.image.release_date)) { - return finalize_image_records(query, record.image, id); + return finalize_image_records(query, record.image, id, download_dir); } } @@ -389,7 +389,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co const auto prepared_image = record.second.image; try { - return finalize_image_records(query, prepared_image, record.first); + return finalize_image_records(query, prepared_image, record.first, download_dir); } catch (const std::exception& e) { @@ -427,7 +427,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co auto prepared_image = future.result(); std::lock_guard lock{fetch_mutex}; in_progress_image_fetches.erase(id); - return finalize_image_records(query, prepared_image, id); + return finalize_image_records(query, prepared_image, id, download_dir); } catch (const std::exception&) { @@ -532,7 +532,8 @@ void mp::DefaultVMImageVault::update_images(const FetchType& fetch_type, const P mpl::log(mpl::Level::info, category, fmt::format("Updating {} source image to latest", record.query.release)); try { - fetch_image(fetch_type, record.query, prepare, monitor, false, std::nullopt); + fetch_image(fetch_type, record.query, prepare, monitor, false, std::nullopt, + QFileInfo{record.image.image_path}.absolutePath()); // Remove old image std::lock_guard lock{fetch_mutex}; @@ -630,24 +631,20 @@ mp::VMImage mp::DefaultVMImageVault::download_and_prepare_source_image( } QString mp::DefaultVMImageVault::extract_image_from(const std::string& instance_name, const VMImage& source_image, - const ProgressMonitor& monitor) + const ProgressMonitor& monitor, const mp::Path& dest_dir) { const auto name = QString::fromStdString(instance_name); - const QDir output_dir{MP_UTILS.make_dir(instances_dir, name)}; QFileInfo file_info{source_image.image_path}; const auto image_name = file_info.fileName().remove(".xz"); - const auto image_path = output_dir.filePath(image_name); + const auto image_path = QDir(dest_dir).filePath(image_name); return mp::vault::extract_image(image_path, monitor); } mp::VMImage mp::DefaultVMImageVault::image_instance_from(const std::string& instance_name, - const VMImage& prepared_image) + const VMImage& prepared_image, const mp::Path& dest_dir) { - auto name = QString::fromStdString(instance_name); - auto output_dir = MP_UTILS.make_dir(instances_dir, name); - - return {mp::vault::copy(prepared_image.image_path, output_dir), + return {mp::vault::copy(prepared_image.image_path, dest_dir), prepared_image.id, prepared_image.original_release, prepared_image.current_release, @@ -667,13 +664,13 @@ std::optional> mp::DefaultVMImageVault::get_image_future(co } mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, const VMImage& prepared_image, - const std::string& id) + const std::string& id, const mp::Path& dest_dir) { VMImage vm_image; if (!query.name.empty()) { - vm_image = image_instance_from(query.name, prepared_image); + vm_image = image_instance_from(query.name, prepared_image, dest_dir); instance_image_records[query.name] = {vm_image, query, std::chrono::system_clock::now()}; } diff --git a/src/daemon/default_vm_image_vault.h b/src/daemon/default_vm_image_vault.h index 77d9828003a..d7a353d273e 100644 --- a/src/daemon/default_vm_image_vault.h +++ b/src/daemon/default_vm_image_vault.h @@ -51,8 +51,8 @@ class DefaultVMImageVault final : public BaseVMImageVault ~DefaultVMImageVault(); VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum) override; + const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + const Path& download_dir) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; void prune_expired_images() override; @@ -61,21 +61,21 @@ class DefaultVMImageVault final : public BaseVMImageVault MemorySize minimum_image_size_for(const std::string& id) override; private: - VMImage image_instance_from(const std::string& name, const VMImage& prepared_image); + VMImage image_instance_from(const std::string& name, const VMImage& prepared_image, const Path& dest_dir); VMImage download_and_prepare_source_image(const VMImageInfo& info, std::optional& existing_source_image, const QDir& image_dir, const FetchType& fetch_type, const PrepareAction& prepare, const ProgressMonitor& monitor); QString extract_image_from(const std::string& instance_name, const VMImage& source_image, - const ProgressMonitor& monitor); + const ProgressMonitor& monitor, const Path& dest_dir); std::optional> get_image_future(const std::string& id); - VMImage finalize_image_records(const Query& query, const VMImage& prepared_image, const std::string& id); + VMImage finalize_image_records(const Query& query, const VMImage& prepared_image, const std::string& id, + const Path& dest_dir); void persist_image_records(); void persist_instance_records(); URLDownloader* const url_downloader; const QDir cache_dir; const QDir data_dir; - const QDir instances_dir; const QDir images_dir; const days days_to_expire; std::mutex fetch_mutex; diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.cpp b/src/platform/backends/lxd/lxd_vm_image_vault.cpp index 7b3c8e56554..3f00e97e6e6 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.cpp +++ b/src/platform/backends/lxd/lxd_vm_image_vault.cpp @@ -159,7 +159,8 @@ mp::LXDVMImageVault::LXDVMImageVault(std::vector image_hosts, URLD mp::VMImage mp::LXDVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, - const bool unlock, const std::optional& checksum) + const bool unlock, const std::optional& checksum, + const mp::Path&) { // Look for an already existing instance and get its image info try diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.h b/src/platform/backends/lxd/lxd_vm_image_vault.h index fe6ade82713..ced3654ea7a 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.h +++ b/src/platform/backends/lxd/lxd_vm_image_vault.h @@ -40,8 +40,8 @@ class LXDVMImageVault final : public BaseVMImageVault const QUrl& base_url, const QString& cache_dir_path, const multipass::days& days_to_expire); VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum) override; + const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + const Path&) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; void prune_expired_images() override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 16cbd457ac2..90b6bb9cd37 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -37,10 +37,9 @@ constexpr auto category = "qemu factory"; } // namespace mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_dir) - : BaseVirtualMachineFactory( - MP_UTILS.make_dir(QDir(data_dir, get_backend_directory_name()).filePath("vault"), "instances")), - qemu_platform{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir)} + : BaseVirtualMachineFactory(QString{}), qemu_platform{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir)} { + instances_dir = MP_UTILS.make_dir(QDir(data_dir, get_backend_directory_name()).filePath("vault"), "instances"); } mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 334509c3809..936174bf28f 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -49,7 +49,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory QString get_instance_directory_name(const std::string& name) const override { - return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); + return MP_UTILS.make_dir(multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name))); } void prepare_networking(std::vector& /*extra_interfaces*/) override @@ -84,7 +84,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory virtual void prepare_interface(NetworkInterface& net, std::vector& host_nets, const std::string& bridge_type); - const Path instances_dir; + Path instances_dir; }; } // namespace multipass diff --git a/tests/blueprint_test_lambdas.cpp b/tests/blueprint_test_lambdas.cpp index 0ea7bb8ede7..542ba9d9f01 100644 --- a/tests/blueprint_test_lambdas.cpp +++ b/tests/blueprint_test_lambdas.cpp @@ -29,17 +29,19 @@ #include "common.h" #include "stub_virtual_machine.h" #include "stub_vm_image_vault.h" +#include "temp_dir.h" namespace mp = multipass; namespace mpt = multipass::test; std::function)> + const mp::ProgressMonitor&, const bool, const std::optional, const mp::Path&)> mpt::fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum) { return [&release, &remote, must_have_checksum]( const mp::FetchType& fetch_type, const mp::Query& query, const mp::VMImageVault::PrepareAction& prepare, - const mp::ProgressMonitor& monitor, const bool unlock, const std::optional& checksum) { + const mp::ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + const mp::Path& download_dir) { EXPECT_EQ(query.release, release); if (remote.empty()) { @@ -55,7 +57,7 @@ mpt::fetch_image_lambda(const std::string& release, const std::string& remote, c EXPECT_NE(checksum, std::nullopt); } - return mpt::StubVMImageVault().fetch_image(fetch_type, query, prepare, monitor, unlock, checksum); + return mpt::StubVMImageVault().fetch_image(fetch_type, query, prepare, monitor, unlock, checksum, download_dir); }; } diff --git a/tests/blueprint_test_lambdas.h b/tests/blueprint_test_lambdas.h index 345b38846b2..8deb2952a9f 100644 --- a/tests/blueprint_test_lambdas.h +++ b/tests/blueprint_test_lambdas.h @@ -39,7 +39,7 @@ namespace test { std::function)> + const bool, const std::optional, const multipass::Path&)> fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum = false); std::function diff --git a/tests/lxd/test_lxd_image_vault.cpp b/tests/lxd/test_lxd_image_vault.cpp index 79e5bd0b127..f98af001b97 100644 --- a/tests/lxd/test_lxd_image_vault.cpp +++ b/tests/lxd/test_lxd_image_vault.cpp @@ -70,6 +70,7 @@ struct LXDImageVault : public Test mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; mpt::StubURLDownloader stub_url_downloader; mpt::TempDir cache_dir; + mpt::TempDir download_dir; }; } // namespace @@ -95,7 +96,7 @@ TEST_F(LXDImageVault, instance_exists_fetch_returns_expected_image_info) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, download_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -123,7 +124,7 @@ TEST_F(LXDImageVault, instance_exists_custom_image_returns_expected_image_info) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, download_dir.path())); EXPECT_EQ(image.id, "6937ddd3f4c3329182855843571fc91ae4fee24e8e0eb0f7cdcf2c22feed4dab"); EXPECT_EQ(image.original_release, "Snapcraft builder for Core 20"); @@ -152,7 +153,7 @@ TEST_F(LXDImageVault, instance_exists_uses_cached_release_title) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, download_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "Fake Title"); @@ -182,7 +183,7 @@ TEST_F(LXDImageVault, instance_exists_no_cached_release_title_info_for_fails) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, download_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, ""); @@ -212,7 +213,7 @@ TEST_F(LXDImageVault, returns_expected_info_with_valid_remote) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt)); + std::nullopt, download_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -240,11 +241,11 @@ TEST_F(LXDImageVault, throws_with_invalid_alias) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - MP_EXPECT_THROW_THAT( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), - std::runtime_error, - mpt::match_what( - StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", alias, "release")))); + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + std::runtime_error, + mpt::match_what(StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", + alias, "release")))); } TEST_F(LXDImageVault, throws_with_invalid_remote) @@ -259,9 +260,10 @@ TEST_F(LXDImageVault, throws_with_invalid_remote) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - MP_EXPECT_THROW_THAT( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), - std::runtime_error, mpt::match_what(HasSubstr(fmt::format("Remote \'{}\' is not found.", remote)))); + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + std::runtime_error, + mpt::match_what(HasSubstr(fmt::format("Remote \'{}\' is not found.", remote)))); } TEST_F(LXDImageVault, does_not_download_if_image_exists) @@ -289,7 +291,7 @@ TEST_F(LXDImageVault, does_not_download_if_image_exists) base_url, cache_dir.path(), mp::days{0}}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt)); + std::nullopt, download_dir.path())); } TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) @@ -326,7 +328,7 @@ TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt)); + false, std::nullopt, download_dir.path())); EXPECT_FALSE(download_requested); EXPECT_EQ(image.original_release, mpt::default_release_info); } @@ -353,7 +355,7 @@ TEST_F(LXDImageVault, requests_download_if_image_does_not_exist) base_url, cache_dir.path(), mp::days{0}}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt)); + std::nullopt, download_dir.path())); EXPECT_TRUE(download_requested); } @@ -380,8 +382,8 @@ TEST_F(LXDImageVault, sets_fingerprint_with_hash_query) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "e3b0c44298fc1c1", false, "release", mp::Query::Type::Alias}; - EXPECT_NO_THROW( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt)); + EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path())); } TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) @@ -419,9 +421,9 @@ TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_THROW( - image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, std::nullopt), - mp::AbortedDownloadException); + EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, + std::nullopt, download_dir.path()), + mp::AbortedDownloadException); EXPECT_TRUE(delete_requested); } @@ -457,9 +459,9 @@ TEST_F(LXDImageVault, percent_complete_returns_negative_on_metadata_download) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_THROW( - image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, std::nullopt), - mp::AbortedDownloadException); + EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, + std::nullopt, download_dir.path()), + mp::AbortedDownloadException); } TEST_F(LXDImageVault, delete_requested_on_instance_remove) @@ -832,8 +834,8 @@ TEST_F(LXDImageVault, custom_image_found_returns_expected_info) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "snapcraft", false, "release", mp::Query::Type::Alias}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_EQ(image.id, mpt::lxd_snapcraft_image_id); EXPECT_EQ(image.original_release, mpt::snapcraft_release_info); @@ -890,8 +892,8 @@ TEST_F(LXDImageVault, custom_image_downloads_and_creates_correct_upload) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "custom", false, "release", mp::Query::Type::Alias}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_EQ(image.id, mpt::lxd_custom_image_id); EXPECT_EQ(image.original_release, mpt::custom_release_info); @@ -916,7 +918,7 @@ TEST_F(LXDImageVault, fetch_image_unable_to_connect_logs_error_and_returns_blank StrEq(fmt::format("{} - returning blank image info", exception_message))))); auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt); + std::nullopt, download_dir.path()); EXPECT_TRUE(image.id.empty()); EXPECT_TRUE(image.original_release.empty()); @@ -1043,8 +1045,8 @@ TEST_F(LXDImageVault, http_based_image_downloads_and_creates_correct_upload) const std::string download_url{"http://www.foo.com/images/foo.img"}; const mp::Query query{"", download_url, false, "", mp::Query::Type::HttpDownload}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1109,8 +1111,8 @@ TEST_F(LXDImageVault, file_based_fetch_copies_image_and_returns_expected_info) auto current_time = QDateTime::currentDateTime(); const mp::Query query{"", file.url().toStdString(), false, "", mp::Query::Type::LocalFile}; - auto image = - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1131,9 +1133,10 @@ TEST_F(LXDImageVault, invalid_local_file_image_throws) const std::string missing_file{"/foo"}; const mp::Query query{"", fmt::format("file://{}", missing_file), false, "", mp::Query::Type::LocalFile}; - MP_EXPECT_THROW_THAT( - image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), - std::runtime_error, mpt::match_what(StrEq(fmt::format("Custom image `{}` does not exist.", missing_file)))); + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + std::runtime_error, + mpt::match_what(StrEq(fmt::format("Custom image `{}` does not exist.", missing_file)))); } TEST_F(LXDImageVault, updateImagesThrowsOnMissingImage) diff --git a/tests/mock_vm_image_vault.h b/tests/mock_vm_image_vault.h index 1241a19b831..435319f3e0e 100644 --- a/tests/mock_vm_image_vault.h +++ b/tests/mock_vm_image_vault.h @@ -35,8 +35,8 @@ class MockVMImageVault : public VMImageVault public: MockVMImageVault() { - ON_CALL(*this, fetch_image(_, _, _, _, _, _)) - .WillByDefault([this](auto, auto, const PrepareAction& prepare, auto, auto, auto) { + ON_CALL(*this, fetch_image(_, _, _, _, _, _, _)) + .WillByDefault([this](auto, auto, const PrepareAction& prepare, auto, auto, auto, auto) { return VMImage{dummy_image.name(), {}, {}, {}, {}, {}}; }); ON_CALL(*this, has_record_for(_)).WillByDefault(Return(true)); @@ -45,7 +45,7 @@ class MockVMImageVault : public VMImageVault MOCK_METHOD(VMImage, fetch_image, (const FetchType&, const Query&, const PrepareAction&, const ProgressMonitor&, const bool, - const std::optional&), + const std::optional&, const mp::Path&), (override)); MOCK_METHOD(void, remove, (const std::string&), (override)); MOCK_METHOD(bool, has_record_for, (const std::string&), (override)); diff --git a/tests/stub_vm_image_vault.h b/tests/stub_vm_image_vault.h index c8a7687ea22..535390510a7 100644 --- a/tests/stub_vm_image_vault.h +++ b/tests/stub_vm_image_vault.h @@ -29,8 +29,8 @@ namespace test struct StubVMImageVault final : public multipass::VMImageVault { multipass::VMImage fetch_image(const multipass::FetchType&, const multipass::Query&, const PrepareAction& prepare, - const multipass::ProgressMonitor&, const bool, - const std::optional&) override + const multipass::ProgressMonitor&, const bool, const std::optional&, + const multipass::Path&) override { return prepare({dummy_image.name(), {}, {}, {}, {}, {}}); }; diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index a6dab1b360e..974a61dfb43 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -579,7 +579,7 @@ TEST_P(DaemonAliasTestsuite, purge_removes_purged_instance_aliases_and_scripts) auto mock_image_vault = std::make_unique>(); EXPECT_CALL(*mock_image_vault, remove(_)).WillRepeatedly(Return()); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillRepeatedly(Return(mp::VMImage{})); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillRepeatedly(Return(mp::VMImage{})); EXPECT_CALL(*mock_image_vault, prune_expired_images()).WillRepeatedly(Return()); EXPECT_CALL(*mock_image_vault, has_record_for(_)).WillRepeatedly(Return(true)); diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 08d017e52e6..ab906492fcb 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -675,7 +675,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundPassesExpectedAliases) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -714,7 +714,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundMountsWorkspace) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -748,7 +748,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundMountsWorkspaceConfined) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -786,7 +786,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundMountsWorkspaceInExisting EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -829,7 +829,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotMountUnwrittableWo EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -873,7 +873,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundButCannotMount) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -921,7 +921,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundPassesExpectedAliasesWith EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, command_line_name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -968,7 +968,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotOverwriteAliases) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -1017,7 +1017,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotOverwriteAliasesIf EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); auto alias = std::make_optional(std::make_pair(alias_name, mp::AliasDefinition{name, alias_command, alias_wdir})); @@ -1058,7 +1058,7 @@ TEST_P(DaemonCreateLaunchTestSuite, blueprint_found_passes_expected_data) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce(mpt::fetch_blueprint_for_lambda(num_cores, mem_size, disk_space, release, remote)); @@ -1086,7 +1086,7 @@ TEST_P(DaemonCreateLaunchTestSuite, blueprint_not_found_passes_expected_data) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, empty)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, empty)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, empty)); config_builder.vault = std::move(mock_image_vault); mp::Daemon daemon{config_builder.build()}; @@ -1341,7 +1341,7 @@ TEST_F(DaemonCreateLaunchTestSuite, blueprintFromFileCallsCorrectFunction) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)).Times(1); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).Times(1); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).Times(1); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)).Times(0); @@ -1691,9 +1691,9 @@ TEST_F(Daemon, ctor_drops_removed_instances) config_builder.data_directory = temp_dir->path(); auto mock_image_vault = std::make_unique>(); - EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, stayed), _, _, _, _)) + EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, stayed), _, _, _, _, _)) .WillRepeatedly(DoDefault()); // returns an image that can be verified to exist for this instance - EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, gone), _, _, _, _)) + EXPECT_CALL(*mock_image_vault, fetch_image(_, Field(&mp::Query::name, gone), _, _, _, _, _)) .WillOnce(Return(mp::VMImage{"/path/to/nowhere", "", "", "", "", {}})); // an image that can't be verified to // exist for this instance config_builder.vault = std::move(mock_image_vault); diff --git a/tests/test_daemon_launch.cpp b/tests/test_daemon_launch.cpp index 8db790c38e5..5b3740e4fe9 100644 --- a/tests/test_daemon_launch.cpp +++ b/tests/test_daemon_launch.cpp @@ -72,7 +72,7 @@ TEST_F(TestDaemonLaunch, blueprintFoundMountsWorkspaceWithNameOverride) EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, command_line_name)); - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)).WillOnce(mpt::fetch_image_lambda(release, remote)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) .WillOnce( @@ -127,7 +127,7 @@ TEST_F(TestDaemonLaunch, v2BlueprintFoundPropagatesSha) .WillOnce(mpt::create_virtual_machine_lambda(num_cores, mem_size, disk_space, command_line_name)); // The expectation of this test is set in fetch_image_lambda(). - EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _)) + EXPECT_CALL(*mock_image_vault, fetch_image(_, _, _, _, _, _, _)) .WillOnce(mpt::fetch_image_lambda(release, remote, true)); EXPECT_CALL(*mock_blueprint_provider, fetch_blueprint_for(_, _, _)) diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index 26a417e77d1..c78a569cfa1 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -168,6 +168,7 @@ struct ImageVault : public testing::Test [](const mp::VMImage& source_image) -> mp::VMImage { return source_image; }}; mpt::TempDir cache_dir; mpt::TempDir data_dir; + mpt::TempDir download_dir; std::string instance_name{"valley-pied-piper"}; mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; }; @@ -176,8 +177,8 @@ struct ImageVault : public testing::Test TEST_F(ImageVault, downloads_image) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(host.image.url())); @@ -186,8 +187,8 @@ TEST_F(ImageVault, downloads_image) TEST_F(ImageVault, returned_image_contains_instance_name) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_TRUE(vm_image.image_path.contains(QString::fromStdString(instance_name))); } @@ -201,8 +202,8 @@ TEST_F(ImageVault, calls_prepare) prepare_called = true; return source_image; }; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_TRUE(prepare_called); } @@ -215,10 +216,10 @@ TEST_F(ImageVault, records_instanced_images) ++prepare_called_count; return source_image; }; - auto vm_image1 = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); - auto vm_image2 = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); + auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -234,13 +235,13 @@ TEST_F(ImageVault, caches_prepared_images) ++prepare_called_count; return source_image; }; - auto vm_image1 = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; - auto vm_image2 = - vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -258,12 +259,12 @@ TEST_F(ImageVault, remembers_instance_images) }; mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image1 = - first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image2 = - another_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -279,14 +280,14 @@ TEST_F(ImageVault, remembers_prepared_images) }; mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image1 = - first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image2 = - another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -307,8 +308,8 @@ TEST_F(ImageVault, uses_image_from_prepare) }; mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); const auto image_data = mp::utils::contents_of(vm_image.image_path); EXPECT_THAT(image_data, StrEq(expected_data)); @@ -326,8 +327,8 @@ TEST_F(ImageVault, image_purged_expired) mpt::make_file_with_content(file_name); return {file_name, source_image.id, "", "", "", {}}; }; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -347,8 +348,8 @@ TEST_F(ImageVault, image_exists_not_expired) mpt::make_file_with_content(file_name); return {file_name, source_image.id, "", "", "", {}}; }; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, + std::nullopt, download_dir.path()); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -383,7 +384,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_fetch_copies_image_an query.release = file.url().toStdString(); query.query_type = mp::Query::Type::LocalFile; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()); EXPECT_TRUE(QFileInfo::exists(vm_image.image_path)); EXPECT_EQ(vm_image.id, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); @@ -397,7 +399,8 @@ TEST_F(ImageVault, invalid_custom_image_file_throws) query.release = "file://foo"; query.query_type = mp::Query::Type::LocalFile; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()), std::runtime_error); } @@ -409,7 +412,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(custom_image_url_downloads)) query.release = "http://www.foo.com/fake.img"; query.query_type = mp::Query::Type::HttpDownload; - vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(QString::fromStdString(query.release))); @@ -419,18 +423,18 @@ TEST_F(ImageVault, missing_downloaded_image_throws) { mpt::StubURLDownloader stub_url_downloader; mp::DefaultVMImageVault vault{hosts, &stub_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::CreateImageException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + mp::CreateImageException); } TEST_F(ImageVault, hash_mismatch_throws) { BadURLDownloader bad_url_downloader; mp::DefaultVMImageVault vault{hosts, &bad_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::CreateImageException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + mp::CreateImageException); } TEST_F(ImageVault, invalid_remote_throws) @@ -441,7 +445,8 @@ TEST_F(ImageVault, invalid_remote_throws) query.remote_name = "foo"; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()), std::runtime_error); } @@ -453,7 +458,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(invalid_image_alias_throw)) query.release = "foo"; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()), mp::CreateImageException); } @@ -466,8 +472,8 @@ TEST_F(ImageVault, valid_remote_and_alias_returns_valid_image_info) query.remote_name = "release"; mp::VMImage image; - EXPECT_NO_THROW( - image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt)); + EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path())); EXPECT_THAT(image.original_release, Eq("18.04 LTS")); EXPECT_THAT(image.id, Eq("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); @@ -482,8 +488,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i mp::Query query{instance_name, image_url, false, "", mp::Query::Type::HttpDownload}; mp::VMImage image; - EXPECT_NO_THROW( - image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt)); + EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path())); // Hash is based on image url EXPECT_THAT(image.id, Eq("7404f51c9b4f40312fa048a0ad36e07b74b718a2d3a5a08e8cca906c69059ddf")); @@ -493,7 +499,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i TEST_F(ImageVault, image_update_creates_new_dir_and_removes_old) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()); auto original_file{url_downloader.downloaded_files[0]}; auto original_absolute_path{QFileInfo(original_file).absolutePath()}; @@ -524,9 +531,9 @@ TEST_F(ImageVault, aborted_download_throws) running_url_downloader.abort_all_downloads(); - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::AbortedDownloadException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + mp::AbortedDownloadException); } TEST_F(ImageVault, minimum_image_size_returns_expected_size) @@ -537,8 +544,8 @@ TEST_F(ImageVault, minimum_image_size_returns_expected_size) auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -559,7 +566,8 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_minimum_size_returns_ query.release = file.url().toStdString(); query.query_type = mp::Query::Type::LocalFile; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -582,8 +590,8 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_crashes) auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("with output")))); @@ -596,8 +604,8 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_cannot_find_the_i auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("Could not find")))); @@ -610,8 +618,8 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_does_not_understa auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(HasSubstr("Could not obtain image's virtual size"))); @@ -682,7 +690,8 @@ TEST_F(ImageVault, updateImagesLogsWarningOnUnsupportedImage) { mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()); EXPECT_CALL(host, info_for(_)).WillOnce(Throw(mp::UnsupportedImageException(default_query.release))); @@ -699,7 +708,8 @@ TEST_F(ImageVault, updateImagesLogsWarningOnEmptyVault) { mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt); + vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()); EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); @@ -719,9 +729,9 @@ TEST_F(ImageVault, fetchLocalImageThrowsOnEmptyVault) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); - EXPECT_THROW( - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt), - mp::ImageNotFoundException); + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, + std::nullopt, download_dir.path()), + mp::ImageNotFoundException); } TEST_F(ImageVault, fetchRemoteImageThrowsOnMissingKernel) @@ -731,6 +741,7 @@ TEST_F(ImageVault, fetchRemoteImageThrowsOnMissingKernel) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + download_dir.path()), mp::ImageNotFoundException); } From de77e9b1a4ec9339bdc7f7a10af054e9503c5378 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 27 Jun 2023 12:50:29 +0100 Subject: [PATCH 392/627] [tests] Adapt LXD mount tests to instance_dir Account for instance directories in new tests, after rebasing. Signed-off-by: sharder996 --- tests/lxd/test_lxd_mount_handler.cpp | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/lxd/test_lxd_mount_handler.cpp b/tests/lxd/test_lxd_mount_handler.cpp index 8bd3efc103b..267041f26c1 100644 --- a/tests/lxd/test_lxd_mount_handler.cpp +++ b/tests/lxd/test_lxd_mount_handler.cpp @@ -22,9 +22,11 @@ #include "tests/mock_virtual_machine.h" #include "tests/stub_ssh_key_provider.h" #include "tests/stub_status_monitor.h" +#include "tests/temp_dir.h" #include "src/platform/backends/lxd/lxd_mount_handler.h" +#include #include #include #include @@ -40,8 +42,9 @@ class MockLXDVirtualMachine : public mpt::MockVirtualMachineT{desc, monitor, manager, base_url, bridge_name, storage_pool} + const QString& storage_pool, const mp::Path& instance_dir) + : mpt::MockVirtualMachineT{desc, monitor, manager, base_url, + bridge_name, storage_pool, instance_dir} { } }; @@ -73,6 +76,8 @@ struct LXDMountHandlerTestFixture : public testing::Test mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); + mpt::TempDir instance_dir; + const mpt::StubSSHKeyProvider key_provider; const mp::VMMount vm_mount{source_path, {}, {}, mp::VMMount::MountType::Native}; const QUrl base_url{"unix:///foo@1.0"}; @@ -108,7 +113,8 @@ 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}; + default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); @@ -121,7 +127,8 @@ 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}; + default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; 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)); @@ -134,7 +141,8 @@ 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}; + default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); EXPECT_CALL(lxd_vm, current_state) @@ -149,7 +157,8 @@ 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}; + default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); @@ -165,8 +174,8 @@ TEST_F(LXDMountHandlerTestFixture, stopThrowsIfVMIsRunning) TEST_P(LXDMountHandlerInvalidGidUidParameterTests, mountWithGidOrUid) { - mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; const auto& [host_gid, instance_gid, host_uid, instance_uid] = GetParam(); const mp::VMMount vm_mount{ source_path, {{host_gid, instance_gid}}, {{host_uid, instance_uid}}, mp::VMMount::MountType::Native}; @@ -182,8 +191,8 @@ INSTANTIATE_TEST_SUITE_P(mountWithGidOrUidInstantiation, LXDMountHandlerInvalidG TEST_P(LXDMountHandlerValidGidUidParameterTests, mountWithGidOrUid) { - mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, - bridge_name, default_storage_pool}; + mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, + bridge_name, default_storage_pool, instance_dir.path()}; const auto& [host_gid, host_uid] = GetParam(); const int default_instance_id = -1; const mp::VMMount vm_mount{source_path, From 574b4c871fc5466aaaccef4dd1a19965e128667a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:37:07 -0700 Subject: [PATCH 393/627] [image vault] rename parameter --- include/multipass/vm_image_vault.h | 2 +- src/daemon/default_vm_image_vault.h | 2 +- src/platform/backends/lxd/lxd_vm_image_vault.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/multipass/vm_image_vault.h b/include/multipass/vm_image_vault.h index f6977697e2c..e4a24f1bc06 100644 --- a/include/multipass/vm_image_vault.h +++ b/include/multipass/vm_image_vault.h @@ -82,7 +82,7 @@ class VMImageVault : private DisabledCopyMove virtual ~VMImageVault() = default; virtual VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum, const Path& download_dir) = 0; + const std::optional& checksum, const Path& save_dir) = 0; virtual void remove(const std::string& name) = 0; virtual bool has_record_for(const std::string& name) = 0; virtual void prune_expired_images() = 0; diff --git a/src/daemon/default_vm_image_vault.h b/src/daemon/default_vm_image_vault.h index d7a353d273e..1f8b07c0f6a 100644 --- a/src/daemon/default_vm_image_vault.h +++ b/src/daemon/default_vm_image_vault.h @@ -52,7 +52,7 @@ class DefaultVMImageVault final : public BaseVMImageVault VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, - const Path& download_dir) override; + const Path& save_dir) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; void prune_expired_images() override; diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.h b/src/platform/backends/lxd/lxd_vm_image_vault.h index ced3654ea7a..ed4f63c8568 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.h +++ b/src/platform/backends/lxd/lxd_vm_image_vault.h @@ -41,7 +41,7 @@ class LXDVMImageVault final : public BaseVMImageVault VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, - const Path&) override; + const Path& /* save_dir */) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; void prune_expired_images() override; From e169089e8740d805375bc764ecc91de6bda2785a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:39:11 -0700 Subject: [PATCH 394/627] [vm factory] move common code to base class --- .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 3 +-- src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 4 +--- src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 4 +--- .../backends/shared/base_virtual_machine_factory.cpp | 6 ++++++ src/platform/backends/shared/base_virtual_machine_factory.h | 2 ++ 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 2cdeadf7bf0..149c68ee562 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -150,8 +150,7 @@ void mp::LibVirtVirtualMachineFactory::remove_resources_for(const std::string& n libvirt_wrapper->virDomainUndefine(libvirt_wrapper->virDomainLookupByName(connection.get(), name.c_str())); - QDir instance_dir{get_instance_directory_name(name)}; - instance_dir.removeRecursively(); + BaseVirtualMachineFactory::remove_resources_for(name); } mp::VMImage mp::LibVirtVirtualMachineFactory::prepare_source_image(const VMImage& source_image) diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 13caf6767f0..64030435ba8 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -109,9 +109,7 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co void mp::LXDVirtualMachineFactory::remove_resources_for(const std::string& name) { mpl::log(mpl::Level::trace, category, fmt::format("No resources to remove for \"{}\"", name)); - - QDir instance_dir{get_instance_directory_name(name)}; - instance_dir.removeRecursively(); + BaseVirtualMachineFactory::remove_resources_for(name); } auto mp::LXDVirtualMachineFactory::prepare_source_image(const VMImage& source_image) -> VMImage diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 90b6bb9cd37..67647145c26 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -52,9 +52,7 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(c void mp::QemuVirtualMachineFactory::remove_resources_for(const std::string& name) { qemu_platform->remove_resources_for(name); - - QDir instance_dir{get_instance_directory_name(name)}; - instance_dir.removeRecursively(); + BaseVirtualMachineFactory::remove_resources_for(name); } mp::VMImage mp::QemuVirtualMachineFactory::prepare_source_image(const mp::VMImage& source_image) diff --git a/src/platform/backends/shared/base_virtual_machine_factory.cpp b/src/platform/backends/shared/base_virtual_machine_factory.cpp index 7566ec9a192..2a8291e6a3b 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.cpp +++ b/src/platform/backends/shared/base_virtual_machine_factory.cpp @@ -41,6 +41,12 @@ auto find_bridge_with(const NetworkContainer& networks, const std::string& membe mp::BaseVirtualMachineFactory::BaseVirtualMachineFactory(const Path& instances_dir) : instances_dir{instances_dir} {}; +void mp::BaseVirtualMachineFactory::remove_resources_for(const std::string& name) +{ + QDir instance_dir{get_instance_directory_name(name)}; + instance_dir.removeRecursively(); +} + void mp::BaseVirtualMachineFactory::configure(VirtualMachineDescription& vm_desc) { auto instance_dir{mpu::base_dir(vm_desc.image.image_path)}; diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 936174bf28f..c419ff5d645 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -37,6 +37,8 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory BaseVirtualMachineFactory() = default; BaseVirtualMachineFactory(const Path& instances_dir); + void remove_resources_for(const std::string& name) override; + FetchType fetch_type() override { return FetchType::ImageOnly; From f49786b6a13d7dd82255d7449d148db23cd86d67 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:41:08 -0700 Subject: [PATCH 395/627] [vm] remove extra constructor --- src/platform/backends/shared/base_virtual_machine.cpp | 2 -- src/platform/backends/shared/base_virtual_machine.h | 2 -- tests/test_base_virtual_machine.cpp | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6652ff2342f..2f64ec215bb 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -105,8 +105,6 @@ BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::s BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name, const mp::Path& instance_dir) : VirtualMachine(vm_name, instance_dir){}; -BaseVirtualMachine::BaseVirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name){}; - std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& key_provider) { std::vector all_ipv4; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 04ccd4e3a77..0e2d7aed3a8 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -72,8 +72,6 @@ class BaseVirtualMachine : public VirtualMachine std::vector get_childrens_names(const Snapshot* parent) const override; protected: - BaseVirtualMachine(const std::string& vm_name); - virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 088af34b76c..413071b7af5 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -34,7 +34,7 @@ namespace struct StubBaseVirtualMachine : public mp::BaseVirtualMachine { StubBaseVirtualMachine(const mp::VirtualMachine::State s = mp::VirtualMachine::State::off) - : mp::BaseVirtualMachine("stub") + : mp::BaseVirtualMachine("stub", "") { state = s; } From cb83f6a1c7761385dd639ff08e7cba6f79fc38ea Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:49:20 -0700 Subject: [PATCH 396/627] [vm image vault] only create vm directory when required --- src/daemon/default_vm_image_vault.cpp | 3 +++ .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 5 ++--- src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 2 +- src/platform/backends/shared/base_virtual_machine_factory.h | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index e0ebb97deab..13716f9a127 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -634,6 +634,7 @@ QString mp::DefaultVMImageVault::extract_image_from(const std::string& instance_ const ProgressMonitor& monitor, const mp::Path& dest_dir) { const auto name = QString::fromStdString(instance_name); + MP_UTILS.make_dir(dest_dir, name); QFileInfo file_info{source_image.image_path}; const auto image_name = file_info.fileName().remove(".xz"); const auto image_path = QDir(dest_dir).filePath(image_name); @@ -644,6 +645,8 @@ QString mp::DefaultVMImageVault::extract_image_from(const std::string& instance_ mp::VMImage mp::DefaultVMImageVault::image_instance_from(const std::string& instance_name, const VMImage& prepared_image, const mp::Path& dest_dir) { + MP_UTILS.make_dir(dest_dir, QString::fromStdString(instance_name)); + return {mp::vault::copy(prepared_image.image_path, dest_dir), prepared_image.id, prepared_image.original_release, diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 149c68ee562..ec4a6e1d394 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -108,8 +108,7 @@ auto make_libvirt_wrapper(const std::string& libvirt_object_path) mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir, const std::string& libvirt_object_path) - : BaseVirtualMachineFactory( - MP_UTILS.make_dir(QDir(data_dir, get_backend_directory_name()).filePath("vault"), "instances")), + : BaseVirtualMachineFactory(QDir(data_dir, get_backend_directory_name()).filePath("vault/instances")), libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)}, data_dir{data_dir}, bridge_name{enable_libvirt_network(data_dir, libvirt_wrapper)}, @@ -129,7 +128,7 @@ mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machin bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper, - MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); + get_instance_directory_name(desc.vm_name)); } mp::LibVirtVirtualMachineFactory::~LibVirtVirtualMachineFactory() diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 67647145c26..95fd9f79cdd 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -39,7 +39,7 @@ constexpr auto category = "qemu factory"; mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_dir) : BaseVirtualMachineFactory(QString{}), qemu_platform{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir)} { - instances_dir = MP_UTILS.make_dir(QDir(data_dir, get_backend_directory_name()).filePath("vault"), "instances"); + instances_dir = QDir(data_dir, get_backend_directory_name()).filePath("vault/instances"); } mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index c419ff5d645..c46342c29b5 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -51,7 +51,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory QString get_instance_directory_name(const std::string& name) const override { - return MP_UTILS.make_dir(multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name))); + return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); } void prepare_networking(std::vector& /*extra_interfaces*/) override From 2217517bafc6609ae67873510c573fc25856e996 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:49:55 -0700 Subject: [PATCH 397/627] [vm] make instance_dir protected --- include/multipass/virtual_machine.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index b547344241f..8f53f6e5ccc 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -101,13 +101,14 @@ class VirtualMachine : private DisabledCopyMove VirtualMachine::State state; const std::string vm_name; - const QDir instance_dir; std::condition_variable state_wait; std::mutex state_mutex; std::optional management_ip; bool shutdown_while_starting{false}; protected: + const QDir instance_dir; + VirtualMachine(VirtualMachine::State state, const std::string& vm_name, const Path& instance_dir) : state{state}, vm_name{vm_name}, instance_dir{QDir{instance_dir}} {}; VirtualMachine(const std::string& vm_name, const Path& instance_dir) From a30eb6245af994ad2363522fca11ff4e139ba4e2 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:53:00 -0700 Subject: [PATCH 398/627] [vm factory] use mp::Path for clarity on return parameter usage --- include/multipass/virtual_machine_factory.h | 2 +- src/platform/backends/shared/base_virtual_machine_factory.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index f2144a7608c..963224b3df5 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -60,7 +60,7 @@ class VirtualMachineFactory : private DisabledCopyMove virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0; virtual void hypervisor_health_check() = 0; virtual QString get_backend_directory_name() const = 0; - virtual QString get_instance_directory_name(const std::string& name) const = 0; + virtual Path get_instance_directory_name(const std::string& name) const = 0; // postcondition: return directory exists virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_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 c46342c29b5..85a86f74194 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -49,7 +49,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory return {}; }; - QString get_instance_directory_name(const std::string& name) const override + Path get_instance_directory_name(const std::string& name) const override { return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); } From 0041084ad3bdeeb416c3f23410da93a8b82a1b2f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 30 Aug 2023 20:54:49 -0700 Subject: [PATCH 399/627] [vm factory] make constructor explicit --- src/platform/backends/qemu/qemu_virtual_machine_factory.h | 2 ++ src/platform/backends/shared/base_virtual_machine_factory.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index f11de47c6d2..3c36c48821c 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -44,6 +44,8 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory std::vector networks() const override; private: + explicit QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir); + QemuPlatform::UPtr qemu_platform; }; } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 85a86f74194..fa91ca0fa37 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -35,7 +35,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory { public: BaseVirtualMachineFactory() = default; - BaseVirtualMachineFactory(const Path& instances_dir); + explicit BaseVirtualMachineFactory(const Path& instances_dir); void remove_resources_for(const std::string& name) override; From c40f50dd00927bfe7af815e7e5276310d6ffdc1f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 13 Sep 2023 12:23:22 -0500 Subject: [PATCH 400/627] [vm factory] use virtual{,-impl} architecture to remove duplicate function calls to base class --- .../libvirt/libvirt_virtual_machine_factory.cpp | 4 +--- .../libvirt/libvirt_virtual_machine_factory.h | 4 +++- .../backends/lxd/lxd_virtual_machine_factory.cpp | 3 +-- .../backends/lxd/lxd_virtual_machine_factory.h | 4 +++- .../backends/qemu/qemu_virtual_machine_factory.cpp | 3 +-- .../backends/qemu/qemu_virtual_machine_factory.h | 4 +++- .../backends/shared/base_virtual_machine_factory.cpp | 6 ------ .../backends/shared/base_virtual_machine_factory.h | 11 ++++++++++- tests/stub_virtual_machine_factory.h | 2 +- tests/test_base_virtual_machine_factory.cpp | 2 +- 10 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index ec4a6e1d394..b23798e8313 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -143,13 +143,11 @@ mp::LibVirtVirtualMachineFactory::~LibVirtVirtualMachineFactory() } } -void mp::LibVirtVirtualMachineFactory::remove_resources_for(const std::string& name) +void mp::LibVirtVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { auto connection = LibVirtVirtualMachine::open_libvirt_connection(libvirt_wrapper); libvirt_wrapper->virDomainUndefine(libvirt_wrapper->virDomainLookupByName(connection.get(), name.c_str())); - - BaseVirtualMachineFactory::remove_resources_for(name); } mp::VMImage mp::LibVirtVirtualMachineFactory::prepare_source_image(const VMImage& source_image) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h index 96b4f2d33d7..f26fbc9a9a2 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.h @@ -37,7 +37,6 @@ class LibVirtVirtualMachineFactory final : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) override; - void remove_resources_for(const std::string& name) override; VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; @@ -46,6 +45,9 @@ class LibVirtVirtualMachineFactory final : public BaseVirtualMachineFactory // Making this public makes this modifiable which is necessary for testing LibvirtWrapper::UPtr libvirt_wrapper; +protected: + void remove_resources_for_impl(const std::string& name) override; + private: const Path data_dir; std::string bridge_name; diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 64030435ba8..0c287037942 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -106,10 +106,9 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); } -void mp::LXDVirtualMachineFactory::remove_resources_for(const std::string& name) +void mp::LXDVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { mpl::log(mpl::Level::trace, category, fmt::format("No resources to remove for \"{}\"", name)); - BaseVirtualMachineFactory::remove_resources_for(name); } auto mp::LXDVirtualMachineFactory::prepare_source_image(const VMImage& source_image) -> VMImage diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.h b/src/platform/backends/lxd/lxd_virtual_machine_factory.h index cce0de51a35..94e190bf0d3 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.h +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.h @@ -37,7 +37,6 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory void prepare_networking(std::vector& extra_interfaces) override; VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) override; - void remove_resources_for(const std::string& name) override; VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; @@ -53,6 +52,9 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory std::vector networks() const override; +protected: + void remove_resources_for_impl(const std::string& name) override; + protected: std::string create_bridge_with(const NetworkInterfaceInfo& interface) override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 95fd9f79cdd..528e73aa9b5 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -49,10 +49,9 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(c MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); } -void mp::QemuVirtualMachineFactory::remove_resources_for(const std::string& name) +void mp::QemuVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { qemu_platform->remove_resources_for(name); - BaseVirtualMachineFactory::remove_resources_for(name); } mp::VMImage mp::QemuVirtualMachineFactory::prepare_source_image(const mp::VMImage& source_image) diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index 3c36c48821c..457b3f39d8e 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -35,7 +35,6 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory VirtualMachine::UPtr create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) override; - void remove_resources_for(const std::string& name) override; VMImage prepare_source_image(const VMImage& source_image) override; void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) override; void hypervisor_health_check() override; @@ -43,6 +42,9 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory QString get_backend_directory_name() const override; std::vector networks() const override; +protected: + void remove_resources_for_impl(const std::string& name) override; + private: explicit QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir); diff --git a/src/platform/backends/shared/base_virtual_machine_factory.cpp b/src/platform/backends/shared/base_virtual_machine_factory.cpp index 2a8291e6a3b..7566ec9a192 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.cpp +++ b/src/platform/backends/shared/base_virtual_machine_factory.cpp @@ -41,12 +41,6 @@ auto find_bridge_with(const NetworkContainer& networks, const std::string& membe mp::BaseVirtualMachineFactory::BaseVirtualMachineFactory(const Path& instances_dir) : instances_dir{instances_dir} {}; -void mp::BaseVirtualMachineFactory::remove_resources_for(const std::string& name) -{ - QDir instance_dir{get_instance_directory_name(name)}; - instance_dir.removeRecursively(); -} - void mp::BaseVirtualMachineFactory::configure(VirtualMachineDescription& vm_desc) { auto instance_dir{mpu::base_dir(vm_desc.image.image_path)}; diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index fa91ca0fa37..c3fae25508e 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -37,7 +37,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory BaseVirtualMachineFactory() = default; explicit BaseVirtualMachineFactory(const Path& instances_dir); - void remove_resources_for(const std::string& name) override; + void remove_resources_for(const std::string& name) final; FetchType fetch_type() override { @@ -86,8 +86,17 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory virtual void prepare_interface(NetworkInterface& net, std::vector& host_nets, const std::string& bridge_type); + virtual void remove_resources_for_impl(const std::string& name) = 0; + Path instances_dir; }; } // namespace multipass +inline void multipass::BaseVirtualMachineFactory::remove_resources_for(const std::string& name) +{ + remove_resources_for_impl(name); + QDir instance_dir{get_instance_directory_name(name)}; + instance_dir.removeRecursively(); +} + #endif // MULTIPASS_BASE_VIRTUAL_MACHINE_FACTORY_H diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index 633e8b21ccd..abe8a7d8f33 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -35,7 +35,7 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory return std::make_unique(); } - void remove_resources_for(const std::string& name) override + void remove_resources_for_impl(const std::string& name) override { } diff --git a/tests/test_base_virtual_machine_factory.cpp b/tests/test_base_virtual_machine_factory.cpp index cab6d46a96a..2e41e375264 100644 --- a/tests/test_base_virtual_machine_factory.cpp +++ b/tests/test_base_virtual_machine_factory.cpp @@ -39,7 +39,6 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory { MOCK_METHOD(mp::VirtualMachine::UPtr, create_virtual_machine, (const mp::VirtualMachineDescription&, mp::VMStatusMonitor&), (override)); - MOCK_METHOD(void, remove_resources_for, (const std::string&), (override)); MOCK_METHOD(mp::VMImage, prepare_source_image, (const mp::VMImage&), (override)); MOCK_METHOD(void, prepare_instance_image, (const mp::VMImage&, const mp::VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); @@ -51,6 +50,7 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory (mp::NetworkInterface & net, std::vector& host_nets, const std::string& bridge_type), (override)); + MOCK_METHOD(void, remove_resources_for_impl, (const std::string&), (override)); std::string base_create_bridge_with(const mp::NetworkInterfaceInfo& interface) { From 073f39e769e3baa3c3a3374aa798d2bb026811de Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 14 Sep 2023 17:18:55 +0100 Subject: [PATCH 401/627] [qemu] Fix circular dependency in initialization --- .../backends/qemu/qemu_virtual_machine_factory.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 528e73aa9b5..05beb849dd2 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -34,12 +34,20 @@ namespace mpl = multipass::logging; namespace { constexpr auto category = "qemu factory"; +mp::Path derive_instances_dir(mp::QemuPlatform& qemu_platform, const mp::Path& data_dir) +{ + return QDir(data_dir, qemu_platform.get_directory_name()).filePath("vault/instances"); +} } // namespace mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(const mp::Path& data_dir) - : BaseVirtualMachineFactory(QString{}), qemu_platform{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir)} + : QemuVirtualMachineFactory{MP_QEMU_PLATFORM_FACTORY.make_qemu_platform(data_dir), data_dir} +{ +} + +mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const mp::Path& data_dir) + : BaseVirtualMachineFactory(derive_instances_dir(*qemu_platform, data_dir)), qemu_platform{std::move(qemu_platform)} { - instances_dir = QDir(data_dir, get_backend_directory_name()).filePath("vault/instances"); } mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, From d92eaadc8df4f94e81be82ff023d9f62931ba4b1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 14 Sep 2023 17:19:42 +0100 Subject: [PATCH 402/627] [qemu] Remove unnecessary `explicit` Remove `explicit` keyword from constructor with two parameters. --- src/platform/backends/qemu/qemu_virtual_machine_factory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.h b/src/platform/backends/qemu/qemu_virtual_machine_factory.h index 457b3f39d8e..bc09eedf16c 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.h +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.h @@ -46,7 +46,7 @@ class QemuVirtualMachineFactory final : public BaseVirtualMachineFactory void remove_resources_for_impl(const std::string& name) override; private: - explicit QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir); + QemuVirtualMachineFactory(QemuPlatform::UPtr qemu_platform, const Path& data_dir); QemuPlatform::UPtr qemu_platform; }; From 3e60a3aa1c929bca48d3e886650abfed96a0cdde Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 14 Sep 2023 17:29:59 +0100 Subject: [PATCH 403/627] [qemu] Make QemuPlatform::get_directory_name const --- src/platform/backends/qemu/qemu_platform.h | 2 +- src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 2 +- tests/qemu/mock_qemu_platform.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/qemu/qemu_platform.h b/src/platform/backends/qemu/qemu_platform.h index 953a161e722..ae8f37d049a 100644 --- a/src/platform/backends/qemu/qemu_platform.h +++ b/src/platform/backends/qemu/qemu_platform.h @@ -50,7 +50,7 @@ class QemuPlatform : private DisabledCopyMove return {}; }; virtual QStringList vm_platform_args(const VirtualMachineDescription& vm_desc) = 0; - virtual QString get_directory_name() + virtual QString get_directory_name() const { return {}; }; diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 05beb849dd2..b2bb22859e8 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -34,7 +34,7 @@ namespace mpl = multipass::logging; namespace { constexpr auto category = "qemu factory"; -mp::Path derive_instances_dir(mp::QemuPlatform& qemu_platform, const mp::Path& data_dir) +mp::Path derive_instances_dir(const mp::QemuPlatform& qemu_platform, const mp::Path& data_dir) { return QDir(data_dir, qemu_platform.get_directory_name()).filePath("vault/instances"); } diff --git a/tests/qemu/mock_qemu_platform.h b/tests/qemu/mock_qemu_platform.h index 9ae08016b6d..8a84e6b5fe1 100644 --- a/tests/qemu/mock_qemu_platform.h +++ b/tests/qemu/mock_qemu_platform.h @@ -41,7 +41,7 @@ struct MockQemuPlatform : public QemuPlatform MOCK_METHOD(void, platform_health_check, (), (override)); MOCK_METHOD(QStringList, vmstate_platform_args, (), (override)); MOCK_METHOD(QStringList, vm_platform_args, (const VirtualMachineDescription&), (override)); - MOCK_METHOD(QString, get_directory_name, (), (override)); + MOCK_METHOD(QString, get_directory_name, (), (const, override)); }; struct MockQemuPlatformFactory : public QemuPlatformFactory From 3157510db1651f520ceead07efa26ab4b4ee4ad4 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 14 Sep 2023 13:52:09 -0500 Subject: [PATCH 404/627] [tests] update tests to properly reference an instance's directory and modified vm factory --- tests/qemu/test_qemu_backend.cpp | 10 +++-- tests/test_alias_dict.cpp | 3 ++ tests/test_image_vault.cpp | 73 ++++++++++++++++---------------- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 7383fb63098..bc582ed9d2c 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -733,11 +733,13 @@ TEST_F(QemuBackend, get_backend_directory_name_calls_qemu_platform) bool get_directory_name_called{false}; const QString backend_dir_name{"foo"}; - EXPECT_CALL(*mock_qemu_platform, get_directory_name()).WillOnce([&get_directory_name_called, &backend_dir_name] { - get_directory_name_called = true; + EXPECT_CALL(*mock_qemu_platform, get_directory_name()) + .Times(2) + .WillRepeatedly([&get_directory_name_called, &backend_dir_name] { + get_directory_name_called = true; - return backend_dir_name; - }); + return backend_dir_name; + }); EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { return std::move(mock_qemu_platform); diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index 974a61dfb43..f05e48dd7a0 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -584,6 +584,9 @@ TEST_P(DaemonAliasTestsuite, purge_removes_purged_instance_aliases_and_scripts) EXPECT_CALL(*mock_image_vault, has_record_for(_)).WillRepeatedly(Return(true)); config_builder.vault = std::move(mock_image_vault); + auto mock_factory = use_a_mock_vm_factory(); + + EXPECT_CALL(*mock_factory, remove_resources_for(_)).WillRepeatedly(Return()); std::string json_contents = make_instance_json(std::nullopt, {}, {"primary"}); diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index c78a569cfa1..047875efd2a 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -170,6 +170,7 @@ struct ImageVault : public testing::Test mpt::TempDir data_dir; mpt::TempDir download_dir; std::string instance_name{"valley-pied-piper"}; + QString instance_dir = download_dir.filePath(QString::fromStdString(instance_name)); mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; }; } // namespace @@ -178,7 +179,7 @@ TEST_F(ImageVault, downloads_image) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(host.image.url())); @@ -188,7 +189,7 @@ TEST_F(ImageVault, returned_image_contains_instance_name) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_TRUE(vm_image.image_path.contains(QString::fromStdString(instance_name))); } @@ -203,7 +204,7 @@ TEST_F(ImageVault, calls_prepare) return source_image; }; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_TRUE(prepare_called); } @@ -217,9 +218,9 @@ TEST_F(ImageVault, records_instanced_images) return source_image; }; auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -236,12 +237,12 @@ TEST_F(ImageVault, caches_prepared_images) return source_image; }; auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, download_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -260,11 +261,11 @@ TEST_F(ImageVault, remembers_instance_images) mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -281,13 +282,14 @@ TEST_F(ImageVault, remembers_prepared_images) mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + auto vm_image2 = + another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt, + download_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -309,7 +311,7 @@ TEST_F(ImageVault, uses_image_from_prepare) mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); const auto image_data = mp::utils::contents_of(vm_image.image_path); EXPECT_THAT(image_data, StrEq(expected_data)); @@ -328,7 +330,7 @@ TEST_F(ImageVault, image_purged_expired) return {file_name, source_image.id, "", "", "", {}}; }; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -349,7 +351,7 @@ TEST_F(ImageVault, image_exists_not_expired) return {file_name, source_image.id, "", "", "", {}}; }; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -385,7 +387,7 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_fetch_copies_image_an query.query_type = mp::Query::Type::LocalFile; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()); + instance_dir); EXPECT_TRUE(QFileInfo::exists(vm_image.image_path)); EXPECT_EQ(vm_image.id, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); @@ -400,7 +402,7 @@ TEST_F(ImageVault, invalid_custom_image_file_throws) query.query_type = mp::Query::Type::LocalFile; EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()), + instance_dir), std::runtime_error); } @@ -412,8 +414,7 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(custom_image_url_downloads)) query.release = "http://www.foo.com/fake.img"; query.query_type = mp::Query::Type::HttpDownload; - vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()); + vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(QString::fromStdString(query.release))); @@ -424,7 +425,7 @@ TEST_F(ImageVault, missing_downloaded_image_throws) mpt::StubURLDownloader stub_url_downloader; mp::DefaultVMImageVault vault{hosts, &stub_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, instance_dir), mp::CreateImageException); } @@ -433,7 +434,7 @@ TEST_F(ImageVault, hash_mismatch_throws) BadURLDownloader bad_url_downloader; mp::DefaultVMImageVault vault{hosts, &bad_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, instance_dir), mp::CreateImageException); } @@ -446,7 +447,7 @@ TEST_F(ImageVault, invalid_remote_throws) query.remote_name = "foo"; EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()), + instance_dir), std::runtime_error); } @@ -459,7 +460,7 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(invalid_image_alias_throw)) query.release = "foo"; EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()), + instance_dir), mp::CreateImageException); } @@ -473,7 +474,7 @@ TEST_F(ImageVault, valid_remote_and_alias_returns_valid_image_info) mp::VMImage image; EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path())); + std::nullopt, instance_dir)); EXPECT_THAT(image.original_release, Eq("18.04 LTS")); EXPECT_THAT(image.id, Eq("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); @@ -489,7 +490,7 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i mp::VMImage image; EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path())); + std::nullopt, instance_dir)); // Hash is based on image url EXPECT_THAT(image.id, Eq("7404f51c9b4f40312fa048a0ad36e07b74b718a2d3a5a08e8cca906c69059ddf")); @@ -500,7 +501,7 @@ TEST_F(ImageVault, image_update_creates_new_dir_and_removes_old) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()); + instance_dir); auto original_file{url_downloader.downloaded_files[0]}; auto original_absolute_path{QFileInfo(original_file).absolutePath()}; @@ -532,7 +533,7 @@ TEST_F(ImageVault, aborted_download_throws) running_url_downloader.abort_all_downloads(); EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, instance_dir), mp::AbortedDownloadException); } @@ -545,7 +546,7 @@ TEST_F(ImageVault, minimum_image_size_returns_expected_size) mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -567,7 +568,7 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_minimum_size_returns_ query.query_type = mp::Query::Type::LocalFile; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()); + instance_dir); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -591,7 +592,7 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_crashes) mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("with output")))); @@ -605,7 +606,7 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_cannot_find_the_i mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("Could not find")))); @@ -619,7 +620,7 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_does_not_understa mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(HasSubstr("Could not obtain image's virtual size"))); @@ -691,7 +692,7 @@ TEST_F(ImageVault, updateImagesLogsWarningOnUnsupportedImage) mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()); + instance_dir); EXPECT_CALL(host, info_for(_)).WillOnce(Throw(mp::UnsupportedImageException(default_query.release))); @@ -709,7 +710,7 @@ TEST_F(ImageVault, updateImagesLogsWarningOnEmptyVault) mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()); + instance_dir); EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); @@ -730,7 +731,7 @@ TEST_F(ImageVault, fetchLocalImageThrowsOnEmptyVault) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, instance_dir), mp::ImageNotFoundException); } @@ -742,6 +743,6 @@ TEST_F(ImageVault, fetchRemoteImageThrowsOnMissingKernel) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, - download_dir.path()), + instance_dir), mp::ImageNotFoundException); } From 832099ef0d636e5faf8aa8d1befdc40e2e1c968f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 14 Sep 2023 14:16:42 -0500 Subject: [PATCH 405/627] [vm factory] make instance variable private --- src/platform/backends/shared/base_virtual_machine_factory.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index c3fae25508e..1215ace86c4 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -88,6 +88,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory virtual void remove_resources_for_impl(const std::string& name) = 0; +private: Path instances_dir; }; } // namespace multipass From ae42fcd13d6f4bc5c8f0586564f71d8a27dec315 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 14 Sep 2023 14:17:02 -0500 Subject: [PATCH 406/627] [lxd vm image vault] label unused parameter --- src/platform/backends/lxd/lxd_vm_image_vault.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.cpp b/src/platform/backends/lxd/lxd_vm_image_vault.cpp index 3f00e97e6e6..ab8669fd2ee 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.cpp +++ b/src/platform/backends/lxd/lxd_vm_image_vault.cpp @@ -160,7 +160,7 @@ mp::LXDVMImageVault::LXDVMImageVault(std::vector image_hosts, URLD mp::VMImage mp::LXDVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, - const mp::Path&) + const mp::Path& /* save_dir */) { // Look for an already existing instance and get its image info try From fc4cc50f6ae992047904773b48242058b5e51496 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 14 Sep 2023 14:28:00 -0500 Subject: [PATCH 407/627] linting changes --- include/multipass/virtual_machine_factory.h | 3 ++- src/daemon/daemon.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 963224b3df5..87eaf799906 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -60,7 +60,8 @@ class VirtualMachineFactory : private DisabledCopyMove virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0; virtual void hypervisor_health_check() = 0; virtual QString get_backend_directory_name() const = 0; - virtual Path get_instance_directory_name(const std::string& name) const = 0; // postcondition: return directory exists + virtual Path + get_instance_directory_name(const std::string& name) const = 0; // postcondition: return directory exists virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index cd36467c919..dae7bde88a2 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2563,8 +2563,8 @@ try { reply_msg(server, fmt::format("Taking snapshot before restoring {}", instance_name)); - const auto snapshot = vm_ptr->take_snapshot(vm_specs, "", - fmt::format("Before restoring {}", request->snapshot())); + const auto snapshot = + vm_ptr->take_snapshot(vm_specs, "", fmt::format("Before restoring {}", request->snapshot())); reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), /* sticky = */ true); From f50673fdac5320a115de99cb012932592a1251fb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 18 Sep 2023 13:12:37 +0100 Subject: [PATCH 408/627] [vms] Remove artificial constructors for tests Remove artificial constructors from base VM and factory classes. Let stub classes pass in stub arguments when needed. --- include/multipass/virtual_machine.h | 1 - .../backends/qemu/qemu_virtual_machine.h | 3 ++- .../shared/base_virtual_machine_factory.h | 1 - tests/lxd/test_lxd_mount_handler.cpp | 21 +++++++------------ tests/mock_virtual_machine.h | 11 +++++++++- tests/stub_virtual_machine.h | 2 +- tests/stub_virtual_machine_factory.h | 12 +++++++++++ tests/test_base_virtual_machine_factory.cpp | 11 ++++++++++ 8 files changed, 44 insertions(+), 18 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 8f53f6e5ccc..589174bf7f8 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -113,7 +113,6 @@ class VirtualMachine : private DisabledCopyMove : state{state}, vm_name{vm_name}, instance_dir{QDir{instance_dir}} {}; VirtualMachine(const std::string& vm_name, const Path& instance_dir) : VirtualMachine(State::off, vm_name, instance_dir){}; - VirtualMachine(const std::string& vm_name) : VirtualMachine(vm_name, ""){}; }; } // namespace multipass #endif // MULTIPASS_VIRTUAL_MACHINE_H diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 631c395d6d6..dfcfd7d2306 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -70,7 +70,8 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine void on_reset_network(); protected: - QemuVirtualMachine(const std::string& name) : BaseVirtualMachine{name, ""} + // TODO remove this, the onus of composing a VM of stubs should be on the stub VMs + QemuVirtualMachine(const std::string& name, const mp::Path& instance_dir) : BaseVirtualMachine{name, instance_dir} { } diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 1215ace86c4..f713fa4ce42 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -34,7 +34,6 @@ constexpr auto log_category = "base factory"; class BaseVirtualMachineFactory : public VirtualMachineFactory { public: - BaseVirtualMachineFactory() = default; explicit BaseVirtualMachineFactory(const Path& instances_dir); void remove_resources_for(const std::string& name) final; diff --git a/tests/lxd/test_lxd_mount_handler.cpp b/tests/lxd/test_lxd_mount_handler.cpp index 267041f26c1..8c6608c79da 100644 --- a/tests/lxd/test_lxd_mount_handler.cpp +++ b/tests/lxd/test_lxd_mount_handler.cpp @@ -42,9 +42,8 @@ class MockLXDVirtualMachine : public mpt::MockVirtualMachineT{desc, monitor, manager, base_url, - bridge_name, storage_pool, instance_dir} + const QString& storage_pool) + : mpt::MockVirtualMachineT{desc, monitor, manager, base_url, bridge_name, storage_pool} { } }; @@ -76,8 +75,6 @@ struct LXDMountHandlerTestFixture : public testing::Test mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); - mpt::TempDir instance_dir; - const mpt::StubSSHKeyProvider key_provider; const mp::VMMount vm_mount{source_path, {}, {}, mp::VMMount::MountType::Native}; const QUrl base_url{"unix:///foo@1.0"}; @@ -113,8 +110,7 @@ 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, instance_dir.path()}; + default_description, stub_monitor, &mock_network_access_manager, base_url, bridge_name, default_storage_pool}; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); @@ -127,8 +123,7 @@ 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, instance_dir.path()}; + default_description, stub_monitor, &mock_network_access_manager, base_url, bridge_name, default_storage_pool}; 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)); @@ -141,8 +136,7 @@ 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, instance_dir.path()}; + default_description, stub_monitor, &mock_network_access_manager, base_url, bridge_name, default_storage_pool}; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); EXPECT_CALL(lxd_vm, current_state) @@ -157,8 +151,7 @@ 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, instance_dir.path()}; + default_description, stub_monitor, &mock_network_access_manager, base_url, bridge_name, default_storage_pool}; mp::LXDMountHandler lxd_mount_handler(&mock_network_access_manager, &lxd_vm, &key_provider, target_path, vm_mount); @@ -174,6 +167,7 @@ TEST_F(LXDMountHandlerTestFixture, stopThrowsIfVMIsRunning) TEST_P(LXDMountHandlerInvalidGidUidParameterTests, mountWithGidOrUid) { + mpt::TempDir instance_dir{}; mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, bridge_name, default_storage_pool, instance_dir.path()}; const auto& [host_gid, instance_gid, host_uid, instance_uid] = GetParam(); @@ -191,6 +185,7 @@ INSTANTIATE_TEST_SUITE_P(mountWithGidOrUidInstantiation, LXDMountHandlerInvalidG TEST_P(LXDMountHandlerValidGidUidParameterTests, mountWithGidOrUid) { + mpt::TempDir instance_dir{}; mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, bridge_name, default_storage_pool, instance_dir.path()}; const auto& [host_gid, host_uid] = GetParam(); diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index a3d15955010..a012b47b325 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -19,6 +19,7 @@ #define MULTIPASS_MOCK_VIRTUAL_MACHINE_H #include "common.h" +#include "temp_dir.h" #include #include @@ -34,7 +35,13 @@ template - MockVirtualMachineT(Args&&... args) : T{std::forward(args)...} + MockVirtualMachineT(Args&&... args) : MockVirtualMachineT{std::make_unique(), std::forward(args)...} + { + } + + template + MockVirtualMachineT(std::unique_ptr&& tmp_dir, Args&&... args) + : T{std::forward(args)..., tmp_dir->path()} { ON_CALL(*this, current_state()).WillByDefault(Return(multipass::VirtualMachine::State::off)); ON_CALL(*this, ssh_port()).WillByDefault(Return(42)); @@ -76,6 +83,8 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (), (override)); MOCK_METHOD(std::vector, get_childrens_names, (const Snapshot*), (const, override)); + + std::unique_ptr tmp_dir; }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 7e0b331ac2e..400f312826c 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -32,7 +32,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } - StubVirtualMachine(const std::string& name) : VirtualMachine{name} + StubVirtualMachine(const std::string& name) : VirtualMachine{name, "fake_dir"} { } diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index abe8a7d8f33..b66ee2b1c4c 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -20,6 +20,7 @@ #include "stub_virtual_machine.h" #include "stub_vm_image_vault.h" +#include "temp_dir.h" #include @@ -29,6 +30,15 @@ namespace test { struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory { + StubVirtualMachineFactory() : StubVirtualMachineFactory{std::make_unique()} + { + } + + StubVirtualMachineFactory(std::unique_ptr&& tmp_dir) + : mp::BaseVirtualMachineFactory{tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + { + } + multipass::VirtualMachine::UPtr create_virtual_machine(const multipass::VirtualMachineDescription&, multipass::VMStatusMonitor&) override { @@ -79,6 +89,8 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory { return std::make_unique(); } + + std::unique_ptr tmp_dir; }; } } diff --git a/tests/test_base_virtual_machine_factory.cpp b/tests/test_base_virtual_machine_factory.cpp index 2e41e375264..25a9c624a57 100644 --- a/tests/test_base_virtual_machine_factory.cpp +++ b/tests/test_base_virtual_machine_factory.cpp @@ -37,6 +37,15 @@ namespace { struct MockBaseFactory : mp::BaseVirtualMachineFactory { + MockBaseFactory() : MockBaseFactory{std::make_unique()} + { + } + + MockBaseFactory(std::unique_ptr&& tmp_dir) + : mp::BaseVirtualMachineFactory{tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + { + } + MOCK_METHOD(mp::VirtualMachine::UPtr, create_virtual_machine, (const mp::VirtualMachineDescription&, mp::VMStatusMonitor&), (override)); MOCK_METHOD(mp::VMImage, prepare_source_image, (const mp::VMImage&), (override)); @@ -68,6 +77,8 @@ struct MockBaseFactory : mp::BaseVirtualMachineFactory { return mp::BaseVirtualMachineFactory::prepare_interface(net, host_nets, bridge_type); // protected } + + std::unique_ptr tmp_dir; }; struct BaseFactory : public Test From f14bf7bdd9c58295b1b4d498cba653f2161f8e7e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 18 Sep 2023 20:35:15 +0100 Subject: [PATCH 409/627] [tests] Use a temporary dir in remaining stub VMs --- tests/stub_virtual_machine.h | 10 +++++++++- tests/stub_virtual_machine_factory.h | 2 +- tests/test_base_virtual_machine.cpp | 12 ++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 400f312826c..1e36d78d76f 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -20,6 +20,8 @@ #include "stub_mount_handler.h" #include "stub_snapshot.h" +#include "temp_dir.h" + #include namespace multipass @@ -32,7 +34,12 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } - StubVirtualMachine(const std::string& name) : VirtualMachine{name, "fake_dir"} + StubVirtualMachine(const std::string& name) : StubVirtualMachine{name, std::make_unique()} + { + } + + StubVirtualMachine(const std::string& name, std::unique_ptr&& tmp_dir) + : VirtualMachine{name, tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} { } @@ -161,6 +168,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine } StubSnapshot snapshot; + std::unique_ptr tmp_dir; }; } // namespace test } // namespace multipass diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index b66ee2b1c4c..0980bd438fb 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -75,7 +75,7 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory QString get_instance_directory_name(const std::string& name) const override { - return {}; + return tmp_dir->path(); } QString get_backend_version_string() const override diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 413071b7af5..c2ac3c920e5 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -18,6 +18,7 @@ #include "common.h" #include "dummy_ssh_key_provider.h" #include "mock_ssh_test_fixture.h" +#include "temp_dir.h" #include @@ -33,12 +34,17 @@ namespace { struct StubBaseVirtualMachine : public mp::BaseVirtualMachine { - StubBaseVirtualMachine(const mp::VirtualMachine::State s = mp::VirtualMachine::State::off) - : mp::BaseVirtualMachine("stub", "") + StubBaseVirtualMachine(mp::VirtualMachine::State s = mp::VirtualMachine::State::off) + : StubBaseVirtualMachine{s, std::make_unique()} { state = s; } + StubBaseVirtualMachine(mp::VirtualMachine::State s, std::unique_ptr&& tmp_dir) + : mp::BaseVirtualMachine{"stub", tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + { + } + void stop() override { state = mp::VirtualMachine::State::off; @@ -125,6 +131,8 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine { return nullptr; } + + std::unique_ptr&& tmp_dir; }; struct BaseVM : public Test From 55bfab169e03be652576c4c69c833c7a09665e99 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 19 Sep 2023 08:17:12 -0500 Subject: [PATCH 410/627] rename param to be more descriptive --- src/daemon/default_vm_image_vault.cpp | 12 ++++---- tests/blueprint_test_lambdas.cpp | 4 +-- tests/lxd/test_lxd_image_vault.cpp | 40 +++++++++++++-------------- tests/test_image_vault.cpp | 8 +++--- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 13716f9a127..1719db0e220 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -254,7 +254,7 @@ mp::DefaultVMImageVault::~DefaultVMImageVault() mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, - const mp::Path& download_dir) + const mp::Path& save_dir) { { std::lock_guard lock{fetch_mutex}; @@ -282,11 +282,11 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (source_image.image_path.endsWith(".xz")) { - source_image.image_path = extract_image_from(query.name, source_image, monitor, download_dir); + source_image.image_path = extract_image_from(query.name, source_image, monitor, save_dir); } else { - source_image = image_instance_from(query.name, source_image, download_dir); + source_image = image_instance_from(query.name, source_image, save_dir); } vm_image = prepare(source_image); @@ -327,7 +327,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (last_modified.isValid() && (last_modified.toString().toStdString() == record.image.release_date)) { - return finalize_image_records(query, record.image, id, download_dir); + return finalize_image_records(query, record.image, id, save_dir); } } @@ -389,7 +389,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co const auto prepared_image = record.second.image; try { - return finalize_image_records(query, prepared_image, record.first, download_dir); + return finalize_image_records(query, prepared_image, record.first, save_dir); } catch (const std::exception& e) { @@ -427,7 +427,7 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co auto prepared_image = future.result(); std::lock_guard lock{fetch_mutex}; in_progress_image_fetches.erase(id); - return finalize_image_records(query, prepared_image, id, download_dir); + return finalize_image_records(query, prepared_image, id, save_dir); } catch (const std::exception&) { diff --git a/tests/blueprint_test_lambdas.cpp b/tests/blueprint_test_lambdas.cpp index 542ba9d9f01..91d2bb8a533 100644 --- a/tests/blueprint_test_lambdas.cpp +++ b/tests/blueprint_test_lambdas.cpp @@ -41,7 +41,7 @@ mpt::fetch_image_lambda(const std::string& release, const std::string& remote, c return [&release, &remote, must_have_checksum]( const mp::FetchType& fetch_type, const mp::Query& query, const mp::VMImageVault::PrepareAction& prepare, const mp::ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, - const mp::Path& download_dir) { + const mp::Path& save_dir) { EXPECT_EQ(query.release, release); if (remote.empty()) { @@ -57,7 +57,7 @@ mpt::fetch_image_lambda(const std::string& release, const std::string& remote, c EXPECT_NE(checksum, std::nullopt); } - return mpt::StubVMImageVault().fetch_image(fetch_type, query, prepare, monitor, unlock, checksum, download_dir); + return mpt::StubVMImageVault().fetch_image(fetch_type, query, prepare, monitor, unlock, checksum, save_dir); }; } diff --git a/tests/lxd/test_lxd_image_vault.cpp b/tests/lxd/test_lxd_image_vault.cpp index f98af001b97..2934d31191d 100644 --- a/tests/lxd/test_lxd_image_vault.cpp +++ b/tests/lxd/test_lxd_image_vault.cpp @@ -70,7 +70,7 @@ struct LXDImageVault : public Test mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; mpt::StubURLDownloader stub_url_downloader; mpt::TempDir cache_dir; - mpt::TempDir download_dir; + mpt::TempDir save_dir; }; } // namespace @@ -96,7 +96,7 @@ TEST_F(LXDImageVault, instance_exists_fetch_returns_expected_image_info) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, download_dir.path())); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -124,7 +124,7 @@ TEST_F(LXDImageVault, instance_exists_custom_image_returns_expected_image_info) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, download_dir.path())); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, "6937ddd3f4c3329182855843571fc91ae4fee24e8e0eb0f7cdcf2c22feed4dab"); EXPECT_EQ(image.original_release, "Snapcraft builder for Core 20"); @@ -153,7 +153,7 @@ TEST_F(LXDImageVault, instance_exists_uses_cached_release_title) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, download_dir.path())); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "Fake Title"); @@ -183,7 +183,7 @@ TEST_F(LXDImageVault, instance_exists_no_cached_release_title_info_for_fails) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, download_dir.path())); + false, std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, ""); @@ -213,7 +213,7 @@ TEST_F(LXDImageVault, returns_expected_info_with_valid_remote) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path())); + std::nullopt, save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -242,7 +242,7 @@ TEST_F(LXDImageVault, throws_with_invalid_alias) base_url, cache_dir.path(), mp::days{0}}; MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, save_dir.path()), std::runtime_error, mpt::match_what(StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", alias, "release")))); @@ -261,7 +261,7 @@ TEST_F(LXDImageVault, throws_with_invalid_remote) base_url, cache_dir.path(), mp::days{0}}; MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, save_dir.path()), std::runtime_error, mpt::match_what(HasSubstr(fmt::format("Remote \'{}\' is not found.", remote)))); } @@ -291,7 +291,7 @@ TEST_F(LXDImageVault, does_not_download_if_image_exists) base_url, cache_dir.path(), mp::days{0}}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path())); + std::nullopt, save_dir.path())); } TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) @@ -328,7 +328,7 @@ TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) mp::VMImage image; EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, download_dir.path())); + false, std::nullopt, save_dir.path())); EXPECT_FALSE(download_requested); EXPECT_EQ(image.original_release, mpt::default_release_info); } @@ -355,7 +355,7 @@ TEST_F(LXDImageVault, requests_download_if_image_does_not_exist) base_url, cache_dir.path(), mp::days{0}}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path())); + std::nullopt, save_dir.path())); EXPECT_TRUE(download_requested); } @@ -383,7 +383,7 @@ TEST_F(LXDImageVault, sets_fingerprint_with_hash_query) const mp::Query query{"", "e3b0c44298fc1c1", false, "release", mp::Query::Type::Alias}; EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path())); + std::nullopt, save_dir.path())); } TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) @@ -422,7 +422,7 @@ TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) base_url, cache_dir.path(), mp::days{0}}; EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, save_dir.path()), mp::AbortedDownloadException); EXPECT_TRUE(delete_requested); @@ -460,7 +460,7 @@ TEST_F(LXDImageVault, percent_complete_returns_negative_on_metadata_download) base_url, cache_dir.path(), mp::days{0}}; EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, save_dir.path()), mp::AbortedDownloadException); } @@ -835,7 +835,7 @@ TEST_F(LXDImageVault, custom_image_found_returns_expected_info) const mp::Query query{"", "snapcraft", false, "release", mp::Query::Type::Alias}; auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, mpt::lxd_snapcraft_image_id); EXPECT_EQ(image.original_release, mpt::snapcraft_release_info); @@ -893,7 +893,7 @@ TEST_F(LXDImageVault, custom_image_downloads_and_creates_correct_upload) const mp::Query query{"", "custom", false, "release", mp::Query::Type::Alias}; auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, mpt::lxd_custom_image_id); EXPECT_EQ(image.original_release, mpt::custom_release_info); @@ -918,7 +918,7 @@ TEST_F(LXDImageVault, fetch_image_unable_to_connect_logs_error_and_returns_blank StrEq(fmt::format("{} - returning blank image info", exception_message))))); auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, save_dir.path()); EXPECT_TRUE(image.id.empty()); EXPECT_TRUE(image.original_release.empty()); @@ -1046,7 +1046,7 @@ TEST_F(LXDImageVault, http_based_image_downloads_and_creates_correct_upload) const std::string download_url{"http://www.foo.com/images/foo.img"}; const mp::Query query{"", download_url, false, "", mp::Query::Type::HttpDownload}; auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1112,7 +1112,7 @@ TEST_F(LXDImageVault, file_based_fetch_copies_image_and_returns_expected_info) const mp::Query query{"", file.url().toStdString(), false, "", mp::Query::Type::LocalFile}; auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()); + std::nullopt, save_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1134,7 +1134,7 @@ TEST_F(LXDImageVault, invalid_local_file_image_throws) const mp::Query query{"", fmt::format("file://{}", missing_file), false, "", mp::Query::Type::LocalFile}; MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, download_dir.path()), + std::nullopt, save_dir.path()), std::runtime_error, mpt::match_what(StrEq(fmt::format("Custom image `{}` does not exist.", missing_file)))); } diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index 047875efd2a..3b815841728 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -168,9 +168,9 @@ struct ImageVault : public testing::Test [](const mp::VMImage& source_image) -> mp::VMImage { return source_image; }}; mpt::TempDir cache_dir; mpt::TempDir data_dir; - mpt::TempDir download_dir; + mpt::TempDir save_dir; std::string instance_name{"valley-pied-piper"}; - QString instance_dir = download_dir.filePath(QString::fromStdString(instance_name)); + QString instance_dir = save_dir.filePath(QString::fromStdString(instance_name)); mp::Query default_query{instance_name, "xenial", false, "", mp::Query::Type::Alias}; }; } // namespace @@ -242,7 +242,7 @@ TEST_F(ImageVault, caches_prepared_images) auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, - std::nullopt, download_dir.filePath(QString::fromStdString(another_query.name))); + std::nullopt, save_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -289,7 +289,7 @@ TEST_F(ImageVault, remembers_prepared_images) mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt, - download_dir.filePath(QString::fromStdString(another_query.name))); + save_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); From 18c4069f97d5e6f7f2b42a4f48d08dfa526ffc14 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 19 Sep 2023 08:18:08 -0500 Subject: [PATCH 411/627] [lxd] edit log string to be more descriptive --- src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 2 +- tests/lxd/test_lxd_backend.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 0c287037942..acbc927b2ea 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -108,7 +108,7 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co void mp::LXDVirtualMachineFactory::remove_resources_for_impl(const std::string& name) { - mpl::log(mpl::Level::trace, category, fmt::format("No resources to remove for \"{}\"", name)); + mpl::log(mpl::Level::trace, category, fmt::format("No further resources to remove for \"{}\"", name)); } auto mp::LXDVirtualMachineFactory::prepare_source_image(const VMImage& source_image) -> VMImage diff --git a/tests/lxd/test_lxd_backend.cpp b/tests/lxd/test_lxd_backend.cpp index 61b525d9489..ca3fd40612d 100644 --- a/tests/lxd/test_lxd_backend.cpp +++ b/tests/lxd/test_lxd_backend.cpp @@ -959,10 +959,10 @@ TEST_F(LXDBackend, unimplemented_functions_logs_trace_message) const std::string name{"foo"}; - EXPECT_CALL( - *logger_scope.mock_logger, - log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), - mpt::MockLogger::make_cstring_matcher(StrEq(fmt::format("No resources to remove for \"{}\"", name))))); + EXPECT_CALL(*logger_scope.mock_logger, + log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), + mpt::MockLogger::make_cstring_matcher( + StrEq(fmt::format("No further resources to remove for \"{}\"", name))))); EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), From b50e5edf1d56243512fdee1d643de878c64a7b58 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 19 Sep 2023 08:18:44 -0500 Subject: [PATCH 412/627] remove unnecessary comment --- include/multipass/virtual_machine_factory.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 87eaf799906..8d551894dc1 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -60,8 +60,7 @@ class VirtualMachineFactory : private DisabledCopyMove virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0; virtual void hypervisor_health_check() = 0; virtual QString get_backend_directory_name() const = 0; - virtual Path - get_instance_directory_name(const std::string& name) const = 0; // postcondition: return directory exists + virtual Path get_instance_directory_name(const std::string& name) const = 0; virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, From 6a2fa2fd7b15b6e227a54d6a9964d8c3894af41c Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 19 Sep 2023 11:55:54 -0500 Subject: [PATCH 413/627] [vm factory] rename function --- include/multipass/virtual_machine_factory.h | 2 +- src/daemon/daemon.cpp | 4 ++-- .../backends/libvirt/libvirt_virtual_machine_factory.cpp | 2 +- src/platform/backends/lxd/lxd_virtual_machine_factory.cpp | 2 +- src/platform/backends/qemu/qemu_virtual_machine_factory.cpp | 2 +- src/platform/backends/shared/base_virtual_machine_factory.h | 4 ++-- tests/mock_virtual_machine_factory.h | 2 +- tests/stub_virtual_machine_factory.h | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/multipass/virtual_machine_factory.h b/include/multipass/virtual_machine_factory.h index 8d551894dc1..123e354d867 100644 --- a/include/multipass/virtual_machine_factory.h +++ b/include/multipass/virtual_machine_factory.h @@ -60,7 +60,7 @@ class VirtualMachineFactory : private DisabledCopyMove virtual void prepare_instance_image(const VMImage& instance_image, const VirtualMachineDescription& desc) = 0; virtual void hypervisor_health_check() = 0; virtual QString get_backend_directory_name() const = 0; - virtual Path get_instance_directory_name(const std::string& name) const = 0; + virtual Path get_instance_directory(const std::string& name) const = 0; virtual QString get_backend_version_string() const = 0; virtual VMImageVault::UPtr create_image_vault(std::vector image_hosts, URLDownloader* downloader, const Path& cache_dir_path, const Path& data_dir_path, diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index dae7bde88a2..684164c6307 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -445,7 +445,7 @@ auto fetch_image_for(const std::string& name, mp::VirtualMachineFactory& factory mp::Query query{name, "", false, "", mp::Query::Type::Alias, false}; return vault.fetch_image(factory.fetch_type(), query, stub_prepare, stub_progress, false, std::nullopt, - factory.get_instance_directory_name(name)); + factory.get_instance_directory(name)); } auto try_mem_size(const std::string& val) -> std::optional @@ -2920,7 +2920,7 @@ void mp::Daemon::create_vm(const CreateRequest* request, auto vm_image = config->vault->fetch_image(fetch_type, query, prepare_action, progress_monitor, launch_from_blueprint, - checksum, config->factory->get_instance_directory_name(name)); + checksum, config->factory->get_instance_directory(name)); const auto image_size = config->vault->minimum_image_size_for(vm_image.id); vm_desc.disk_space = compute_final_image_size( diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index b23798e8313..451fb2ffc7c 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -128,7 +128,7 @@ mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machin bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper, - get_instance_directory_name(desc.vm_name)); + get_instance_directory(desc.vm_name)); } mp::LibVirtVirtualMachineFactory::~LibVirtVirtualMachineFactory() diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index acbc927b2ea..5225a508215 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -103,7 +103,7 @@ mp::VirtualMachine::UPtr mp::LXDVirtualMachineFactory::create_virtual_machine(co { return std::make_unique(desc, monitor, manager.get(), base_url, multipass_bridge_name, storage_pool, - MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); + MP_UTILS.make_dir(get_instance_directory(desc.vm_name))); } void mp::LXDVirtualMachineFactory::remove_resources_for_impl(const std::string& name) diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index b2bb22859e8..b96f825b08d 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -54,7 +54,7 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(c VMStatusMonitor& monitor) { return std::make_unique(desc, qemu_platform.get(), monitor, - MP_UTILS.make_dir(get_instance_directory_name(desc.vm_name))); + MP_UTILS.make_dir(get_instance_directory(desc.vm_name))); } void mp::QemuVirtualMachineFactory::remove_resources_for_impl(const std::string& name) diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index f713fa4ce42..edb1556fa86 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -48,7 +48,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory return {}; }; - Path get_instance_directory_name(const std::string& name) const override + Path get_instance_directory(const std::string& name) const override { return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); } @@ -95,7 +95,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory inline void multipass::BaseVirtualMachineFactory::remove_resources_for(const std::string& name) { remove_resources_for_impl(name); - QDir instance_dir{get_instance_directory_name(name)}; + QDir instance_dir{get_instance_directory(name)}; instance_dir.removeRecursively(); } diff --git a/tests/mock_virtual_machine_factory.h b/tests/mock_virtual_machine_factory.h index 9139963b62e..37ad0d89d9d 100644 --- a/tests/mock_virtual_machine_factory.h +++ b/tests/mock_virtual_machine_factory.h @@ -41,7 +41,7 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory MOCK_METHOD(void, prepare_instance_image, (const VMImage&, const VirtualMachineDescription&), (override)); MOCK_METHOD(void, hypervisor_health_check, (), (override)); MOCK_METHOD(QString, get_backend_directory_name, (), (const, override)); - MOCK_METHOD(QString, get_instance_directory_name, (const std::string&), (const, override)); + MOCK_METHOD(QString, get_instance_directory, (const std::string&), (const, override)); MOCK_METHOD(QString, get_backend_version_string, (), (const, override)); MOCK_METHOD(VMImageVault::UPtr, create_image_vault, (std::vector, URLDownloader*, const Path&, const Path&, const days&), (override)); diff --git a/tests/stub_virtual_machine_factory.h b/tests/stub_virtual_machine_factory.h index 0980bd438fb..93305b094d3 100644 --- a/tests/stub_virtual_machine_factory.h +++ b/tests/stub_virtual_machine_factory.h @@ -73,7 +73,7 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory return {}; } - QString get_instance_directory_name(const std::string& name) const override + QString get_instance_directory(const std::string& name) const override { return tmp_dir->path(); } From 63e606a55c4c14b5955566df818b81ab7c1ebccf Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 19 Sep 2023 21:26:40 -0500 Subject: [PATCH 414/627] [vm factory] extract instance directory derivation into util function --- include/multipass/utils.h | 2 ++ .../libvirt/libvirt_virtual_machine_factory.cpp | 3 ++- .../backends/lxd/lxd_virtual_machine_factory.cpp | 4 ++-- .../backends/lxd/lxd_virtual_machine_factory.h | 2 -- .../backends/qemu/qemu_virtual_machine_factory.cpp | 10 ++++------ .../backends/shared/base_virtual_machine_factory.cpp | 2 ++ .../backends/shared/base_virtual_machine_factory.h | 3 +++ src/utils/utils.cpp | 9 +++++++++ 8 files changed, 24 insertions(+), 11 deletions(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 93674cfa324..21ce3e1189f 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -218,6 +218,8 @@ class Utils : public Singleton // virtual machine helpers virtual void wait_for_cloud_init(VirtualMachine* virtual_machine, std::chrono::milliseconds timeout, const SSHKeyProvider& key_provider) const; + virtual Path derive_instances_dir(const Path& data_dir, const Path& backend_directory_name, + const Path& instances_subdir) const; // system info helpers virtual std::string get_kernel_version() const; diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index 451fb2ffc7c..dfdb429037b 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -108,7 +108,8 @@ auto make_libvirt_wrapper(const std::string& libvirt_object_path) mp::LibVirtVirtualMachineFactory::LibVirtVirtualMachineFactory(const mp::Path& data_dir, const std::string& libvirt_object_path) - : BaseVirtualMachineFactory(QDir(data_dir, get_backend_directory_name()).filePath("vault/instances")), + : BaseVirtualMachineFactory( + MP_UTILS.derive_instances_dir(data_dir, get_backend_directory_name(), instances_subdir)), libvirt_wrapper{make_libvirt_wrapper(libvirt_object_path)}, data_dir{data_dir}, bridge_name{enable_libvirt_network(data_dir, libvirt_wrapper)}, diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index 5225a508215..d7683b0239c 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -82,12 +82,12 @@ mp::NetworkInterfaceInfo munch_network(std::mapget_directory_name(), instances_subdir)), + qemu_platform{std::move(qemu_platform)} { } @@ -54,7 +52,7 @@ mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(c VMStatusMonitor& monitor) { return std::make_unique(desc, qemu_platform.get(), monitor, - MP_UTILS.make_dir(get_instance_directory(desc.vm_name))); + get_instance_directory(desc.vm_name)); } void mp::QemuVirtualMachineFactory::remove_resources_for_impl(const std::string& name) diff --git a/src/platform/backends/shared/base_virtual_machine_factory.cpp b/src/platform/backends/shared/base_virtual_machine_factory.cpp index 7566ec9a192..b7241c2f8ac 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.cpp +++ b/src/platform/backends/shared/base_virtual_machine_factory.cpp @@ -39,6 +39,8 @@ auto find_bridge_with(const NetworkContainer& networks, const std::string& membe } } // namespace +const mp::Path mp::BaseVirtualMachineFactory::instances_subdir = "vault/instances"; + mp::BaseVirtualMachineFactory::BaseVirtualMachineFactory(const Path& instances_dir) : instances_dir{instances_dir} {}; void mp::BaseVirtualMachineFactory::configure(VirtualMachineDescription& vm_desc) diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index edb1556fa86..28377029da1 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -73,6 +73,9 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory throw NotImplementedOnThisBackendException("networks"); }; +protected: + static const Path instances_subdir; + protected: std::string create_bridge_with(const NetworkInterfaceInfo& interface) override { diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index b91b45d1263..131187c4de8 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -689,3 +689,12 @@ void mp::utils::set_owner_for(mp::SSHSession& session, const std::string& root, fmt::format("sudo /bin/bash -c 'cd \"{}\" && chown -R {}:{} \"{}\"'", root, vm_user, vm_group, relative_target.substr(0, relative_target.find_first_of('/')))); } + +mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir, const mp::Path& backend_directory_name, + const mp::Path& instances_subdir) const +{ + if (backend_directory_name.isEmpty()) + return QDir(data_dir).filePath(instances_subdir); + else + return QDir(data_dir).filePath(QString("%1/%2").arg(backend_directory_name).arg(instances_subdir)); +} From 3ca785b8a06b47e03a9a1424d464c19444ffeebd Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 20 Sep 2023 11:45:00 -0500 Subject: [PATCH 415/627] [vm image vault] correct instance directory creation location --- src/daemon/default_vm_image_vault.cpp | 18 ++++++++---------- src/daemon/default_vm_image_vault.h | 5 ++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 1719db0e220..7f6d9d73a62 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -282,11 +282,11 @@ mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, co if (source_image.image_path.endsWith(".xz")) { - source_image.image_path = extract_image_from(query.name, source_image, monitor, save_dir); + source_image.image_path = extract_image_from(source_image, monitor, save_dir); } else { - source_image = image_instance_from(query.name, source_image, save_dir); + source_image = image_instance_from(source_image, save_dir); } vm_image = prepare(source_image); @@ -630,11 +630,10 @@ mp::VMImage mp::DefaultVMImageVault::download_and_prepare_source_image( } } -QString mp::DefaultVMImageVault::extract_image_from(const std::string& instance_name, const VMImage& source_image, - const ProgressMonitor& monitor, const mp::Path& dest_dir) +QString mp::DefaultVMImageVault::extract_image_from(const VMImage& source_image, const ProgressMonitor& monitor, + const mp::Path& dest_dir) { - const auto name = QString::fromStdString(instance_name); - MP_UTILS.make_dir(dest_dir, name); + MP_UTILS.make_dir(dest_dir); QFileInfo file_info{source_image.image_path}; const auto image_name = file_info.fileName().remove(".xz"); const auto image_path = QDir(dest_dir).filePath(image_name); @@ -642,10 +641,9 @@ QString mp::DefaultVMImageVault::extract_image_from(const std::string& instance_ return mp::vault::extract_image(image_path, monitor); } -mp::VMImage mp::DefaultVMImageVault::image_instance_from(const std::string& instance_name, - const VMImage& prepared_image, const mp::Path& dest_dir) +mp::VMImage mp::DefaultVMImageVault::image_instance_from(const VMImage& prepared_image, const mp::Path& dest_dir) { - MP_UTILS.make_dir(dest_dir, QString::fromStdString(instance_name)); + MP_UTILS.make_dir(dest_dir); return {mp::vault::copy(prepared_image.image_path, dest_dir), prepared_image.id, @@ -673,7 +671,7 @@ mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, if (!query.name.empty()) { - vm_image = image_instance_from(query.name, prepared_image, dest_dir); + vm_image = image_instance_from(prepared_image, dest_dir); instance_image_records[query.name] = {vm_image, query, std::chrono::system_clock::now()}; } diff --git a/src/daemon/default_vm_image_vault.h b/src/daemon/default_vm_image_vault.h index 1f8b07c0f6a..bea7e74c83a 100644 --- a/src/daemon/default_vm_image_vault.h +++ b/src/daemon/default_vm_image_vault.h @@ -61,12 +61,11 @@ class DefaultVMImageVault final : public BaseVMImageVault MemorySize minimum_image_size_for(const std::string& id) override; private: - VMImage image_instance_from(const std::string& name, const VMImage& prepared_image, const Path& dest_dir); + VMImage image_instance_from(const VMImage& prepared_image, const Path& dest_dir); VMImage download_and_prepare_source_image(const VMImageInfo& info, std::optional& existing_source_image, const QDir& image_dir, const FetchType& fetch_type, const PrepareAction& prepare, const ProgressMonitor& monitor); - QString extract_image_from(const std::string& instance_name, const VMImage& source_image, - const ProgressMonitor& monitor, const Path& dest_dir); + QString extract_image_from(const VMImage& source_image, const ProgressMonitor& monitor, const Path& dest_dir); std::optional> get_image_future(const std::string& id); VMImage finalize_image_records(const Query& query, const VMImage& prepared_image, const std::string& id, const Path& dest_dir); From 2e3d9de51cf9ef4480f62198abe60dd8b6873ef5 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 20 Sep 2023 11:46:35 -0500 Subject: [PATCH 416/627] [utils] avoid hardcoded directory separators --- src/utils/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 131187c4de8..85c7d42fbbc 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -696,5 +696,5 @@ mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir, const mp::Pat if (backend_directory_name.isEmpty()) return QDir(data_dir).filePath(instances_subdir); else - return QDir(data_dir).filePath(QString("%1/%2").arg(backend_directory_name).arg(instances_subdir)); + return QDir(QDir(data_dir).filePath(backend_directory_name)).filePath(instances_subdir); } From d65652a5af3e2377538cb4dc274a440c2281659f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 9 Aug 2023 13:30:26 -0700 Subject: [PATCH 417/627] [cli] add snapshots option to list command --- src/client/cli/cmd/list.cpp | 9 +++++---- src/rpc/multipass.proto | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/client/cli/cmd/list.cpp b/src/client/cli/cmd/list.cpp index 93c0ea13ca8..d5ecf2e7892 100644 --- a/src/client/cli/cmd/list.cpp +++ b/src/client/cli/cmd/list.cpp @@ -59,24 +59,24 @@ std::vector cmd::List::aliases() const QString cmd::List::short_help() const { - return QStringLiteral("List all available instances"); + return QStringLiteral("List all available instances or snapshots"); } QString cmd::List::description() const { - return QStringLiteral("List all instances which have been created."); + return QStringLiteral("List all instances or snapshots which have been created."); } mp::ParseCode cmd::List::parse_args(mp::ArgParser* parser) { + QCommandLineOption snapshotsOption("snapshots", "List all available snapshots"); QCommandLineOption formatOption( "format", "Output list in the requested format.\nValid formats are: table (default), json, csv and yaml", "format", "table"); - QCommandLineOption noIpv4Option("no-ipv4", "Do not query the instances for the IPv4's they are using"); noIpv4Option.setFlags(QCommandLineOption::HiddenFromHelp); - parser->addOptions({formatOption, noIpv4Option}); + parser->addOptions({snapshotsOption, formatOption, noIpv4Option}); auto status = parser->commandParse(this); @@ -91,6 +91,7 @@ mp::ParseCode cmd::List::parse_args(mp::ArgParser* parser) return ParseCode::CommandLineError; } + request.set_snapshots(parser->isSet(snapshotsOption)); request.set_request_ipv4(!parser->isSet(noIpv4Option)); status = handle_format_option(parser, &chosen_formatter, cerr); diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 4de76bf73fb..042b9b283a4 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -271,7 +271,8 @@ message InfoReply { message ListRequest { int32 verbosity_level = 1; - bool request_ipv4 = 2; + bool snapshots = 2; + bool request_ipv4 = 3; } message ListVMInstance { From 252c68a1db473abb1cecbca4c305315babaa961e Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 9 Aug 2023 13:37:41 -0700 Subject: [PATCH 418/627] [rpc] add option of list of instances or list of snapshots in list reply --- src/client/cli/formatter/csv_formatter.cpp | 2 +- src/client/cli/formatter/json_formatter.cpp | 2 +- src/client/cli/formatter/table_formatter.cpp | 4 ++-- src/client/cli/formatter/yaml_formatter.cpp | 2 +- src/client/gui/gui_cmd.cpp | 8 +++---- src/daemon/daemon.cpp | 4 ++-- src/rpc/multipass.proto | 24 +++++++++++++++++--- tests/test_cli_client.cpp | 2 +- tests/test_daemon.cpp | 22 ++++++++++-------- tests/test_output_formatter.cpp | 16 ++++++------- 10 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 906ecc9cf5b..e9c4a55d35b 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -173,7 +173,7 @@ std::string mp::CSVFormatter::format(const ListReply& reply) const fmt::format_to(std::back_inserter(buf), "Name,State,IPv4,IPv6,Release,AllIPv4\n"); - for (const auto& instance : format::sorted(reply.instances())) + for (const auto& instance : format::sorted(reply.instances().info())) { fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},\"{}\"\n", instance.name(), mp::format::status_string_for(instance.instance_status()), diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 2f2acb1d9fe..03fb7195430 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -297,7 +297,7 @@ std::string mp::JsonFormatter::format(const ListReply& reply) const QJsonObject list_json; QJsonArray instances; - for (const auto& instance : reply.instances()) + for (const auto& instance : reply.instances().info()) { QJsonObject instance_obj; instance_obj.insert("name", QString::fromStdString(instance.name())); diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index ead3faebe1b..2de1152af1c 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -288,7 +288,7 @@ std::string mp::TableFormatter::format(const ListReply& reply) const { fmt::memory_buffer buf; - auto instances = reply.instances(); + auto instances = reply.instances().info(); if (instances.empty()) return "No instances found.\n"; @@ -304,7 +304,7 @@ std::string mp::TableFormatter::format(const ListReply& reply) const fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, "State", state_column_width, "IPv4", ip_column_width, "Image"); - for (const auto& instance : format::sorted(reply.instances())) + for (const auto& instance : format::sorted(reply.instances().info())) { int ipv4_size = instance.ipv4_size(); diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 4e7afaddb12..ab0567ca403 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -235,7 +235,7 @@ std::string mp::YamlFormatter::format(const ListReply& reply) const { YAML::Node list; - for (const auto& instance : format::sorted(reply.instances())) + for (const auto& instance : format::sorted(reply.instances().info())) { YAML::Node instance_node; instance_node["state"] = mp::format::status_string_for(instance.instance_status()); diff --git a/src/client/gui/gui_cmd.cpp b/src/client/gui/gui_cmd.cpp index 655e6cfb285..b6558059d37 100644 --- a/src/client/gui/gui_cmd.cpp +++ b/src/client/gui/gui_cmd.cpp @@ -191,14 +191,14 @@ void cmd::GuiCmd::update_menu() auto reply = list_future.result(); - handle_petenv_instance(reply.instances()); + handle_petenv_instance(reply.instances().info()); for (auto it = instances_entries.cbegin(); it != instances_entries.cend(); ++it) { - auto instance = std::find_if(reply.instances().cbegin(), reply.instances().cend(), + auto instance = std::find_if(reply.instances().info().cbegin(), reply.instances().info().cend(), [it](const ListVMInstance& instance) { return it->first == instance.name(); }); - if (instance == reply.instances().cend()) + if (instance == reply.instances().info().cend()) { instances_to_remove.push_back(it->first); } @@ -209,7 +209,7 @@ void cmd::GuiCmd::update_menu() instances_entries.erase(instance); } - for (const auto& instance : reply.instances()) + for (const auto& instance : reply.instances().info()) { auto name = instance.name(); auto state = instance.instance_status(); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 2b8e1cc92f1..acd8d70a927 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1816,7 +1816,7 @@ try // clang-format on const auto& name = instance.first; const auto& vm = instance.second; auto present_state = vm->current_state(); - auto entry = response.add_instances(); + auto entry = response.mutable_instances()->add_info(); entry->set_name(name); entry->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); @@ -1858,7 +1858,7 @@ try // clang-format on for (const auto& instance : deleted_instances) { const auto& name = instance.first; - auto entry = response.add_instances(); + auto entry = response.mutable_instances()->add_info(); entry->set_name(name); entry->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); } diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 042b9b283a4..afcd48f901f 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -283,10 +283,28 @@ message ListVMInstance { string current_release = 5; } +message ListVMSnapshot { + string name = 1; + SnapshotFundamentals fundamentals = 2; +} + +message InstancesList { + repeated ListVMInstance info = 1; +} + +message SnapshotsList { + repeated ListVMSnapshot info = 1; +} + message ListReply { - repeated ListVMInstance instances = 1; - string log_line = 2; - UpdateInfo update_info = 3; + oneof list_contents + { + InstancesList instances = 1; + SnapshotsList snapshots = 2; + } + + string log_line = 3; + UpdateInfo update_info = 4; } message NetworksRequest { diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 3423a14302d..a0eef099da6 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -333,7 +333,7 @@ struct Client : public Test for (mp::InstanceStatus_Status status : statuses) { - auto list_entry = list_reply.add_instances(); + auto list_entry = list_reply.mutable_instances()->add_info(); list_entry->mutable_instance_status()->set_status(status); } diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 08d017e52e6..1519c1f13c1 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -1413,12 +1413,13 @@ TEST_F(Daemon, reads_mac_addresses_from_json) // Check that the instance was indeed read and there were no errors. { StrictMock> mock_server; + mp::ListReply list_reply; - auto instance_matcher = Property(&mp::ListVMInstance::name, "real-zebraphant"); - EXPECT_CALL(mock_server, Write(Property(&mp::ListReply::instances, ElementsAre(instance_matcher)), _)) - .WillOnce(Return(true)); + auto instance_matcher = UnorderedElementsAre(Property(&mp::ListVMInstance::name, "real-zebraphant")); + EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&list_reply), Return(true))); EXPECT_TRUE(call_daemon_slot(daemon, &mp::Daemon::list, mp::ListRequest{}, mock_server).ok()); + EXPECT_THAT(list_reply.instances().info(), instance_matcher); } // Removing the JSON is possible now because data was already read. This step is not necessary, but doing it we @@ -1475,12 +1476,13 @@ TEST_F(Daemon, writesAndReadsMountsInJson) // Check that the instance was indeed read and there were no errors. { StrictMock> mock_server; + mp::ListReply list_reply; - auto instance_matcher = Property(&mp::ListVMInstance::name, "real-zebraphant"); - EXPECT_CALL(mock_server, Write(Property(&mp::ListReply::instances, ElementsAre(instance_matcher)), _)) - .WillOnce(Return(true)); + auto instance_matcher = UnorderedElementsAre(Property(&mp::ListVMInstance::name, "real-zebraphant")); + EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&list_reply), Return(true))); EXPECT_TRUE(call_daemon_slot(daemon, &mp::Daemon::list, mp::ListRequest{}, mock_server).ok()); + EXPECT_THAT(list_reply.instances().info(), instance_matcher); } QFile::remove(filename); // Remove the JSON. @@ -1707,11 +1709,13 @@ TEST_F(Daemon, ctor_drops_removed_instances) mp::Daemon daemon{config_builder.build()}; StrictMock> mock_server; - auto stayed_matcher = Property(&mp::ListVMInstance::name, stayed); - EXPECT_CALL(mock_server, Write(Property(&mp::ListReply::instances, ElementsAre(stayed_matcher)), _)) - .WillOnce(Return(true)); + mp::ListReply list_reply; + + auto stayed_matcher = UnorderedElementsAre(Property(&mp::ListVMInstance::name, stayed)); + EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&list_reply), Return(true))); EXPECT_TRUE(call_daemon_slot(daemon, &mp::Daemon::list, mp::ListRequest{}, mock_server).ok()); + EXPECT_THAT(list_reply.instances().info(), stayed_matcher); auto updated_json = mpt::load(filename); EXPECT_THAT(updated_json.toStdString(), AllOf(HasSubstr(stayed), Not(HasSubstr(gone)))); diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index dcef2b02024..014991d8942 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -44,7 +44,7 @@ auto construct_single_instance_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.add_instances(); + auto list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("foo"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); list_entry->set_current_release("16.04 LTS"); @@ -60,13 +60,13 @@ auto construct_multiple_instances_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.add_instances(); + auto list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("bogus-instance"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); list_entry->set_current_release("16.04 LTS"); list_entry->add_ipv4("10.21.124.56"); - list_entry = list_reply.add_instances(); + list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("bombastic"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); list_entry->set_current_release("18.04 LTS"); @@ -78,22 +78,22 @@ auto construct_unsorted_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.add_instances(); + auto list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("trusty-190611-1542"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); list_entry->set_current_release("N/A"); - list_entry = list_reply.add_instances(); + list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("trusty-190611-1535"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); list_entry->set_current_release("N/A"); - list_entry = list_reply.add_instances(); + list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("trusty-190611-1539"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); list_entry->set_current_release(""); - list_entry = list_reply.add_instances(); + list_entry = list_reply.mutable_instances()->add_info(); list_entry->set_name("trusty-190611-1529"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); list_entry->set_current_release(""); @@ -103,7 +103,7 @@ auto construct_unsorted_list_reply() auto add_petenv_to_reply(mp::ListReply& reply) { - auto instance = reply.add_instances(); + auto instance = reply.mutable_instances()->add_info(); instance->set_name(petenv_name()); instance->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); instance->set_current_release("Not Available"); From cc9840bfc427082c67ed55184205321c5394d5da Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 9 Aug 2023 14:07:51 -0700 Subject: [PATCH 419/627] [table formatter] print out snapshot list --- include/multipass/cli/format_utils.h | 28 ++++- src/client/cli/formatter/table_formatter.cpp | 107 +++++++++++++++---- tests/test_cli_client.cpp | 8 +- tests/test_daemon.cpp | 11 +- tests/test_output_formatter.cpp | 9 +- tests/unix/test_daemon_rpc.cpp | 15 ++- 6 files changed, 144 insertions(+), 34 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 00f83da5535..a041edb4af5 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -65,8 +65,8 @@ static constexpr auto column_width = [](const auto begin, const auto end, const } // namespace format } // namespace multipass -template -Instances multipass::format::sorted(const Instances& instances) +template +Container multipass::format::sorted(const Container& instances) { if (instances.empty()) return instances; @@ -74,12 +74,30 @@ Instances multipass::format::sorted(const Instances& instances) auto ret = instances; const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString(); std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) { - if (a.name() == petenv_name) + using T = std::decay_t; + using google::protobuf::util::TimeUtil; + + if (a.name() == petenv_name && b.name() != petenv_name) return true; - else if (b.name() == petenv_name) + else if (b.name() == petenv_name && a.name() != petenv_name) return false; else - return a.name() < b.name(); + { + if constexpr (std::is_same_v) + { + if (a.name() < b.name()) + return true; + else if (a.name() > b.name()) + return false; + + return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); + } + else + { + return a.name() < b.name(); + } + } }); return ret; diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 2de1152af1c..990a5521825 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -265,30 +265,12 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) return fmt::to_string(buf); } -} // namespace - -std::string mp::TableFormatter::format(const InfoReply& reply) const -{ - std::string output; - - if (reply.has_detailed_report()) - { - output = generate_instance_info_report(reply); - } - else - { - assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - output = generate_snapshot_overview_report(reply); - } - return output; -} - -std::string mp::TableFormatter::format(const ListReply& reply) const +std::string generate_instances_list(const mp::InstancesList& instances_list) { fmt::memory_buffer buf; - auto instances = reply.instances().info(); + auto instances = instances_list.info(); if (instances.empty()) return "No instances found.\n"; @@ -304,7 +286,7 @@ std::string mp::TableFormatter::format(const ListReply& reply) const fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, "State", state_column_width, "IPv4", ip_column_width, "Image"); - for (const auto& instance : format::sorted(reply.instances().info())) + for (const auto& instance : mp::format::sorted(instances_list.info())) { int ipv4_size = instance.ipv4_size(); @@ -324,6 +306,89 @@ std::string mp::TableFormatter::format(const ListReply& reply) const return fmt::to_string(buf); } +std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +{ + fmt::memory_buffer buf; + + auto snapshots = snapshots_list.info(); + + if (snapshots.empty()) + return "No snapshots found.\n"; + + const std::string name_col_header = "Instance", snapshot_col_header = "Snapshot", parent_col_header = "Parent", + comment_col_header = "Comment"; + const auto name_column_width = mp::format::column_width( + snapshots.begin(), snapshots.end(), [](const auto& snapshot) -> int { return snapshot.name().length(); }, + name_col_header.length()); + const auto snapshot_column_width = mp::format::column_width( + snapshots.begin(), snapshots.end(), + [](const auto& snapshot) -> int { return snapshot.fundamentals().snapshot_name().length(); }, + snapshot_col_header.length()); + const auto parent_column_width = mp::format::column_width( + snapshots.begin(), snapshots.end(), + [](const auto& snapshot) -> int { return snapshot.fundamentals().parent().length(); }, + parent_col_header.length()); + + const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; + fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, snapshot_col_header, + snapshot_column_width, parent_col_header, parent_column_width, comment_col_header); + + for (const auto& snapshot : mp::format::sorted(snapshots_list.info())) + { + size_t max_comment_column_width = 50; + std::smatch match; + auto fundamentals = snapshot.fundamentals(); + + if (std::regex_search(fundamentals.comment().begin(), fundamentals.comment().end(), match, newline)) + max_comment_column_width = std::min((size_t)(match.position(1)) + 1, max_comment_column_width); + + fmt::format_to(std::back_inserter(buf), row_format, snapshot.name(), name_column_width, + fundamentals.snapshot_name(), snapshot_column_width, + fundamentals.parent().empty() ? "--" : fundamentals.parent(), parent_column_width, + fundamentals.comment().empty() ? "--" + : fundamentals.comment().length() > max_comment_column_width + ? fmt::format("{}…", fundamentals.comment().substr(0, max_comment_column_width - 1)) + : fundamentals.comment()); + } + + return fmt::to_string(buf); +} +} // namespace + +std::string mp::TableFormatter::format(const InfoReply& reply) const +{ + std::string output; + + if (reply.has_detailed_report()) + { + output = generate_instance_info_report(reply); + } + else + { + assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); + output = generate_snapshot_overview_report(reply); + } + + return output; +} + +std::string mp::TableFormatter::format(const ListReply& reply) const +{ + std::string output; + + if (reply.has_instances()) + { + output = generate_instances_list(reply.instances()); + } + else + { + assert(reply.has_snapshots() && "either one of instances or snapshots should be populated"); + output = generate_snapshots_list(reply.snapshots()); + } + + return output; +} + std::string mp::TableFormatter::format(const NetworksReply& reply) const { fmt::memory_buffer buf; diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index a0eef099da6..abc84ec8654 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -1796,9 +1796,11 @@ TEST_F(Client, infoCmdSucceedsWithAllAndNoRuntimeInformation) TEST_F(Client, list_cmd_ok_no_args) { const auto list_matcher = Property(&mp::ListRequest::request_ipv4, IsTrue()); + mp::ListReply reply; + reply.mutable_instances(); EXPECT_CALL(mock_daemon, list) - .WillOnce(WithArg<1>(check_request_and_return(list_matcher, ok))); + .WillOnce(WithArg<1>(check_request_and_return(list_matcher, ok, reply))); EXPECT_THAT(send_command({"list"}), Eq(mp::ReturnCode::Ok)); } @@ -1815,9 +1817,11 @@ TEST_F(Client, list_cmd_help_ok) TEST_F(Client, list_cmd_no_ipv4_ok) { const auto list_matcher = Property(&mp::ListRequest::request_ipv4, IsFalse()); + mp::ListReply reply; + reply.mutable_instances(); EXPECT_CALL(mock_daemon, list) - .WillOnce(WithArg<1>(check_request_and_return(list_matcher, ok))); + .WillOnce(WithArg<1>(check_request_and_return(list_matcher, ok, reply))); EXPECT_THAT(send_command({"list", "--no-ipv4"}), Eq(mp::ReturnCode::Ok)); } diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 1519c1f13c1..1030608a722 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -166,8 +166,15 @@ TEST_F(Daemon, receives_commands_and_calls_corresponding_slot) return grpc::Status{}; }); - EXPECT_CALL(daemon, list(_, _, _)) - .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); + EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto server, auto status_promise) { + mp::ListReply reply; + reply.mutable_instances(); + + server->Write(reply); + status_promise->set_value(grpc::Status::OK); + + return grpc::Status{}; + }); EXPECT_CALL(daemon, recover(_, _, _)) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); EXPECT_CALL(daemon, start(_, _, _)) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 014991d8942..41c7ba7c4d8 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -40,6 +40,13 @@ auto petenv_name() return MP_SETTINGS.get(mp::petenv_key).toStdString(); } +auto construct_empty_list_reply() +{ + mp::ListReply list_reply; + list_reply.mutable_instances(); + return list_reply; +} + auto construct_single_instance_list_reply() { mp::ListReply list_reply; @@ -788,7 +795,7 @@ const mp::JsonFormatter json_formatter; const mp::CSVFormatter csv_formatter; const mp::YamlFormatter yaml_formatter; -const auto empty_list_reply = mp::ListReply(); +const auto empty_list_reply = construct_empty_list_reply(); const auto single_instance_list_reply = construct_single_instance_list_reply(); const auto multiple_instances_list_reply = construct_multiple_instances_list_reply(); const auto unsorted_list_reply = construct_unsorted_list_reply(); diff --git a/tests/unix/test_daemon_rpc.cpp b/tests/unix/test_daemon_rpc.cpp index 33636866d89..ec922bed027 100644 --- a/tests/unix/test_daemon_rpc.cpp +++ b/tests/unix/test_daemon_rpc.cpp @@ -202,7 +202,10 @@ TEST_F(TestDaemonRpc, listCertExistsCompletesSuccesfully) EXPECT_CALL(*mock_cert_store, verify_cert(StrEq(mpt::client_cert))).WillOnce(Return(true)); mpt::MockDaemon daemon{make_secure_server()}; - EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto, auto* status_promise) { + EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { + mp::ListReply reply; + reply.mutable_instances(); + server->Write(reply); status_promise->set_value(grpc::Status::OK); }); @@ -218,7 +221,10 @@ TEST_F(TestDaemonRpc, listNoCertsExistWillVerifyAndComplete) EXPECT_CALL(*mock_cert_store, add_cert(StrEq(mpt::client_cert))).Times(1); mpt::MockDaemon daemon{make_secure_server()}; - EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto, auto* status_promise) { + EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { + mp::ListReply reply; + reply.mutable_instances(); + server->Write(reply); status_promise->set_value(grpc::Status::OK); }); @@ -302,7 +308,10 @@ TEST_F(TestDaemonRpc, listSettingServerPermissionsFailLogsErrorAndExits) logger_scope.mock_logger->expect_log(mpl::Level::error, error_msg); logger_scope.mock_logger->expect_log(mpl::Level::error, "Failed to set up autostart prerequisites", AnyNumber()); - EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto, auto* status_promise) { + EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { + mp::ListReply reply; + reply.mutable_instances(); + server->Write(reply); status_promise->set_value(grpc::Status::OK); }); From b2866854eb0ad038b342eac399a62c2d7b5f2c4d Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 10 Aug 2023 06:48:23 -0700 Subject: [PATCH 420/627] [daemon] populate snapshot list item --- src/daemon/daemon.cpp | 61 ++++++++++++++++++++++++++++++++----------- tests/test_daemon.cpp | 16 +++++++++++- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index acd8d70a927..6b00afd4ac4 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1811,14 +1811,19 @@ try // clang-format on ListReply response; config->update_prompt->populate_if_time_to_show(response.mutable_update_info()); - for (const auto& instance : operative_instances) - { - const auto& name = instance.first; - const auto& vm = instance.second; - auto present_state = vm->current_state(); + // Need to 'touch' a report in the response so formatters know what to do with an otherwise empty response + request->snapshots() ? (void)response.mutable_snapshots() : (void)response.mutable_instances(); + bool deleted = false; + + auto fetch_instance = [&](VirtualMachine& vm) { + const auto& name = vm.vm_name; + auto present_state = vm.current_state(); auto entry = response.mutable_instances()->add_info(); entry->set_name(name); - entry->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); + if (deleted) + entry->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); + else + entry->mutable_instance_status()->set_status(grpc_instance_status_for(present_state)); // FIXME: Set the release to the cached current version when supported auto vm_image = fetch_image_for(name, config->factory->fetch_type(), *config->vault); @@ -1841,8 +1846,8 @@ try // clang-format on if (request->request_ipv4() && mp::utils::is_running(present_state)) { - std::string management_ip = vm->management_ipv4(*config->ssh_key_provider); - auto all_ipv4 = vm->get_all_ipv4(*config->ssh_key_provider); + std::string management_ip = vm.management_ipv4(*config->ssh_key_provider); + auto all_ipv4 = vm.get_all_ipv4(*config->ssh_key_provider); if (is_ipv4_valid(management_ip)) entry->add_ipv4(management_ip); @@ -1853,18 +1858,44 @@ try // clang-format on if (extra_ipv4 != management_ip) entry->add_ipv4(extra_ipv4); } - } - for (const auto& instance : deleted_instances) + return grpc::Status::OK; + }; + + auto fetch_snapshot = [&](VirtualMachine& vm) { + fmt::memory_buffer errors; + const auto& name = vm.vm_name; + + try + { + for (const auto& snapshot : vm.view_snapshots()) + { + auto entry = response.mutable_snapshots()->add_info(); + auto fundamentals = entry->mutable_fundamentals(); + + entry->set_name(name); + populate_snapshot_fundamentals(snapshot, fundamentals); + } + } + catch (const NoSuchSnapshot& e) + { + add_fmt_to(errors, e.what()); + } + + return grpc_status_for(errors); + }; + + auto cmd = request->snapshots() ? std::function(fetch_snapshot) : std::function(fetch_instance); + + auto status = cmd_vms(select_all(operative_instances), cmd); + if (status.ok() && !request->snapshots()) { - const auto& name = instance.first; - auto entry = response.mutable_instances()->add_info(); - entry->set_name(name); - entry->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); + deleted = true; + status = cmd_vms(select_all(deleted_instances), cmd); } server->Write(response); - status_promise->set_value(grpc::Status::OK); + status_promise->set_value(status); } catch (const std::exception& e) { diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 1030608a722..f58ee62fbcb 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -1413,6 +1413,10 @@ TEST_F(Daemon, reads_mac_addresses_from_json) const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces)); + EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { + return std::make_unique(desc.vm_name); + })); + // Make the daemon look for the JSON on our temporary directory. It will read the contents of the file. config_builder.data_directory = temp_dir->path(); mp::Daemon daemon{config_builder.build()}; @@ -1476,6 +1480,10 @@ TEST_F(Daemon, writesAndReadsMountsInJson) const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces, mounts)); + EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { + return std::make_unique(desc.vm_name); + })); + // Make the daemon look for the JSON on our temporary directory. It will read the contents of the file. config_builder.data_directory = temp_dir->path(); mp::Daemon daemon{config_builder.build()}; @@ -1511,6 +1519,10 @@ TEST_F(Daemon, writes_and_reads_ordered_maps_in_json) const auto [temp_dir, filename] = plant_instance_json(fake_json_contents("52:54:00:73:76:29", std::vector{}, mounts)); + EXPECT_CALL(*use_a_mock_vm_factory(), create_virtual_machine).WillRepeatedly(WithArg<0>([](const auto& desc) { + return std::make_unique(desc.vm_name); + })); + config_builder.data_directory = temp_dir->path(); mp::Daemon daemon{config_builder.build()}; @@ -1709,7 +1721,9 @@ TEST_F(Daemon, ctor_drops_removed_instances) auto mock_factory = use_a_mock_vm_factory(); EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, stayed), _)) - .Times(1); + .Times(1) + .WillRepeatedly( + WithArg<0>([](const auto& desc) { return std::make_unique(desc.vm_name); })); EXPECT_CALL(*mock_factory, create_virtual_machine(Field(&mp::VirtualMachineDescription::vm_name, gone), _)) .Times(0); From c4cad4aab893f1951d32320fdfd1d54c54204132 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 10 Aug 2023 12:08:33 -0700 Subject: [PATCH 421/627] [tests] add list snapshots tests for table formatter --- tests/test_output_formatter.cpp | 121 ++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 6 deletions(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 41c7ba7c4d8..c3246a5dbb8 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -47,6 +47,13 @@ auto construct_empty_list_reply() return list_reply; } +auto construct_empty_list_snapshot_reply() +{ + mp::ListReply list_reply; + list_reply.mutable_snapshots(); + return list_reply; +} + auto construct_single_instance_list_reply() { mp::ListReply list_reply; @@ -108,12 +115,92 @@ auto construct_unsorted_list_reply() return list_reply; } +auto construct_single_snapshot_list_reply() +{ + mp::ListReply list_reply; + + auto list_entry = list_reply.mutable_snapshots()->add_info(); + list_entry->set_name("foo"); + + auto fundamentals = list_entry->mutable_fundamentals(); + fundamentals->set_snapshot_name("snapshot1"); + fundamentals->set_comment("This is a sample comment"); + + google::protobuf::Timestamp timestamp; + timestamp.set_seconds(time(nullptr)); + timestamp.set_nanos(0); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return list_reply; +} + +auto construct_multiple_snapshots_list_reply() +{ + mp::ListReply list_reply; + + auto list_entry = list_reply.mutable_snapshots()->add_info(); + auto fundamentals = list_entry->mutable_fundamentals(); + google::protobuf::Timestamp timestamp; + + list_entry->set_name("prosperous-spadefish"); + fundamentals->set_snapshot_name("snapshot10"); + fundamentals->set_parent("snapshot2"); + timestamp.set_seconds(1672531200); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + list_entry = list_reply.mutable_snapshots()->add_info(); + fundamentals = list_entry->mutable_fundamentals(); + list_entry->set_name("hale-roller"); + fundamentals->set_snapshot_name("rolling"); + fundamentals->set_parent("pristine"); + fundamentals->set_comment("Loaded with stuff"); + timestamp.set_seconds(25425952800); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + list_entry = list_reply.mutable_snapshots()->add_info(); + fundamentals = list_entry->mutable_fundamentals(); + list_entry->set_name("hale-roller"); + fundamentals->set_snapshot_name("rocking"); + fundamentals->set_parent("pristine"); + fundamentals->set_comment("A very long comment that should be truncated by the table formatter"); + timestamp.set_seconds(2209234259); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + list_entry = list_reply.mutable_snapshots()->add_info(); + fundamentals = list_entry->mutable_fundamentals(); + list_entry->set_name("hale-roller"); + fundamentals->set_snapshot_name("pristine"); + fundamentals->set_comment("A first snapshot"); + timestamp.set_seconds(409298914); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + list_entry = list_reply.mutable_snapshots()->add_info(); + fundamentals = list_entry->mutable_fundamentals(); + list_entry->set_name("prosperous-spadefish"); + fundamentals->set_snapshot_name("snapshot2"); + fundamentals->set_comment("Before restoring snap1\nContains a newline that\r\nshould be truncated"); + timestamp.set_seconds(1671840000); + fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); + + return list_reply; +} + auto add_petenv_to_reply(mp::ListReply& reply) { - auto instance = reply.mutable_instances()->add_info(); - instance->set_name(petenv_name()); - instance->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); - instance->set_current_release("Not Available"); + if (reply.has_instances()) + { + auto instance = reply.mutable_instances()->add_info(); + instance->set_name(petenv_name()); + instance->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); + instance->set_current_release("Not Available"); + } + else + { + auto snapshot = reply.mutable_snapshots()->add_info(); + snapshot->set_name(petenv_name()); + snapshot->mutable_fundamentals()->set_snapshot_name("snapshot1"); + snapshot->mutable_fundamentals()->set_comment("An exemplary comment"); + } } auto construct_one_short_line_networks_reply() @@ -796,9 +883,12 @@ const mp::CSVFormatter csv_formatter; const mp::YamlFormatter yaml_formatter; const auto empty_list_reply = construct_empty_list_reply(); +const auto empty_list_snapshot_reply = construct_empty_list_snapshot_reply(); const auto single_instance_list_reply = construct_single_instance_list_reply(); const auto multiple_instances_list_reply = construct_multiple_instances_list_reply(); const auto unsorted_list_reply = construct_unsorted_list_reply(); +const auto single_snapshot_list_reply = construct_single_snapshot_list_reply(); +const auto multiple_snapshots_list_reply = construct_multiple_snapshots_list_reply(); const auto empty_networks_reply = mp::NetworksReply(); const auto one_short_line_networks_reply = construct_one_short_line_networks_reply(); @@ -819,6 +909,7 @@ const auto multiple_snapshot_overview_info_reply = construct_multiple_snapshot_o const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &empty_list_reply, "No instances found.\n", "table_list_empty"}, + {&table_formatter, &empty_list_snapshot_reply, "No snapshots found.\n", "table_list_snapshot_empty"}, {&table_formatter, &single_instance_list_reply, "Name State IPv4 Image\n" "foo Running 10.168.32.2 Ubuntu 16.04 LTS\n" @@ -838,6 +929,18 @@ const std::vector orderable_list_info_formatter_outputs{ "trusty-190611-1539 Suspended -- Not Available\n" "trusty-190611-1542 Running -- Ubuntu N/A\n", "table_list_unsorted"}, + {&table_formatter, &single_snapshot_list_reply, + "Instance Snapshot Parent Comment\n" + "foo snapshot1 -- This is a sample comment\n", + "table_list_single_snapshot"}, + {&table_formatter, &multiple_snapshots_list_reply, + "Instance Snapshot Parent Comment\n" + "hale-roller pristine -- A first snapshot\n" + "hale-roller rocking pristine A very long comment that should be truncated by t…\n" + "hale-roller rolling pristine Loaded with stuff\n" + "prosperous-spadefish snapshot2 -- Before restoring snap1…\n" + "prosperous-spadefish snapshot10 snapshot2 --\n", + "table_list_multiple_snapshots"}, {&table_formatter, &empty_info_reply, "\n", "table_info_empty"}, {&table_formatter, &empty_snapshot_overview_reply, "No snapshots found.\n", "table_snapshot_overview_empty"}, @@ -2391,6 +2494,12 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) if (auto input = dynamic_cast(reply)) { mp::ListReply reply_copy; + + if (input->has_instances()) + reply_copy.mutable_instances(); + else + reply_copy.mutable_snapshots(); + if (prepend) { add_petenv_to_reply(reply_copy); @@ -2404,9 +2513,9 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) output = formatter->format(reply_copy); if (dynamic_cast(formatter)) - regex = fmt::format("Name[[:print:]]*\n{}[[:space:]]+.*", petenv_name()); + regex = fmt::format("((Name|Instance)[[:print:]]*\n{0}[[:space:]]+.*)", petenv_name()); else if (dynamic_cast(formatter)) - regex = fmt::format("Name[[:print:]]*\n{},.*", petenv_name()); + regex = fmt::format("(Name|Instance)[[:print:]]*\n{},.*", petenv_name()); else if (dynamic_cast(formatter)) regex = fmt::format("{}:.*", petenv_name()); else From 6326404dd72cac5551d29698ef3f65859c7c6907 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 21 Aug 2023 14:34:03 -0700 Subject: [PATCH 422/627] [csv] add snapshot list output to csv formatter --- src/client/cli/formatter/csv_formatter.cpp | 55 +++++++++++++++++----- tests/test_output_formatter.cpp | 10 ++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index e9c4a55d35b..23b53685f44 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -148,6 +148,41 @@ std::string generate_snapshot_overview_report(const mp::InfoReply& reply) return fmt::to_string(buf); } + +std::string generate_instances_list(const mp::InstancesList& instances_list) +{ + fmt::memory_buffer buf; + + fmt::format_to(std::back_inserter(buf), "Name,State,IPv4,IPv6,Release,AllIPv4\n"); + + for (const auto& instance : mp::format::sorted(instances_list.info())) + { + fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},\"{}\"\n", instance.name(), + mp::format::status_string_for(instance.instance_status()), + instance.ipv4_size() ? instance.ipv4(0) : "", instance.ipv6_size() ? instance.ipv6(0) : "", + instance.current_release().empty() ? "Not Available" + : fmt::format("Ubuntu {}", instance.current_release()), + fmt::join(instance.ipv4(), ",")); + } + + return fmt::to_string(buf); +} + +std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +{ + fmt::memory_buffer buf; + + fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n"); + + for (const auto& item : mp::format::sorted(snapshots_list.info())) + { + const auto& snapshot = item.fundamentals(); + fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.name(), snapshot.snapshot_name(), + snapshot.parent(), snapshot.comment()); + } + + return fmt::to_string(buf); +} } // namespace std::string mp::CSVFormatter::format(const InfoReply& reply) const @@ -169,21 +204,19 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const std::string mp::CSVFormatter::format(const ListReply& reply) const { - fmt::memory_buffer buf; - - fmt::format_to(std::back_inserter(buf), "Name,State,IPv4,IPv6,Release,AllIPv4\n"); + std::string output; - for (const auto& instance : format::sorted(reply.instances().info())) + if (reply.has_instances()) { - fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},\"{}\"\n", instance.name(), - mp::format::status_string_for(instance.instance_status()), - instance.ipv4_size() ? instance.ipv4(0) : "", instance.ipv6_size() ? instance.ipv6(0) : "", - instance.current_release().empty() ? "Not Available" - : fmt::format("Ubuntu {}", instance.current_release()), - fmt::join(instance.ipv4(), ",")); + output = generate_instances_list(reply.instances()); + } + else + { + assert(reply.has_snapshots() && "either one of instances or snapshots should be populated"); + output = generate_snapshots_list(reply.snapshots()); } - return fmt::to_string(buf); + return output; } std::string mp::CSVFormatter::format(const NetworksReply& reply) const diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index c3246a5dbb8..e6f4fa7a86a 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -1145,6 +1145,16 @@ const std::vector orderable_list_info_formatter_outputs{ "trusty-190611-1539,Suspended,,,Not Available,\"\"\n" "trusty-190611-1542,Running,,,Ubuntu N/A,\"\"\n", "csv_list_unsorted"}, + {&csv_formatter, &empty_list_snapshot_reply, "Instance,Snapshot,Parent,Comment\n", "csv_list_snapshot_empty"}, + {&csv_formatter, &single_snapshot_list_reply, + "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,\"This is a sample comment\"\n", "csv_list_single_snapshot"}, + {&csv_formatter, &multiple_snapshots_list_reply, + "Instance,Snapshot,Parent,Comment\nhale-roller,pristine,,\"A first " + "snapshot\"\nhale-roller,rocking,pristine,\"A very long comment that should be truncated by the table " + "formatter\"\nhale-roller,rolling,pristine,\"Loaded with stuff\"\nprosperous-spadefish,snapshot2,,\"Before " + "restoring snap1\nContains a newline that\r\nshould be " + "truncated\"\nprosperous-spadefish,snapshot10,snapshot2,\"\"\n", + "csv_list_multiple_snapshots"}, {&csv_formatter, &empty_info_reply, "", "csv_info_empty"}, {&csv_formatter, &empty_snapshot_overview_reply, "Instance,Snapshot,Parent,Comment\n", From ae3dde34309d8426c3c25883bdf63ce64503bc3a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 21 Aug 2023 14:34:24 -0700 Subject: [PATCH 423/627] [yaml] add snapshot list output to yaml formatter --- src/client/cli/formatter/yaml_formatter.cpp | 65 ++++++++++++++++----- tests/test_output_formatter.cpp | 28 ++++++++- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index ab0567ca403..6239981b243 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -212,6 +212,48 @@ YAML::Node generate_snapshot_overview_report(const mp::InfoReply& reply) return info_node; } + +std::string generate_instances_list(const mp::InstancesList& instances_list) +{ + YAML::Node list; + + for (const auto& instance : mp::format::sorted(instances_list.info())) + { + YAML::Node instance_node; + instance_node["state"] = mp::format::status_string_for(instance.instance_status()); + + instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); + for (const auto& ip : instance.ipv4()) + instance_node["ipv4"].push_back(ip); + + instance_node["release"] = + instance.current_release().empty() ? "Not Available" : fmt::format("Ubuntu {}", instance.current_release()); + + list[instance.name()].push_back(instance_node); + } + + return mpu::emit_yaml(list); +} + +std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +{ + YAML::Node info_node; + + for (const auto& item : mp::format::sorted(snapshots_list.info())) + { + const auto& snapshot = item.fundamentals(); + YAML::Node instance_node; + YAML::Node snapshot_node; + + snapshot_node["parent"] = snapshot.parent().empty() ? YAML::Node() : YAML::Node(snapshot.parent()); + snapshot_node["comment"] = snapshot.comment().empty() ? YAML::Node() : YAML::Node(snapshot.comment()); + + instance_node[snapshot.snapshot_name()].push_back(snapshot_node); + info_node[item.name()].push_back(instance_node); + } + + return mpu::emit_yaml(info_node); +} } // namespace std::string mp::YamlFormatter::format(const InfoReply& reply) const @@ -233,24 +275,19 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const std::string mp::YamlFormatter::format(const ListReply& reply) const { - YAML::Node list; + std::string output; - for (const auto& instance : format::sorted(reply.instances().info())) + if (reply.has_instances()) { - YAML::Node instance_node; - instance_node["state"] = mp::format::status_string_for(instance.instance_status()); - - instance_node["ipv4"] = YAML::Node(YAML::NodeType::Sequence); - for (const auto& ip : instance.ipv4()) - instance_node["ipv4"].push_back(ip); - - instance_node["release"] = - instance.current_release().empty() ? "Not Available" : fmt::format("Ubuntu {}", instance.current_release()); - - list[instance.name()].push_back(instance_node); + output = generate_instances_list(reply.instances()); + } + else + { + assert(reply.has_snapshots() && "eitherr one of instances or snapshots should be populated"); + output = generate_snapshots_list(reply.snapshots()); } - return mpu::emit_yaml(list); + return output; } std::string mp::YamlFormatter::format(const NetworksReply& reply) const diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index e6f4fa7a86a..dc37c0815bb 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -1242,9 +1242,35 @@ const std::vector orderable_list_info_formatter_outputs{ " []\n" " release: Ubuntu N/A\n", "yaml_list_unsorted"}, + {&yaml_formatter, &empty_list_snapshot_reply, "\n", "yaml_list_snapshot_empty"}, + {&yaml_formatter, &single_snapshot_list_reply, + "foo:\n" + " - snapshot1:\n" + " - parent: ~\n" + " comment: This is a sample comment\n", + "yaml_list_single_snapshot"}, + {&yaml_formatter, &multiple_snapshots_list_reply, + "hale-roller:\n" + " - pristine:\n" + " - parent: ~\n" + " comment: A first snapshot\n" + " - rocking:\n" + " - parent: pristine\n" + " comment: A very long comment that should be truncated by the table formatter\n" + " - rolling:\n" + " - parent: pristine\n" + " comment: Loaded with stuff\n" + "prosperous-spadefish:\n" + " - snapshot2:\n" + " - parent: ~\n" + " comment: \"Before restoring snap1\\nContains a newline that\\r\\nshould be truncated\"\n" + " - snapshot10:\n" + " - parent: snapshot2\n" + " comment: ~\n", + "yaml_list_multiple_snapshots"}, + {&yaml_formatter, &empty_info_reply, "errors:\n - ~\n", "yaml_info_empty"}, {&yaml_formatter, &empty_snapshot_overview_reply, "errors:\n - ~\n", "yaml_snapshot_overview_empty"}, - {&yaml_formatter, &single_instance_info_reply, "errors:\n" " - ~\n" From 02a9f058ee2bdc3acfbf4fd4058ff4a1416458e7 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 21 Aug 2023 14:34:40 -0700 Subject: [PATCH 424/627] [json] add snapshot list output to json formatter --- src/client/cli/formatter/json_formatter.cpp | 97 ++++++++++++++++----- tests/test_output_formatter.cpp | 46 ++++++++++ 2 files changed, 120 insertions(+), 23 deletions(-) diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 03fb7195430..a34133d5a90 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -174,7 +174,7 @@ QJsonObject generate_instance_details(const mp::DetailedInfoItem& item) return instance_info; } -QJsonObject generate_instance_info_report(const mp::InfoReply& reply) +std::string generate_instance_info_report(const mp::InfoReply& reply) { QJsonObject info_json; QJsonObject info_obj; @@ -237,10 +237,10 @@ QJsonObject generate_instance_info_report(const mp::InfoReply& reply) } info_json.insert("info", info_obj); - return info_json; + return mp::json_to_string(info_json); } -QJsonObject generate_snapshot_overview_report(const mp::InfoReply& reply) +std::string generate_snapshot_overview_report(const mp::InfoReply& reply) { QJsonObject info_json; QJsonObject info_obj; @@ -271,33 +271,15 @@ QJsonObject generate_snapshot_overview_report(const mp::InfoReply& reply) info_json.insert("info", info_obj); - return info_json; -} -} // namespace - -std::string mp::JsonFormatter::format(const InfoReply& reply) const -{ - QJsonObject info_json; - - if (reply.has_detailed_report()) - { - info_json = generate_instance_info_report(reply); - } - else - { - assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - info_json = generate_snapshot_overview_report(reply); - } - return mp::json_to_string(info_json); } -std::string mp::JsonFormatter::format(const ListReply& reply) const +std::string generate_instances_list(const mp::InstancesList& instances_list) { QJsonObject list_json; QJsonArray instances; - for (const auto& instance : reply.instances().info()) + for (const auto& instance : instances_list.info()) { QJsonObject instance_obj; instance_obj.insert("name", QString::fromStdString(instance.name())); @@ -321,6 +303,75 @@ std::string mp::JsonFormatter::format(const ListReply& reply) const return mp::json_to_string(list_json); } +std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +{ + QJsonObject info_json; + QJsonObject info_obj; + + info_json.insert("errors", QJsonArray()); + + for (const auto& item : snapshots_list.info()) + { + const auto& snapshot = item.fundamentals(); + QJsonObject snapshot_obj; + + snapshot_obj.insert("parent", QString::fromStdString(snapshot.parent())); + snapshot_obj.insert("comment", QString::fromStdString(snapshot.comment())); + + auto it = info_obj.find(QString::fromStdString(item.name())); + if (it == info_obj.end()) + { + info_obj.insert(QString::fromStdString(item.name()), + QJsonObject{{QString::fromStdString(snapshot.snapshot_name()), snapshot_obj}}); + } + else + { + QJsonObject obj = it.value().toObject(); + obj.insert(QString::fromStdString(snapshot.snapshot_name()), snapshot_obj); + it.value() = obj; + } + } + + info_json.insert("info", info_obj); + + return mp::json_to_string(info_json); +} +} // namespace + +std::string mp::JsonFormatter::format(const InfoReply& reply) const +{ + std::string output; + + if (reply.has_detailed_report()) + { + output = generate_instance_info_report(reply); + } + else + { + assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); + output = generate_snapshot_overview_report(reply); + } + + return output; +} + +std::string mp::JsonFormatter::format(const ListReply& reply) const +{ + std::string output; + + if (reply.has_instances()) + { + output = generate_instances_list(reply.instances()); + } + else + { + assert(reply.has_snapshots() && "either one of the reports should be populated"); + output = generate_snapshots_list(reply.snapshots()); + } + + return output; +} + std::string mp::JsonFormatter::format(const NetworksReply& reply) const { QJsonObject list_json; diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index dc37c0815bb..0a5fa331faa 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -1615,6 +1615,52 @@ const std::vector non_orderable_list_info_formatter_outputs{ " ]\n" "}\n", "json_list_multiple"}, + {&json_formatter, &single_snapshot_list_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"foo\": {\n" + " \"snapshot1\": {\n" + " \"comment\": \"This is a sample comment\",\n" + " \"parent\": \"\"\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_list_single_snapshot"}, + {&json_formatter, &multiple_snapshots_list_reply, + "{\n" + " \"errors\": [\n" + " ],\n" + " \"info\": {\n" + " \"hale-roller\": {\n" + " \"pristine\": {\n" + " \"comment\": \"A first snapshot\",\n" + " \"parent\": \"\"\n" + " },\n" + " \"rocking\": {\n" + " \"comment\": \"A very long comment that should be truncated by the table formatter\",\n" + " \"parent\": \"pristine\"\n" + " },\n" + " \"rolling\": {\n" + " \"comment\": \"Loaded with stuff\",\n" + " \"parent\": \"pristine\"\n" + " }\n" + " },\n" + " \"prosperous-spadefish\": {\n" + " \"snapshot10\": {\n" + " \"comment\": \"\",\n" + " \"parent\": \"snapshot2\"\n" + " },\n" + " \"snapshot2\": {\n" + " \"comment\": \"Before restoring snap1\\nContains a newline that\\r\\nshould be truncated\",\n" + " \"parent\": \"\"\n" + " }\n" + " }\n" + " }\n" + "}\n", + "json_list_multiple_snapshots"}, {&json_formatter, &empty_info_reply, "{\n" " \"errors\": [\n" From a1fab161e90dac65476be894c17ae23eb628603b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 21 Aug 2023 19:52:16 -0700 Subject: [PATCH 425/627] tear out `snapshot-overview` internals --- include/multipass/cli/format_utils.h | 31 -- src/client/cli/cmd/exec.cpp | 2 +- src/client/cli/cmd/info.cpp | 4 +- src/client/cli/formatter/csv_formatter.cpp | 47 +-- src/client/cli/formatter/json_formatter.cpp | 169 ++++------- src/client/cli/formatter/table_formatter.cpp | 99 ++----- src/client/cli/formatter/yaml_formatter.cpp | 75 ++--- src/daemon/daemon.cpp | 59 +--- src/rpc/multipass.proto | 21 +- tests/test_cli_client.cpp | 31 +- tests/test_daemon.cpp | 18 +- tests/test_output_formatter.cpp | 287 +++---------------- 12 files changed, 153 insertions(+), 690 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index a041edb4af5..f7907077304 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -43,9 +43,6 @@ Formatter* formatter_for(const std::string& format); template Instances sorted(const Instances& instances); -template -Snapshots sort_snapshots(const Snapshots& snapshots); - template Details sort_instances_and_snapshots(const Details& details); @@ -103,34 +100,6 @@ Container multipass::format::sorted(const Container& instances) return ret; } -// TODO@snapshots DRY -template -Snapshots multipass::format::sort_snapshots(const Snapshots& snapshots) -{ - using google::protobuf::util::TimeUtil; - if (snapshots.empty()) - return snapshots; - - auto ret = snapshots; - const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString(); - std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) { - if (a.instance_name() == petenv_name && b.instance_name() != petenv_name) - return true; - else if (a.instance_name() != petenv_name && b.instance_name() == petenv_name) - return false; - - if (a.instance_name() < b.instance_name()) - return true; - else if (a.instance_name() > b.instance_name()) - return false; - - return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < - TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); - }); - - return ret; -} - template Details multipass::format::sort_instances_and_snapshots(const Details& details) { diff --git a/src/client/cli/cmd/exec.cpp b/src/client/cli/cmd/exec.cpp index 9dc8fc9c46b..e26036a93db 100644 --- a/src/client/cli/cmd/exec.cpp +++ b/src/client/cli/cmd/exec.cpp @@ -78,7 +78,7 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser) QStringList split_exec_dir = clean_exec_dir.split('/'); auto on_info_success = [&work_dir, &split_exec_dir](mp::InfoReply& reply) { - for (const auto& mount : reply.detailed_report().details(0).mount_info().mount_paths()) + for (const auto& mount : reply.details(0).mount_info().mount_paths()) { auto source_dir = QDir(QString::fromStdString(mount.source_path())); auto clean_source_dir = QDir::cleanPath(source_dir.absolutePath()); diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 209feb5bee5..9a5885cef40 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -70,9 +70,8 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) format_option_name, "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", format_option_name, "table"); - QCommandLineOption snapshotOverviewOption("snapshot-overview", "Display info on snapshots"); - parser->addOptions({all_option, noRuntimeInfoOption, formatOption, snapshotOverviewOption}); + parser->addOptions({all_option, noRuntimeInfoOption, formatOption}); auto status = parser->commandParse(this); if (status != ParseCode::Ok) @@ -96,7 +95,6 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) } request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); - request.set_snapshot_overview(parser->isSet(snapshotOverviewOption)); if (instance_found && snapshot_found && parser->value(format_option_name) == "csv") { diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 23b53685f44..08f0bee551e 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -69,7 +69,7 @@ std::string generate_snapshot_details(const mp::InfoReply reply) fmt::format_to(std::back_inserter(buf), "Snapshot,Instance,CPU(s),Disk space,Memory size,Mounts,Created,Parent,Children,Comment\n"); - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) { const auto& fundamentals = info.snapshot_info().fundamentals(); @@ -95,7 +95,7 @@ std::string generate_instance_details(const mp::InfoReply reply) "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory " "total,Mounts,AllIPv4,CPU(s),Snapshots\n"); - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) { assert(info.has_instance_info() && "outputting instance and snapshot details together is not supported in csv format"); @@ -118,37 +118,6 @@ std::string generate_instance_details(const mp::InfoReply reply) return fmt::to_string(buf); } -std::string generate_instance_info_report(const mp::InfoReply& reply) -{ - std::string output; - - if (reply.detailed_report().details_size() > 0) - { - if (reply.detailed_report().details()[0].has_instance_info()) - output = generate_instance_details(reply); - else - output = generate_snapshot_details(reply); - } - - return output; -} - -std::string generate_snapshot_overview_report(const mp::InfoReply& reply) -{ - fmt::memory_buffer buf; - - fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n"); - - for (const auto& item : mp::format::sort_snapshots(reply.snapshot_overview().overview())) - { - const auto& snapshot = item.fundamentals(); - fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.instance_name(), snapshot.snapshot_name(), - snapshot.parent(), snapshot.comment()); - } - - return fmt::to_string(buf); -} - std::string generate_instances_list(const mp::InstancesList& instances_list) { fmt::memory_buffer buf; @@ -189,14 +158,12 @@ std::string mp::CSVFormatter::format(const InfoReply& reply) const { std::string output; - if (reply.has_detailed_report()) - { - output = generate_instance_info_report(reply); - } - else + if (reply.details_size() > 0) { - assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - output = generate_snapshot_overview_report(reply); + if (reply.details()[0].has_instance_info()) + output = generate_instance_details(reply); + else + output = generate_snapshot_details(reply); } return output; diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index a34133d5a90..e9504137042 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -174,106 +174,6 @@ QJsonObject generate_instance_details(const mp::DetailedInfoItem& item) return instance_info; } -std::string generate_instance_info_report(const mp::InfoReply& reply) -{ - QJsonObject info_json; - QJsonObject info_obj; - info_json.insert("errors", QJsonArray()); - - for (const auto& info : reply.detailed_report().details()) - { - auto instance_it = info_obj.find(QString::fromStdString(info.name())); - - if (info.has_instance_info()) - { - auto instance_details = generate_instance_details(info); - - // Nothing for the instance so far, so insert normally - if (instance_it == info_obj.end()) - { - info_obj.insert(QString::fromStdString(info.name()), instance_details); - } - // Some instance details already exist, so merge the values - else - { - QJsonObject obj = instance_it.value().toObject(); - for (const auto& key : instance_details.keys()) - { - assert(obj.find(key) == obj.end() && "key already exists"); - obj.insert(key, instance_details[key]); - } - instance_it.value() = obj; - } - } - else - { - assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); - - auto snapshot_details = generate_snapshot_details(info); - - // Nothing for the instance so far, so create the "snapshots" node and put snapshot details there - if (instance_it == info_obj.end()) - { - QJsonObject instance_obj, snapshot_obj; - snapshot_obj.insert(QString::fromStdString(info.snapshot_info().fundamentals().snapshot_name()), - snapshot_details); - instance_obj.insert("snapshots", snapshot_obj); - info_obj.insert(QString::fromStdString(info.name()), instance_obj); - } - // Some instance details already exist - else - { - auto instance_obj = instance_it.value().toObject(); - auto snapshots_it = instance_obj.find("snapshots"); - QJsonObject snapshots_obj = - snapshots_it == instance_obj.end() ? QJsonObject() : snapshots_it.value().toObject(); - - snapshots_obj.insert(QString::fromStdString(info.snapshot_info().fundamentals().snapshot_name()), - snapshot_details); - instance_obj.insert("snapshots", snapshots_obj); - instance_it.value() = instance_obj; - } - } - } - info_json.insert("info", info_obj); - - return mp::json_to_string(info_json); -} - -std::string generate_snapshot_overview_report(const mp::InfoReply& reply) -{ - QJsonObject info_json; - QJsonObject info_obj; - - info_json.insert("errors", QJsonArray()); - - for (const auto& item : reply.snapshot_overview().overview()) - { - const auto& snapshot = item.fundamentals(); - QJsonObject snapshot_obj; - - snapshot_obj.insert("parent", QString::fromStdString(snapshot.parent())); - snapshot_obj.insert("comment", QString::fromStdString(snapshot.comment())); - - auto it = info_obj.find(QString::fromStdString(item.instance_name())); - if (it == info_obj.end()) - { - info_obj.insert(QString::fromStdString(item.instance_name()), - QJsonObject{{QString::fromStdString(snapshot.snapshot_name()), snapshot_obj}}); - } - else - { - QJsonObject obj = it.value().toObject(); - obj.insert(QString::fromStdString(snapshot.snapshot_name()), snapshot_obj); - it.value() = obj; - } - } - - info_json.insert("info", info_obj); - - return mp::json_to_string(info_json); -} - std::string generate_instances_list(const mp::InstancesList& instances_list) { QJsonObject list_json; @@ -307,7 +207,6 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) { QJsonObject info_json; QJsonObject info_obj; - info_json.insert("errors", QJsonArray()); for (const auto& item : snapshots_list.info()) @@ -340,19 +239,69 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) std::string mp::JsonFormatter::format(const InfoReply& reply) const { - std::string output; + QJsonObject info_json; + QJsonObject info_obj; - if (reply.has_detailed_report()) - { - output = generate_instance_info_report(reply); - } - else + info_json.insert("errors", QJsonArray()); + + for (const auto& info : reply.details()) { - assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - output = generate_snapshot_overview_report(reply); + auto instance_it = info_obj.find(QString::fromStdString(info.name())); + + if (info.has_instance_info()) + { + auto instance_details = generate_instance_details(info); + + // Nothing for the instance so far, so insert normally + if (instance_it == info_obj.end()) + { + info_obj.insert(QString::fromStdString(info.name()), instance_details); + } + // Some instance details already exist, so merge the values + else + { + QJsonObject obj = instance_it.value().toObject(); + for (const auto& key : instance_details.keys()) + { + assert(obj.find(key) == obj.end() && "key already exists"); + obj.insert(key, instance_details[key]); + } + instance_it.value() = obj; + } + } + else + { + assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); + + auto snapshot_details = generate_snapshot_details(info); + + // Nothing for the instance so far, so create the "snapshots" node and put snapshot details there + if (instance_it == info_obj.end()) + { + QJsonObject instance_obj, snapshot_obj; + snapshot_obj.insert(QString::fromStdString(info.snapshot_info().fundamentals().snapshot_name()), + snapshot_details); + instance_obj.insert("snapshots", snapshot_obj); + info_obj.insert(QString::fromStdString(info.name()), instance_obj); + } + // Some instance details already exist + else + { + auto instance_obj = instance_it.value().toObject(); + auto snapshots_it = instance_obj.find("snapshots"); + QJsonObject snapshots_obj = + snapshots_it == instance_obj.end() ? QJsonObject() : snapshots_it.value().toObject(); + + snapshots_obj.insert(QString::fromStdString(info.snapshot_info().fundamentals().snapshot_name()), + snapshot_details); + instance_obj.insert("snapshots", snapshots_obj); + instance_it.value() = instance_obj; + } + } } + info_json.insert("info", info_obj); - return output; + return mp::json_to_string(info_json); } std::string mp::JsonFormatter::format(const ListReply& reply) const diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 990a5521825..8e58a87f3f7 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -192,80 +192,6 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) return fmt::to_string(buf); } -std::string generate_instance_info_report(const mp::InfoReply& reply) -{ - fmt::memory_buffer buf; - - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) - { - if (info.has_instance_info()) - { - fmt::format_to(std::back_inserter(buf), generate_instance_details(info)); - } - else - { - assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); - fmt::format_to(std::back_inserter(buf), generate_snapshot_details(info)); - } - - fmt::format_to(std::back_inserter(buf), "\n"); - } - - std::string output = fmt::to_string(buf); - if (!reply.detailed_report().details().empty()) - output.pop_back(); - else - output = "\n"; - - return output; -} - -std::string generate_snapshot_overview_report(const mp::InfoReply& reply) -{ - auto overview = reply.snapshot_overview().overview(); - if (overview.empty()) - return "No snapshots found.\n"; - - fmt::memory_buffer buf; - const std::string name_col_header = "Instance", snapshot_col_header = "Snapshot", parent_col_header = "Parent", - comment_col_header = "Comment"; - const auto name_column_width = mp::format::column_width( - overview.begin(), overview.end(), [](const auto& item) -> int { return item.instance_name().length(); }, - name_col_header.length()); - const auto snapshot_column_width = mp::format::column_width( - overview.begin(), overview.end(), - [](const auto& item) -> int { return item.fundamentals().snapshot_name().length(); }, - snapshot_col_header.length()); - const auto parent_column_width = mp::format::column_width( - overview.begin(), overview.end(), [](const auto& item) -> int { return item.fundamentals().parent().length(); }, - parent_col_header.length()); - - const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; - - fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, snapshot_col_header, - snapshot_column_width, parent_col_header, parent_column_width, comment_col_header); - - for (const auto& item : mp::format::sort_snapshots(overview)) - { - size_t max_comment_column_width = 50; - std::smatch match; - auto snapshot = item.fundamentals(); - - if (std::regex_search(snapshot.comment().begin(), snapshot.comment().end(), match, newline)) - max_comment_column_width = std::min((size_t)(match.position(1)) + 1, max_comment_column_width); - - fmt::format_to(std::back_inserter(buf), row_format, item.instance_name(), name_column_width, - snapshot.snapshot_name(), snapshot_column_width, - snapshot.parent().empty() ? "--" : snapshot.parent(), parent_column_width, - snapshot.comment().empty() ? "--" - : snapshot.comment().length() > max_comment_column_width - ? fmt::format("{}…", snapshot.comment().substr(0, max_comment_column_width - 1)) - : snapshot.comment()); - } - - return fmt::to_string(buf); -} - std::string generate_instances_list(const mp::InstancesList& instances_list) { fmt::memory_buffer buf; @@ -357,17 +283,28 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) std::string mp::TableFormatter::format(const InfoReply& reply) const { - std::string output; + fmt::memory_buffer buf; - if (reply.has_detailed_report()) + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) { - output = generate_instance_info_report(reply); + if (info.has_instance_info()) + { + fmt::format_to(std::back_inserter(buf), generate_instance_details(info)); + } + else + { + assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); + fmt::format_to(std::back_inserter(buf), generate_snapshot_details(info)); + } + + fmt::format_to(std::back_inserter(buf), "\n"); } + + std::string output = fmt::to_string(buf); + if (!reply.details().empty()) + output.pop_back(); else - { - assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - output = generate_snapshot_overview_report(reply); - } + output = "\n"; return output; } diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 6239981b243..4e1c051532d 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -165,54 +165,6 @@ YAML::Node generate_instance_details(const mp::DetailedInfoItem& item) return instance_node; } -YAML::Node generate_instance_info_report(const mp::InfoReply& reply) -{ - YAML::Node info_node; - - info_node["errors"].push_back(YAML::Null); - - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.detailed_report().details())) - { - if (info.has_instance_info()) - { - info_node[info.name()].push_back(generate_instance_details(info)); - } - else - { - assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); - - YAML::Node snapshot_node; - snapshot_node[info.snapshot_info().fundamentals().snapshot_name()] = generate_snapshot_details(info); - - info_node[info.name()][0]["snapshots"].push_back(snapshot_node); - } - } - - return info_node; -} - -YAML::Node generate_snapshot_overview_report(const mp::InfoReply& reply) -{ - YAML::Node info_node; - - info_node["errors"].push_back(YAML::Null); - - for (const auto& item : mp::format::sort_snapshots(reply.snapshot_overview().overview())) - { - const auto& snapshot = item.fundamentals(); - YAML::Node instance_node; - YAML::Node snapshot_node; - - snapshot_node["parent"] = snapshot.parent().empty() ? YAML::Node() : YAML::Node(snapshot.parent()); - snapshot_node["comment"] = snapshot.comment().empty() ? YAML::Node() : YAML::Node(snapshot.comment()); - - instance_node[snapshot.snapshot_name()].push_back(snapshot_node); - info_node[item.instance_name()].push_back(instance_node); - } - - return info_node; -} - std::string generate_instances_list(const mp::InstancesList& instances_list) { YAML::Node list; @@ -258,19 +210,28 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) std::string mp::YamlFormatter::format(const InfoReply& reply) const { - YAML::Node info; + YAML::Node info_node; - if (reply.has_detailed_report()) - { - info = generate_instance_info_report(reply); - } - else + info_node["errors"].push_back(YAML::Null); + + for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) { - assert(reply.has_snapshot_overview() && "either one of the reports should be populated"); - info = generate_snapshot_overview_report(reply); + if (info.has_instance_info()) + { + info_node[info.name()].push_back(generate_instance_details(info)); + } + else + { + assert(info.has_snapshot_info() && "either one of instance or snapshot details should be populated"); + + YAML::Node snapshot_node; + snapshot_node[info.snapshot_info().fundamentals().snapshot_name()] = generate_snapshot_details(info); + + info_node[info.name()][0]["snapshots"].push_back(snapshot_node); + } } - return mpu::emit_yaml(info); + return mpu::emit_yaml(info_node); } std::string mp::YamlFormatter::format(const ListReply& reply) const diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 6b00afd4ac4..5df4b1786fc 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1269,15 +1269,6 @@ void populate_snapshot_fundamentals(std::shared_ptr snapshot timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); } -void populate_snapshot_overview(const std::string& instance_name, std::shared_ptr snapshot, - mp::SnapshotOverviewInfoItem* overview) -{ - auto fundamentals = overview->mutable_fundamentals(); - - overview->set_instance_name(instance_name); - populate_snapshot_fundamentals(snapshot, fundamentals); -} - void populate_mount_info(const std::unordered_map& mounts, mp::MountInfo* mount_info, bool& have_mounts) { @@ -1708,10 +1699,6 @@ try // clang-format on server}; InfoReply response; InstanceSnapshotsMap instance_snapshots_map; - - // Need to 'touch' a report in the response so formatters know what to do with an otherwise empty response - request->snapshot_overview() ? (void)response.mutable_snapshot_overview() - : (void)response.mutable_detailed_report(); bool have_mounts = false; bool deleted = false; @@ -1725,44 +1712,11 @@ try // clang-format on try { if (all_or_none) - populate_instance_info(vm, response.mutable_detailed_report()->add_details(), - request->no_runtime_information(), deleted, have_mounts); - - for (const auto& snapshot : pick) - populate_snapshot_info(vm, vm.get_snapshot(snapshot), response.mutable_detailed_report()->add_details(), + populate_instance_info(vm, response.add_details(), request->no_runtime_information(), deleted, have_mounts); - } - catch (const NoSuchSnapshot& e) - { - add_fmt_to(errors, e.what()); - } - - return grpc_status_for(errors); - }; - auto fetch_snapshot_overview = [&](VirtualMachine& vm) { - fmt::memory_buffer errors; - const auto& name = vm.vm_name; - - const auto& it = instance_snapshots_map.find(name); - const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; - - try - { - if (all_or_none) - { - for (const auto& snapshot : pick) - vm.get_snapshot(snapshot); // verify validity of any snapshot name requested separately - - for (const auto& snapshot : vm.view_snapshots()) - populate_snapshot_overview(name, snapshot, response.mutable_snapshot_overview()->add_overview()); - } - else - { - for (const auto& snapshot : pick) - populate_snapshot_overview(name, vm.get_snapshot(snapshot), - response.mutable_snapshot_overview()->add_overview()); - } + for (const auto& snapshot : pick) + populate_snapshot_info(vm, vm.get_snapshot(snapshot), response.add_details(), have_mounts); } catch (const NoSuchSnapshot& e) { @@ -1780,13 +1734,10 @@ try // clang-format on { instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); - auto cmd = request->snapshot_overview() ? std::function(fetch_snapshot_overview) - : std::function(fetch_detailed_report); - - if ((status = cmd_vms(instance_selection.operative_selection, cmd)).ok()) + if ((status = cmd_vms(instance_selection.operative_selection, fetch_detailed_report)).ok()) { deleted = true; - status = cmd_vms(instance_selection.deleted_selection, cmd); + status = cmd_vms(instance_selection.deleted_selection, fetch_detailed_report); } if (have_mounts && !MP_SETTINGS.get_as(mp::mounts_key)) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index afcd48f901f..d9d25b02e0f 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -167,7 +167,6 @@ message InstanceSnapshotPair { message InfoRequest { repeated InstanceSnapshotPair instances_snapshots = 1; - bool snapshot_overview = 2; int32 verbosity_level = 3; bool no_runtime_information = 4; } @@ -246,26 +245,8 @@ message DetailedInfoItem { } } -message SnapshotOverviewInfoItem { - string instance_name = 1; - SnapshotFundamentals fundamentals = 2; -} - -message DetailedInfoReport { - repeated DetailedInfoItem details = 1; -} - -message SnapshotOverviewInfoReport { - repeated SnapshotOverviewInfoItem overview = 1; -} - message InfoReply { - oneof info_contents - { - DetailedInfoReport detailed_report = 1; - SnapshotOverviewInfoReport snapshot_overview = 2; - } - + repeated DetailedInfoItem details = 1; string log_line = 3; } diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index abc84ec8654..e680850e03e 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -343,17 +343,6 @@ struct Client : public Test }; } - auto make_info_instance_details_request() - { - return [](Unused, grpc::ServerReaderWriter* server) { - mp::InfoReply reply; - reply.mutable_detailed_report(); - - server->Write(reply); - return grpc::Status{}; - }; - } - std::string negate_flag_string(const std::string& orig) { auto flag = QVariant{QString::fromStdString(orig)}.toBool(); @@ -419,7 +408,7 @@ auto make_info_function(const std::string& source_path = "", const std::string& if (request.instances_snapshots(0).instance_name() == "primary") { - auto vm_info = info_reply.mutable_detailed_report()->add_details(); + auto vm_info = info_reply.add_details(); vm_info->set_name("primary"); vm_info->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); @@ -1733,13 +1722,13 @@ TEST_F(Client, info_cmd_fails_no_args) TEST_F(Client, info_cmd_ok_with_one_arg) { - EXPECT_CALL(mock_daemon, info(_, _)).WillOnce(make_info_instance_details_request()); + EXPECT_CALL(mock_daemon, info(_, _)); EXPECT_THAT(send_command({"info", "foo"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, info_cmd_succeeds_with_multiple_args) { - EXPECT_CALL(mock_daemon, info(_, _)).WillOnce(make_info_instance_details_request()); + EXPECT_CALL(mock_daemon, info(_, _)); EXPECT_THAT(send_command({"info", "foo", "bar"}), Eq(mp::ReturnCode::Ok)); } @@ -1750,7 +1739,7 @@ TEST_F(Client, info_cmd_help_ok) TEST_F(Client, info_cmd_succeeds_with_all) { - EXPECT_CALL(mock_daemon, info(_, _)).WillOnce(make_info_instance_details_request()); + EXPECT_CALL(mock_daemon, info(_, _)); EXPECT_THAT(send_command({"info", "--all"}), Eq(mp::ReturnCode::Ok)); } @@ -1762,33 +1751,27 @@ TEST_F(Client, info_cmd_fails_with_names_and_all) TEST_F(Client, infoCmdDoesNotDefaultToNoRuntimeInformationAndSucceeds) { const auto info_matcher = Property(&mp::InfoRequest::no_runtime_information, IsFalse()); - mp::InfoReply reply; - reply.mutable_detailed_report(); EXPECT_CALL(mock_daemon, info) - .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok, reply))); + .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok))); EXPECT_THAT(send_command({"info", "name1", "name2"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, infoCmdSucceedsWithInstanceNamesAndNoRuntimeInformation) { const auto info_matcher = Property(&mp::InfoRequest::no_runtime_information, IsTrue()); - mp::InfoReply reply; - reply.mutable_detailed_report(); EXPECT_CALL(mock_daemon, info) - .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok, reply))); + .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok))); EXPECT_THAT(send_command({"info", "name3", "name4", "--no-runtime-information"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, infoCmdSucceedsWithAllAndNoRuntimeInformation) { const auto info_matcher = Property(&mp::InfoRequest::no_runtime_information, IsTrue()); - mp::InfoReply reply; - reply.mutable_detailed_report(); EXPECT_CALL(mock_daemon, info) - .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok, reply))); + .WillOnce(WithArg<1>(check_request_and_return(info_matcher, ok))); EXPECT_THAT(send_command({"info", "name5", "--no-runtime-information"}), Eq(mp::ReturnCode::Ok)); } diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index f58ee62fbcb..c0001658f6a 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -157,15 +157,8 @@ TEST_F(Daemon, receives_commands_and_calls_corresponding_slot) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); EXPECT_CALL(daemon, ssh_info(_, _, _)) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); - EXPECT_CALL(daemon, info(_, _, _)).WillOnce([](auto, auto server, auto status_promise) { - mp::InfoReply reply; - reply.mutable_detailed_report(); - - server->Write(reply); - status_promise->set_value(grpc::Status::OK); - - return grpc::Status{}; - }); + EXPECT_CALL(daemon, info(_, _, _)) + .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto server, auto status_promise) { mp::ListReply reply; reply.mutable_instances(); @@ -2211,14 +2204,9 @@ TEST_F(Daemon, info_all_returns_all_instances) Property(&mp::DetailedInfoItem::name, deleted_instance_name)); StrictMock> mock_server{}; - mp::InfoReply info_reply; - - EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&info_reply), Return(true))); + EXPECT_CALL(mock_server, Write(Property(&mp::InfoReply::details, names_matcher), _)).WillOnce(Return(true)); mp::Daemon daemon{config_builder.build()}; - call_daemon_slot(daemon, &mp::Daemon::info, mp::InfoRequest{}, mock_server); - - EXPECT_THAT(info_reply.detailed_report().details(), names_matcher); } } // namespace diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 0a5fa331faa..2e8a0e78d80 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -242,25 +242,11 @@ auto construct_multiple_lines_networks_reply() return networks_reply; } -auto construct_empty_info_reply() -{ - mp::InfoReply info_reply; - info_reply.mutable_detailed_report(); - return info_reply; -} - -auto construct_empty_snapshot_overview_reply() -{ - mp::InfoReply info_reply; - info_reply.mutable_snapshot_overview(); - return info_reply; -} - auto construct_single_instance_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto info_entry = info_reply.add_details(); info_entry->set_name("foo"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); @@ -313,7 +299,7 @@ auto construct_multiple_instances_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto info_entry = info_reply.add_details(); info_entry->set_name("bogus-instance"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); @@ -344,7 +330,7 @@ auto construct_multiple_instances_info_reply() info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); info_entry->mutable_instance_info()->set_num_snapshots(1); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); @@ -358,7 +344,7 @@ auto construct_single_snapshot_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto info_entry = info_reply.add_details(); auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("bogus-instance"); @@ -391,7 +377,7 @@ auto construct_multiple_snapshots_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto info_entry = info_reply.add_details(); auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("messier-87"); @@ -405,7 +391,7 @@ auto construct_multiple_snapshots_info_reply() timestamp.set_seconds(1554897599); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("bogus-instance"); @@ -435,7 +421,7 @@ auto construct_mixed_instance_and_snapshot_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto info_entry = info_reply.add_details(); auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("bogus-instance"); @@ -459,7 +445,7 @@ auto construct_mixed_instance_and_snapshot_info_reply() timestamp.set_nanos(21000000); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); @@ -473,7 +459,7 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() { mp::InfoReply info_reply; - auto info_entry = info_reply.mutable_detailed_report()->add_details(); + auto info_entry = info_reply.add_details(); info_entry->set_name("bogus-instance"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); info_entry->mutable_instance_info()->set_image_release("16.04 LTS"); @@ -504,7 +490,7 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() info_entry->mutable_instance_info()->add_ipv4("10.21.124.56"); info_entry->mutable_instance_info()->set_num_snapshots(2); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); auto fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("bogus-instance"); @@ -528,7 +514,7 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() timestamp.set_nanos(21000000); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("bogus-instance"); @@ -541,14 +527,14 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() timestamp.set_nanos(21000000); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); info_entry->set_name("bombastic"); info_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); info_entry->mutable_instance_info()->set_image_release("18.04 LTS"); info_entry->mutable_instance_info()->set_id("ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509"); info_entry->mutable_instance_info()->set_num_snapshots(3); - info_entry = info_reply.mutable_detailed_report()->add_details(); + info_entry = info_reply.add_details(); fundamentals = info_entry->mutable_snapshot_info()->mutable_fundamentals(); info_entry->set_name("messier-87"); @@ -565,102 +551,22 @@ auto construct_multiple_mixed_instances_and_snapshots_info_reply() return info_reply; } -auto construct_single_snapshot_overview_info_reply() -{ - mp::InfoReply info_reply; - - auto snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); - snapshot_entry->set_instance_name("foo"); - - auto fundamentals = snapshot_entry->mutable_fundamentals(); - fundamentals->set_snapshot_name("snapshot1"); - fundamentals->set_comment("This is a sample comment"); - - google::protobuf::Timestamp timestamp; - timestamp.set_seconds(time(nullptr)); - timestamp.set_nanos(0); - fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - - return info_reply; -} - -auto construct_multiple_snapshot_overview_info_reply() -{ - mp::InfoReply info_reply; - - auto snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); - auto fundamentals = snapshot_entry->mutable_fundamentals(); - google::protobuf::Timestamp timestamp; - - snapshot_entry->set_instance_name("prosperous-spadefish"); - fundamentals->set_snapshot_name("snapshot10"); - fundamentals->set_parent("snapshot2"); - timestamp.set_seconds(1672531200); - fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - - snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); - fundamentals = snapshot_entry->mutable_fundamentals(); - snapshot_entry->set_instance_name("hale-roller"); - fundamentals->set_snapshot_name("rolling"); - fundamentals->set_parent("pristine"); - fundamentals->set_comment("Loaded with stuff"); - timestamp.set_seconds(25425952800); - fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - - snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); - fundamentals = snapshot_entry->mutable_fundamentals(); - snapshot_entry->set_instance_name("hale-roller"); - fundamentals->set_snapshot_name("rocking"); - fundamentals->set_parent("pristine"); - fundamentals->set_comment("A very long comment that should be truncated by the table formatter"); - timestamp.set_seconds(2209234259); - fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - - snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); - fundamentals = snapshot_entry->mutable_fundamentals(); - snapshot_entry->set_instance_name("hale-roller"); - fundamentals->set_snapshot_name("pristine"); - fundamentals->set_comment("A first snapshot"); - timestamp.set_seconds(409298914); - fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - - snapshot_entry = info_reply.mutable_snapshot_overview()->add_overview(); - fundamentals = snapshot_entry->mutable_fundamentals(); - snapshot_entry->set_instance_name("prosperous-spadefish"); - fundamentals->set_snapshot_name("snapshot2"); - fundamentals->set_comment("Before restoring snap1\nContains a newline that\r\nshould be truncated"); - timestamp.set_seconds(1671840000); - fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - - return info_reply; -} - auto add_petenv_to_reply(mp::InfoReply& reply, bool csv_format, bool snapshots) { - if (reply.has_detailed_report()) + if ((csv_format && !snapshots) || !csv_format) { - if ((csv_format && !snapshots) || !csv_format) - { - auto entry = reply.mutable_detailed_report()->add_details(); - entry->set_name(petenv_name()); - entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); - entry->mutable_instance_info()->set_image_release("18.10"); - entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); - } - - if ((csv_format && snapshots) || !csv_format) - { - auto entry = reply.mutable_detailed_report()->add_details(); - entry->set_name(petenv_name()); - entry->mutable_snapshot_info()->mutable_fundamentals()->set_snapshot_name("snapshot1"); - } + auto entry = reply.add_details(); + entry->set_name(petenv_name()); + entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); + entry->mutable_instance_info()->set_image_release("18.10"); + entry->mutable_instance_info()->set_id("1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd"); } - else + + if ((csv_format && snapshots) || !csv_format) { - auto entry = reply.mutable_snapshot_overview()->add_overview(); - entry->set_instance_name(petenv_name()); - entry->mutable_fundamentals()->set_snapshot_name("snapshot1"); - entry->mutable_fundamentals()->set_comment("An exemplary comment"); + auto entry = reply.add_details(); + entry->set_name(petenv_name()); + entry->mutable_snapshot_info()->mutable_fundamentals()->set_snapshot_name("snapshot1"); } } @@ -895,8 +801,7 @@ const auto one_short_line_networks_reply = construct_one_short_line_networks_rep const auto one_long_line_networks_reply = construct_one_long_line_networks_reply(); const auto multiple_lines_networks_reply = construct_multiple_lines_networks_reply(); -const auto empty_info_reply = construct_empty_info_reply(); -const auto empty_snapshot_overview_reply = construct_empty_snapshot_overview_reply(); +const auto empty_info_reply = mp::InfoReply(); const auto single_instance_info_reply = construct_single_instance_info_reply(); const auto multiple_instances_info_reply = construct_multiple_instances_info_reply(); const auto single_snapshot_info_reply = construct_single_snapshot_info_reply(); @@ -904,8 +809,6 @@ const auto multiple_snapshots_info_reply = construct_multiple_snapshots_info_rep const auto mixed_instance_and_snapshot_info_reply = construct_mixed_instance_and_snapshot_info_reply(); const auto multiple_mixed_instances_and_snapshots_info_reply = construct_multiple_mixed_instances_and_snapshots_info_reply(); -const auto single_snapshot_overview_info_reply = construct_single_snapshot_overview_info_reply(); -const auto multiple_snapshot_overview_info_reply = construct_multiple_snapshot_overview_info_reply(); const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &empty_list_reply, "No instances found.\n", "table_list_empty"}, @@ -943,7 +846,6 @@ const std::vector orderable_list_info_formatter_outputs{ "table_list_multiple_snapshots"}, {&table_formatter, &empty_info_reply, "\n", "table_info_empty"}, - {&table_formatter, &empty_snapshot_overview_reply, "No snapshots found.\n", "table_snapshot_overview_empty"}, {&table_formatter, &single_instance_info_reply, "Name: foo\n" "State: Running\n" @@ -1115,18 +1017,6 @@ const std::vector orderable_list_info_formatter_outputs{ "Children: --\n" "Comment: Captured by EHT\n", "table_info_multiple_mixed_instances_and_snapshots"}, - {&table_formatter, &single_snapshot_overview_info_reply, - "Instance Snapshot Parent Comment\n" - "foo snapshot1 -- This is a sample comment\n", - "table_snapshot_overview_single"}, - {&table_formatter, &multiple_snapshot_overview_info_reply, - "Instance Snapshot Parent Comment\n" - "hale-roller pristine -- A first snapshot\n" - "hale-roller rocking pristine A very long comment that should be truncated by t…\n" - "hale-roller rolling pristine Loaded with stuff\n" - "prosperous-spadefish snapshot2 -- Before restoring snap1…\n" - "prosperous-spadefish snapshot10 snapshot2 --\n", - "table_snapshot_overview_multiple"}, {&csv_formatter, &empty_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n", "csv_list_empty"}, {&csv_formatter, &single_instance_list_reply, @@ -1157,8 +1047,6 @@ const std::vector orderable_list_info_formatter_outputs{ "csv_list_multiple_snapshots"}, {&csv_formatter, &empty_info_reply, "", "csv_info_empty"}, - {&csv_formatter, &empty_snapshot_overview_reply, "Instance,Snapshot,Parent,Comment\n", - "csv_snapshot_overview_empty"}, {&csv_formatter, &single_instance_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nfoo,Running,10.168.32.2,2001:67c:1562:8007::aac:423a,Ubuntu " @@ -1189,15 +1077,6 @@ const std::vector orderable_list_info_formatter_outputs{ "source,10.21.124.56,4,1\nbombastic,Stopped,,,," "ab5191cc172564e7cc0eafd397312a32598823e645279c820f0935393aead509,18.04 LTS,,,,,,,,,3\n", "csv_info_multiple_instances"}, - {&csv_formatter, &single_snapshot_overview_info_reply, - "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,\"This is a sample comment\"\n", "csv_snapshot_overview_single"}, - {&csv_formatter, &multiple_snapshot_overview_info_reply, - "Instance,Snapshot,Parent,Comment\nhale-roller,pristine,,\"A first " - "snapshot\"\nhale-roller,rocking,pristine,\"A very long comment that should be truncated by the table " - "formatter\"\nhale-roller,rolling,pristine,\"Loaded with stuff\"\nprosperous-spadefish,snapshot2,,\"Before " - "restoring snap1\nContains a newline that\r\nshould be " - "truncated\"\nprosperous-spadefish,snapshot10,snapshot2,\"\"\n", - "csv_snapshot_overview_multiple"}, {&yaml_formatter, &empty_list_reply, "\n", "yaml_list_empty"}, {&yaml_formatter, &single_instance_list_reply, @@ -1270,7 +1149,6 @@ const std::vector orderable_list_info_formatter_outputs{ "yaml_list_multiple_snapshots"}, {&yaml_formatter, &empty_info_reply, "errors:\n - ~\n", "yaml_info_empty"}, - {&yaml_formatter, &empty_snapshot_overview_reply, "errors:\n - ~\n", "yaml_snapshot_overview_empty"}, {&yaml_formatter, &single_instance_info_reply, "errors:\n" " - ~\n" @@ -1541,36 +1419,7 @@ const std::vector orderable_list_info_formatter_outputs{ " children:\n" " []\n" " comment: Captured by EHT\n", - "yaml_info_multiple_mixed_instances_and_snapshots"}, - {&yaml_formatter, &single_snapshot_overview_info_reply, - "errors:\n" - " - ~\n" - "foo:\n" - " - snapshot1:\n" - " - parent: ~\n" - " comment: This is a sample comment\n", - "yaml_snapshot_overview_single"}, - {&yaml_formatter, &multiple_snapshot_overview_info_reply, - "errors:\n" - " - ~\n" - "hale-roller:\n" - " - pristine:\n" - " - parent: ~\n" - " comment: A first snapshot\n" - " - rocking:\n" - " - parent: pristine\n" - " comment: A very long comment that should be truncated by the table formatter\n" - " - rolling:\n" - " - parent: pristine\n" - " comment: Loaded with stuff\n" - "prosperous-spadefish:\n" - " - snapshot2:\n" - " - parent: ~\n" - " comment: \"Before restoring snap1\\nContains a newline that\\r\\nshould be truncated\"\n" - " - snapshot10:\n" - " - parent: snapshot2\n" - " comment: ~\n", - "yaml_snapshot_overview_multiple"}}; + "yaml_info_multiple_mixed_instances_and_snapshots"}}; const std::vector non_orderable_list_info_formatter_outputs{ {&json_formatter, &empty_list_reply, @@ -1669,14 +1518,6 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_empty"}, - {&json_formatter, &empty_snapshot_overview_reply, - "{\n" - " \"errors\": [\n" - " ],\n" - " \"info\": {\n" - " }\n" - "}\n", - "json_snapshot_overview_empty"}, {&json_formatter, &single_instance_info_reply, "{\n" " \"errors\": [\n" @@ -2050,53 +1891,7 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" " }\n" "}\n", - "json_info_multiple_mixed_instances_and_snapshots"}, - {&json_formatter, &single_snapshot_overview_info_reply, - "{\n" - " \"errors\": [\n" - " ],\n" - " \"info\": {\n" - " \"foo\": {\n" - " \"snapshot1\": {\n" - " \"comment\": \"This is a sample comment\",\n" - " \"parent\": \"\"\n" - " }\n" - " }\n" - " }\n" - "}\n", - "json_snapshot_overview_single"}, - {&json_formatter, &multiple_snapshot_overview_info_reply, - "{\n" - " \"errors\": [\n" - " ],\n" - " \"info\": {\n" - " \"hale-roller\": {\n" - " \"pristine\": {\n" - " \"comment\": \"A first snapshot\",\n" - " \"parent\": \"\"\n" - " },\n" - " \"rocking\": {\n" - " \"comment\": \"A very long comment that should be truncated by the table formatter\",\n" - " \"parent\": \"pristine\"\n" - " },\n" - " \"rolling\": {\n" - " \"comment\": \"Loaded with stuff\",\n" - " \"parent\": \"pristine\"\n" - " }\n" - " },\n" - " \"prosperous-spadefish\": {\n" - " \"snapshot10\": {\n" - " \"comment\": \"\",\n" - " \"parent\": \"snapshot2\"\n" - " },\n" - " \"snapshot2\": {\n" - " \"comment\": \"Before restoring snap1\\nContains a newline that\\r\\nshould be truncated\",\n" - " \"parent\": \"\"\n" - " }\n" - " }\n" - " }\n" - "}\n", - "json_snapshot_overview_multiple"}}; + "json_info_multiple_mixed_instances_and_snapshots"}}; const std::vector non_orderable_networks_formatter_outputs{ {&table_formatter, &empty_networks_reply, "No network interfaces found.\n", "table_networks_empty"}, @@ -2606,12 +2401,6 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) else if (auto input = dynamic_cast(reply)) { mp::InfoReply reply_copy; - - if (input->has_detailed_report()) - reply_copy.mutable_detailed_report(); - else - reply_copy.mutable_snapshot_overview(); - if (prepend) { add_petenv_to_reply(reply_copy, dynamic_cast(formatter), @@ -2627,23 +2416,13 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) output = formatter->format(reply_copy); if (dynamic_cast(formatter)) - { - if (input->has_detailed_report()) - regex = fmt::format("(Name:[[:space:]]+{0}.+)" - "(Snapshot:[[:print:]]*\nInstance:[[:space:]]+{0}.+)", - petenv_name()); - else - regex = fmt::format("Instance[[:print:]]*\n{}.+", petenv_name()); - } + regex = fmt::format("(Name:[[:space:]]+{0}.+)" + "(Snapshot:[[:print:]]*\nInstance:[[:space:]]+{0}.+)", + petenv_name()); else if (dynamic_cast(formatter)) - { - if (input->has_detailed_report()) - regex = fmt::format("(Name[[:print:]]*\n{0},.*)|" - "(Snapshot[[:print:]]*\n[[:print:]]*,{0},.*)", - petenv_name()); - else - regex = fmt::format("Instance[[:print:]]*\n{},.*", petenv_name()); - } + regex = fmt::format("(Name[[:print:]]*\n{0},.*)|" + "(Snapshot[[:print:]]*\n[[:print:]]*,{0},.*)", + petenv_name()); else if (dynamic_cast(formatter)) regex = fmt::format("(errors:[[:space:]]+-[[:space:]]+~[[:space:]]+)?{}:.*", petenv_name()); else From bfd7464bb3e5c91e39d88d0540096e7ac7b00635 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 26 Sep 2023 09:22:38 -0500 Subject: [PATCH 426/627] [proto] rename message fields --- src/client/cli/formatter/csv_formatter.cpp | 16 ++++---- src/client/cli/formatter/json_formatter.cpp | 16 ++++---- src/client/cli/formatter/table_formatter.cpp | 20 +++++----- src/client/cli/formatter/yaml_formatter.cpp | 16 ++++---- src/client/gui/gui_cmd.cpp | 11 ++--- src/daemon/daemon.cpp | 6 +-- src/rpc/multipass.proto | 8 ++-- tests/test_cli_client.cpp | 6 +-- tests/test_daemon.cpp | 8 ++-- tests/test_output_formatter.cpp | 42 ++++++++++---------- tests/unix/test_daemon_rpc.cpp | 6 +-- 11 files changed, 78 insertions(+), 77 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 08f0bee551e..49d15b48111 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -118,13 +118,13 @@ std::string generate_instance_details(const mp::InfoReply reply) return fmt::to_string(buf); } -std::string generate_instances_list(const mp::InstancesList& instances_list) +std::string generate_instances_list(const mp::InstancesList& instance_list) { fmt::memory_buffer buf; fmt::format_to(std::back_inserter(buf), "Name,State,IPv4,IPv6,Release,AllIPv4\n"); - for (const auto& instance : mp::format::sorted(instances_list.info())) + for (const auto& instance : mp::format::sorted(instance_list.instances())) { fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},\"{}\"\n", instance.name(), mp::format::status_string_for(instance.instance_status()), @@ -137,13 +137,13 @@ std::string generate_instances_list(const mp::InstancesList& instances_list) return fmt::to_string(buf); } -std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) { fmt::memory_buffer buf; fmt::format_to(std::back_inserter(buf), "Instance,Snapshot,Parent,Comment\n"); - for (const auto& item : mp::format::sorted(snapshots_list.info())) + for (const auto& item : mp::format::sorted(snapshot_list.snapshots())) { const auto& snapshot = item.fundamentals(); fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.name(), snapshot.snapshot_name(), @@ -173,14 +173,14 @@ std::string mp::CSVFormatter::format(const ListReply& reply) const { std::string output; - if (reply.has_instances()) + if (reply.has_instance_list()) { - output = generate_instances_list(reply.instances()); + output = generate_instances_list(reply.instance_list()); } else { - assert(reply.has_snapshots() && "either one of instances or snapshots should be populated"); - output = generate_snapshots_list(reply.snapshots()); + assert(reply.has_snapshot_list() && "either one of instances or snapshots should be populated"); + output = generate_snapshots_list(reply.snapshot_list()); } return output; diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index e9504137042..7498fde65da 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -174,12 +174,12 @@ QJsonObject generate_instance_details(const mp::DetailedInfoItem& item) return instance_info; } -std::string generate_instances_list(const mp::InstancesList& instances_list) +std::string generate_instances_list(const mp::InstancesList& instance_list) { QJsonObject list_json; QJsonArray instances; - for (const auto& instance : instances_list.info()) + for (const auto& instance : instance_list.instances()) { QJsonObject instance_obj; instance_obj.insert("name", QString::fromStdString(instance.name())); @@ -203,13 +203,13 @@ std::string generate_instances_list(const mp::InstancesList& instances_list) return mp::json_to_string(list_json); } -std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) { QJsonObject info_json; QJsonObject info_obj; info_json.insert("errors", QJsonArray()); - for (const auto& item : snapshots_list.info()) + for (const auto& item : snapshot_list.snapshots()) { const auto& snapshot = item.fundamentals(); QJsonObject snapshot_obj; @@ -308,14 +308,14 @@ std::string mp::JsonFormatter::format(const ListReply& reply) const { std::string output; - if (reply.has_instances()) + if (reply.has_instance_list()) { - output = generate_instances_list(reply.instances()); + output = generate_instances_list(reply.instance_list()); } else { - assert(reply.has_snapshots() && "either one of the reports should be populated"); - output = generate_snapshots_list(reply.snapshots()); + assert(reply.has_snapshot_list() && "either one of the reports should be populated"); + output = generate_snapshots_list(reply.snapshot_list()); } return output; diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 8e58a87f3f7..8b78ed0c0fe 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -192,11 +192,11 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) return fmt::to_string(buf); } -std::string generate_instances_list(const mp::InstancesList& instances_list) +std::string generate_instances_list(const mp::InstancesList& instance_list) { fmt::memory_buffer buf; - auto instances = instances_list.info(); + auto instances = instance_list.instances(); if (instances.empty()) return "No instances found.\n"; @@ -212,7 +212,7 @@ std::string generate_instances_list(const mp::InstancesList& instances_list) fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, "State", state_column_width, "IPv4", ip_column_width, "Image"); - for (const auto& instance : mp::format::sorted(instances_list.info())) + for (const auto& instance : mp::format::sorted(instance_list.instances())) { int ipv4_size = instance.ipv4_size(); @@ -232,11 +232,11 @@ std::string generate_instances_list(const mp::InstancesList& instances_list) return fmt::to_string(buf); } -std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) { fmt::memory_buffer buf; - auto snapshots = snapshots_list.info(); + auto snapshots = snapshot_list.snapshots(); if (snapshots.empty()) return "No snapshots found.\n"; @@ -259,7 +259,7 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, snapshot_col_header, snapshot_column_width, parent_col_header, parent_column_width, comment_col_header); - for (const auto& snapshot : mp::format::sorted(snapshots_list.info())) + for (const auto& snapshot : mp::format::sorted(snapshot_list.snapshots())) { size_t max_comment_column_width = 50; std::smatch match; @@ -313,14 +313,14 @@ std::string mp::TableFormatter::format(const ListReply& reply) const { std::string output; - if (reply.has_instances()) + if (reply.has_instance_list()) { - output = generate_instances_list(reply.instances()); + output = generate_instances_list(reply.instance_list()); } else { - assert(reply.has_snapshots() && "either one of instances or snapshots should be populated"); - output = generate_snapshots_list(reply.snapshots()); + assert(reply.has_snapshot_list() && "either one of instances or snapshots should be populated"); + output = generate_snapshots_list(reply.snapshot_list()); } return output; diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 4e1c051532d..ab5e6bebe2e 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -165,11 +165,11 @@ YAML::Node generate_instance_details(const mp::DetailedInfoItem& item) return instance_node; } -std::string generate_instances_list(const mp::InstancesList& instances_list) +std::string generate_instances_list(const mp::InstancesList& instance_list) { YAML::Node list; - for (const auto& instance : mp::format::sorted(instances_list.info())) + for (const auto& instance : mp::format::sorted(instance_list.instances())) { YAML::Node instance_node; instance_node["state"] = mp::format::status_string_for(instance.instance_status()); @@ -187,11 +187,11 @@ std::string generate_instances_list(const mp::InstancesList& instances_list) return mpu::emit_yaml(list); } -std::string generate_snapshots_list(const mp::SnapshotsList& snapshots_list) +std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) { YAML::Node info_node; - for (const auto& item : mp::format::sorted(snapshots_list.info())) + for (const auto& item : mp::format::sorted(snapshot_list.snapshots())) { const auto& snapshot = item.fundamentals(); YAML::Node instance_node; @@ -238,14 +238,14 @@ std::string mp::YamlFormatter::format(const ListReply& reply) const { std::string output; - if (reply.has_instances()) + if (reply.has_instance_list()) { - output = generate_instances_list(reply.instances()); + output = generate_instances_list(reply.instance_list()); } else { - assert(reply.has_snapshots() && "eitherr one of instances or snapshots should be populated"); - output = generate_snapshots_list(reply.snapshots()); + assert(reply.has_snapshot_list() && "eitherr one of instances or snapshots should be populated"); + output = generate_snapshots_list(reply.snapshot_list()); } return output; diff --git a/src/client/gui/gui_cmd.cpp b/src/client/gui/gui_cmd.cpp index b6558059d37..81c46eb69db 100644 --- a/src/client/gui/gui_cmd.cpp +++ b/src/client/gui/gui_cmd.cpp @@ -191,14 +191,15 @@ void cmd::GuiCmd::update_menu() auto reply = list_future.result(); - handle_petenv_instance(reply.instances().info()); + handle_petenv_instance(reply.instance_list().instances()); for (auto it = instances_entries.cbegin(); it != instances_entries.cend(); ++it) { - auto instance = std::find_if(reply.instances().info().cbegin(), reply.instances().info().cend(), - [it](const ListVMInstance& instance) { return it->first == instance.name(); }); + auto instance = + std::find_if(reply.instance_list().instances().cbegin(), reply.instance_list().instances().cend(), + [it](const ListVMInstance& instance) { return it->first == instance.name(); }); - if (instance == reply.instances().info().cend()) + if (instance == reply.instance_list().instances().cend()) { instances_to_remove.push_back(it->first); } @@ -209,7 +210,7 @@ void cmd::GuiCmd::update_menu() instances_entries.erase(instance); } - for (const auto& instance : reply.instances().info()) + for (const auto& instance : reply.instance_list().instances()) { auto name = instance.name(); auto state = instance.instance_status(); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 5df4b1786fc..8619b4d39c2 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1763,13 +1763,13 @@ try // clang-format on config->update_prompt->populate_if_time_to_show(response.mutable_update_info()); // Need to 'touch' a report in the response so formatters know what to do with an otherwise empty response - request->snapshots() ? (void)response.mutable_snapshots() : (void)response.mutable_instances(); + request->snapshots() ? (void)response.mutable_snapshot_list() : (void)response.mutable_instance_list(); bool deleted = false; auto fetch_instance = [&](VirtualMachine& vm) { const auto& name = vm.vm_name; auto present_state = vm.current_state(); - auto entry = response.mutable_instances()->add_info(); + auto entry = response.mutable_instance_list()->add_instances(); entry->set_name(name); if (deleted) entry->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); @@ -1821,7 +1821,7 @@ try // clang-format on { for (const auto& snapshot : vm.view_snapshots()) { - auto entry = response.mutable_snapshots()->add_info(); + auto entry = response.mutable_snapshot_list()->add_snapshots(); auto fundamentals = entry->mutable_fundamentals(); entry->set_name(name); diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index d9d25b02e0f..f21328314b5 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -270,18 +270,18 @@ message ListVMSnapshot { } message InstancesList { - repeated ListVMInstance info = 1; + repeated ListVMInstance instances = 1; } message SnapshotsList { - repeated ListVMSnapshot info = 1; + repeated ListVMSnapshot snapshots = 1; } message ListReply { oneof list_contents { - InstancesList instances = 1; - SnapshotsList snapshots = 2; + InstancesList instance_list = 1; + SnapshotsList snapshot_list = 2; } string log_line = 3; diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index e680850e03e..48f3b243be4 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -333,7 +333,7 @@ struct Client : public Test for (mp::InstanceStatus_Status status : statuses) { - auto list_entry = list_reply.mutable_instances()->add_info(); + auto list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->mutable_instance_status()->set_status(status); } @@ -1780,7 +1780,7 @@ TEST_F(Client, list_cmd_ok_no_args) { const auto list_matcher = Property(&mp::ListRequest::request_ipv4, IsTrue()); mp::ListReply reply; - reply.mutable_instances(); + reply.mutable_instance_list(); EXPECT_CALL(mock_daemon, list) .WillOnce(WithArg<1>(check_request_and_return(list_matcher, ok, reply))); @@ -1801,7 +1801,7 @@ TEST_F(Client, list_cmd_no_ipv4_ok) { const auto list_matcher = Property(&mp::ListRequest::request_ipv4, IsFalse()); mp::ListReply reply; - reply.mutable_instances(); + reply.mutable_instance_list(); EXPECT_CALL(mock_daemon, list) .WillOnce(WithArg<1>(check_request_and_return(list_matcher, ok, reply))); diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index c0001658f6a..c276b622b7b 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -161,7 +161,7 @@ TEST_F(Daemon, receives_commands_and_calls_corresponding_slot) .WillOnce(Invoke(&daemon, &mpt::MockDaemon::set_promise_value)); EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto server, auto status_promise) { mp::ListReply reply; - reply.mutable_instances(); + reply.mutable_instance_list(); server->Write(reply); status_promise->set_value(grpc::Status::OK); @@ -1423,7 +1423,7 @@ TEST_F(Daemon, reads_mac_addresses_from_json) EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&list_reply), Return(true))); EXPECT_TRUE(call_daemon_slot(daemon, &mp::Daemon::list, mp::ListRequest{}, mock_server).ok()); - EXPECT_THAT(list_reply.instances().info(), instance_matcher); + EXPECT_THAT(list_reply.instance_list().instances(), instance_matcher); } // Removing the JSON is possible now because data was already read. This step is not necessary, but doing it we @@ -1490,7 +1490,7 @@ TEST_F(Daemon, writesAndReadsMountsInJson) EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&list_reply), Return(true))); EXPECT_TRUE(call_daemon_slot(daemon, &mp::Daemon::list, mp::ListRequest{}, mock_server).ok()); - EXPECT_THAT(list_reply.instances().info(), instance_matcher); + EXPECT_THAT(list_reply.instance_list().instances(), instance_matcher); } QFile::remove(filename); // Remove the JSON. @@ -1729,7 +1729,7 @@ TEST_F(Daemon, ctor_drops_removed_instances) EXPECT_CALL(mock_server, Write(_, _)).WillOnce(DoAll(SaveArg<0>(&list_reply), Return(true))); EXPECT_TRUE(call_daemon_slot(daemon, &mp::Daemon::list, mp::ListRequest{}, mock_server).ok()); - EXPECT_THAT(list_reply.instances().info(), stayed_matcher); + EXPECT_THAT(list_reply.instance_list().instances(), stayed_matcher); auto updated_json = mpt::load(filename); EXPECT_THAT(updated_json.toStdString(), AllOf(HasSubstr(stayed), Not(HasSubstr(gone)))); diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 2e8a0e78d80..0c359607d41 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -43,14 +43,14 @@ auto petenv_name() auto construct_empty_list_reply() { mp::ListReply list_reply; - list_reply.mutable_instances(); + list_reply.mutable_instance_list(); return list_reply; } auto construct_empty_list_snapshot_reply() { mp::ListReply list_reply; - list_reply.mutable_snapshots(); + list_reply.mutable_snapshot_list(); return list_reply; } @@ -58,7 +58,7 @@ auto construct_single_instance_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.mutable_instances()->add_info(); + auto list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("foo"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); list_entry->set_current_release("16.04 LTS"); @@ -74,13 +74,13 @@ auto construct_multiple_instances_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.mutable_instances()->add_info(); + auto list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("bogus-instance"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); list_entry->set_current_release("16.04 LTS"); list_entry->add_ipv4("10.21.124.56"); - list_entry = list_reply.mutable_instances()->add_info(); + list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("bombastic"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); list_entry->set_current_release("18.04 LTS"); @@ -92,22 +92,22 @@ auto construct_unsorted_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.mutable_instances()->add_info(); + auto list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("trusty-190611-1542"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::RUNNING); list_entry->set_current_release("N/A"); - list_entry = list_reply.mutable_instances()->add_info(); + list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("trusty-190611-1535"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::STOPPED); list_entry->set_current_release("N/A"); - list_entry = list_reply.mutable_instances()->add_info(); + list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("trusty-190611-1539"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::SUSPENDED); list_entry->set_current_release(""); - list_entry = list_reply.mutable_instances()->add_info(); + list_entry = list_reply.mutable_instance_list()->add_instances(); list_entry->set_name("trusty-190611-1529"); list_entry->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); list_entry->set_current_release(""); @@ -119,7 +119,7 @@ auto construct_single_snapshot_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.mutable_snapshots()->add_info(); + auto list_entry = list_reply.mutable_snapshot_list()->add_snapshots(); list_entry->set_name("foo"); auto fundamentals = list_entry->mutable_fundamentals(); @@ -138,7 +138,7 @@ auto construct_multiple_snapshots_list_reply() { mp::ListReply list_reply; - auto list_entry = list_reply.mutable_snapshots()->add_info(); + auto list_entry = list_reply.mutable_snapshot_list()->add_snapshots(); auto fundamentals = list_entry->mutable_fundamentals(); google::protobuf::Timestamp timestamp; @@ -148,7 +148,7 @@ auto construct_multiple_snapshots_list_reply() timestamp.set_seconds(1672531200); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - list_entry = list_reply.mutable_snapshots()->add_info(); + list_entry = list_reply.mutable_snapshot_list()->add_snapshots(); fundamentals = list_entry->mutable_fundamentals(); list_entry->set_name("hale-roller"); fundamentals->set_snapshot_name("rolling"); @@ -157,7 +157,7 @@ auto construct_multiple_snapshots_list_reply() timestamp.set_seconds(25425952800); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - list_entry = list_reply.mutable_snapshots()->add_info(); + list_entry = list_reply.mutable_snapshot_list()->add_snapshots(); fundamentals = list_entry->mutable_fundamentals(); list_entry->set_name("hale-roller"); fundamentals->set_snapshot_name("rocking"); @@ -166,7 +166,7 @@ auto construct_multiple_snapshots_list_reply() timestamp.set_seconds(2209234259); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - list_entry = list_reply.mutable_snapshots()->add_info(); + list_entry = list_reply.mutable_snapshot_list()->add_snapshots(); fundamentals = list_entry->mutable_fundamentals(); list_entry->set_name("hale-roller"); fundamentals->set_snapshot_name("pristine"); @@ -174,7 +174,7 @@ auto construct_multiple_snapshots_list_reply() timestamp.set_seconds(409298914); fundamentals->mutable_creation_timestamp()->CopyFrom(timestamp); - list_entry = list_reply.mutable_snapshots()->add_info(); + list_entry = list_reply.mutable_snapshot_list()->add_snapshots(); fundamentals = list_entry->mutable_fundamentals(); list_entry->set_name("prosperous-spadefish"); fundamentals->set_snapshot_name("snapshot2"); @@ -187,16 +187,16 @@ auto construct_multiple_snapshots_list_reply() auto add_petenv_to_reply(mp::ListReply& reply) { - if (reply.has_instances()) + if (reply.has_instance_list()) { - auto instance = reply.mutable_instances()->add_info(); + auto instance = reply.mutable_instance_list()->add_instances(); instance->set_name(petenv_name()); instance->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); instance->set_current_release("Not Available"); } else { - auto snapshot = reply.mutable_snapshots()->add_info(); + auto snapshot = reply.mutable_snapshot_list()->add_snapshots(); snapshot->set_name(petenv_name()); snapshot->mutable_fundamentals()->set_snapshot_name("snapshot1"); snapshot->mutable_fundamentals()->set_comment("An exemplary comment"); @@ -2372,10 +2372,10 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) { mp::ListReply reply_copy; - if (input->has_instances()) - reply_copy.mutable_instances(); + if (input->has_instance_list()) + reply_copy.mutable_instance_list(); else - reply_copy.mutable_snapshots(); + reply_copy.mutable_snapshot_list(); if (prepend) { diff --git a/tests/unix/test_daemon_rpc.cpp b/tests/unix/test_daemon_rpc.cpp index ec922bed027..013868efab7 100644 --- a/tests/unix/test_daemon_rpc.cpp +++ b/tests/unix/test_daemon_rpc.cpp @@ -204,7 +204,7 @@ TEST_F(TestDaemonRpc, listCertExistsCompletesSuccesfully) mpt::MockDaemon daemon{make_secure_server()}; EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { mp::ListReply reply; - reply.mutable_instances(); + reply.mutable_instance_list(); server->Write(reply); status_promise->set_value(grpc::Status::OK); }); @@ -223,7 +223,7 @@ TEST_F(TestDaemonRpc, listNoCertsExistWillVerifyAndComplete) mpt::MockDaemon daemon{make_secure_server()}; EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { mp::ListReply reply; - reply.mutable_instances(); + reply.mutable_instance_list(); server->Write(reply); status_promise->set_value(grpc::Status::OK); }); @@ -310,7 +310,7 @@ TEST_F(TestDaemonRpc, listSettingServerPermissionsFailLogsErrorAndExits) EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { mp::ListReply reply; - reply.mutable_instances(); + reply.mutable_instance_list(); server->Write(reply); status_promise->set_value(grpc::Status::OK); }); From 87ea3cd562c28938b4325d8cd7120de629698aff Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 26 Sep 2023 10:35:41 -0500 Subject: [PATCH 427/627] [formatters] add const references to avoid copying --- src/client/cli/formatter/csv_formatter.cpp | 2 +- src/client/cli/formatter/json_formatter.cpp | 4 ++-- src/client/cli/formatter/table_formatter.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 49d15b48111..a44878653c8 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -49,7 +49,7 @@ std::string format_images(const google::protobuf::RepeatedPtrField Date: Tue, 26 Sep 2023 11:57:13 -0500 Subject: [PATCH 428/627] [cli] add logic to reject ipv4 and snapshots options together --- src/client/cli/cmd/list.cpp | 6 ++++++ tests/test_cli_client.cpp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/client/cli/cmd/list.cpp b/src/client/cli/cmd/list.cpp index d5ecf2e7892..8c237a68b48 100644 --- a/src/client/cli/cmd/list.cpp +++ b/src/client/cli/cmd/list.cpp @@ -91,6 +91,12 @@ mp::ParseCode cmd::List::parse_args(mp::ArgParser* parser) return ParseCode::CommandLineError; } + if (parser->isSet(snapshotsOption) && parser->isSet(noIpv4Option)) + { + cerr << "IP addresses are not applicable in conjunction with listing snapshots\n"; + return ParseCode::CommandLineError; + } + request.set_snapshots(parser->isSet(snapshotsOption)); request.set_request_ipv4(!parser->isSet(noIpv4Option)); diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 48f3b243be4..6fbf77b54c7 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -1808,6 +1808,11 @@ TEST_F(Client, list_cmd_no_ipv4_ok) EXPECT_THAT(send_command({"list", "--no-ipv4"}), Eq(mp::ReturnCode::Ok)); } +TEST_F(Client, listCmdFailsWithIpv4AndSnapshots) +{ + EXPECT_THAT(send_command({"list", "--no-ipv4", "--snapshots"}), Eq(mp::ReturnCode::CommandLineError)); +} + // mount cli tests // Note: mpt::test_data_path() returns an absolute path TEST_F(Client, mount_cmd_good_absolute_source_path) From 36045b7ae13a8eded3c1dabb368e0301fb10b2c1 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 27 Sep 2023 13:36:39 -0500 Subject: [PATCH 429/627] [format utils] remove duplicate code --- include/multipass/cli/format_utils.h | 78 +++++++------------- src/client/cli/formatter/csv_formatter.cpp | 4 +- src/client/cli/formatter/table_formatter.cpp | 2 +- src/client/cli/formatter/yaml_formatter.cpp | 2 +- 4 files changed, 30 insertions(+), 56 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index f7907077304..9b2b8627211 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -40,11 +40,8 @@ std::string status_string_for(const InstanceStatus& status); std::string image_string_for(const multipass::FindReply_AliasInfo& alias); Formatter* formatter_for(const std::string& format); -template -Instances sorted(const Instances& instances); - -template -Details sort_instances_and_snapshots(const Details& details); +template +Container sorted(const Container& items); void filter_aliases(google::protobuf::RepeatedPtrField& aliases); @@ -63,71 +60,48 @@ static constexpr auto column_width = [](const auto begin, const auto end, const } // namespace multipass template -Container multipass::format::sorted(const Container& instances) +Container multipass::format::sorted(const Container& items) { - if (instances.empty()) - return instances; + if (items.empty()) + return items; - auto ret = instances; + auto ret = items; const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString(); std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) { using T = std::decay_t; using google::protobuf::util::TimeUtil; + // Put instances first when sorting info reply + if constexpr (std::is_same_v) + { + if (a.has_instance_info() && b.has_snapshot_info()) + return true; + else if (a.has_snapshot_info() && b.has_instance_info()) + return false; + } + + // Put petenv related entries first if (a.name() == petenv_name && b.name() != petenv_name) return true; else if (b.name() == petenv_name && a.name() != petenv_name) return false; else { - if constexpr (std::is_same_v) + // Sort by timestamp when names are the same for snapshots + if constexpr (std::is_same_v) { - if (a.name() < b.name()) - return true; - else if (a.name() > b.name()) - return false; - - return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < - TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); + if (a.has_snapshot_info() && a.name() == b.name()) + return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp()); } - else + else if constexpr (std::is_same_v) { - return a.name() < b.name(); + if (a.name() == b.name()) + return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) < + TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp()); } - } - }); - - return ret; -} - -template -Details multipass::format::sort_instances_and_snapshots(const Details& details) -{ - using google::protobuf::util::TimeUtil; - if (details.empty()) - return details; - - auto ret = details; - const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString(); - std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) { - if (a.has_instance_info() && b.has_snapshot_info()) - return true; - else if (a.has_snapshot_info() && b.has_instance_info()) - return false; - - if (a.name() == petenv_name && b.name() != petenv_name) - return true; - else if (a.name() != petenv_name && b.name() == petenv_name) - return false; - - if (a.has_instance_info()) - return a.name() < b.name(); - else - { - if (a.name() == b.name()) - return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) < - TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp()); + // Lastly, sort by name return a.name() < b.name(); } }); diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index a44878653c8..87d67c1f2d0 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -69,7 +69,7 @@ std::string generate_snapshot_details(const mp::InfoReply reply) fmt::format_to(std::back_inserter(buf), "Snapshot,Instance,CPU(s),Disk space,Memory size,Mounts,Created,Parent,Children,Comment\n"); - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) + for (const auto& info : mp::format::sorted(reply.details())) { const auto& fundamentals = info.snapshot_info().fundamentals(); @@ -95,7 +95,7 @@ std::string generate_instance_details(const mp::InfoReply reply) "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory usage,Memory " "total,Mounts,AllIPv4,CPU(s),Snapshots\n"); - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) + for (const auto& info : mp::format::sorted(reply.details())) { assert(info.has_instance_info() && "outputting instance and snapshot details together is not supported in csv format"); diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 7576622db86..fd9ad781f61 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -285,7 +285,7 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const { fmt::memory_buffer buf; - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) + for (const auto& info : mp::format::sorted(reply.details())) { if (info.has_instance_info()) { diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index ab5e6bebe2e..4cd7ac0e1e7 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -214,7 +214,7 @@ std::string mp::YamlFormatter::format(const InfoReply& reply) const info_node["errors"].push_back(YAML::Null); - for (const auto& info : mp::format::sort_instances_and_snapshots(reply.details())) + for (const auto& info : mp::format::sorted(reply.details())) { if (info.has_instance_info()) { From f1ab5b20f0c521e74fd4eecb9340d88c2754db2a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 16 Oct 2023 14:36:49 +0100 Subject: [PATCH 430/627] [format] Adapt to latest clang-format config --- include/multipass/cli/format_utils.h | 19 +- include/multipass/file_ops.h | 3 +- include/multipass/mount_handler.h | 4 +- .../sshfs_mount/sshfs_mount_handler.h | 4 +- include/multipass/utils.h | 3 +- include/multipass/virtual_machine.h | 3 +- include/multipass/vm_image_vault.h | 10 +- src/client/cli/cmd/delete.cpp | 11 +- src/client/cli/cmd/delete.h | 4 +- src/client/cli/cmd/info.cpp | 6 +- .../cli/cmd/remote_settings_handler.cpp | 5 +- src/client/cli/cmd/snapshot.cpp | 10 +- src/client/cli/formatter/csv_formatter.cpp | 46 ++- src/client/cli/formatter/json_formatter.cpp | 5 +- src/client/cli/formatter/table_formatter.cpp | 169 ++++++++--- src/client/cli/formatter/yaml_formatter.cpp | 6 +- src/client/gui/gui_cmd.cpp | 6 +- src/daemon/daemon.cpp | 84 ++++-- src/daemon/daemon.h | 11 +- src/daemon/daemon_rpc.cpp | 6 +- src/daemon/daemon_rpc.h | 6 +- src/daemon/default_vm_image_vault.cpp | 31 +- src/daemon/default_vm_image_vault.h | 18 +- .../libvirt/libvirt_virtual_machine.cpp | 10 +- .../libvirt/libvirt_virtual_machine.h | 12 +- .../libvirt_virtual_machine_factory.cpp | 5 +- .../backends/lxd/lxd_mount_handler.cpp | 6 +- src/platform/backends/lxd/lxd_mount_handler.h | 7 +- .../backends/lxd/lxd_virtual_machine.cpp | 16 +- .../backends/lxd/lxd_virtual_machine.h | 14 +- .../lxd/lxd_virtual_machine_factory.cpp | 9 +- .../backends/lxd/lxd_vm_image_vault.cpp | 9 +- .../backends/lxd/lxd_vm_image_vault.h | 8 +- .../backends/qemu/qemu_mount_handler.cpp | 6 +- .../backends/qemu/qemu_mount_handler.h | 4 +- src/platform/backends/qemu/qemu_snapshot.cpp | 22 +- src/platform/backends/qemu/qemu_snapshot.h | 7 +- .../backends/qemu/qemu_virtual_machine.cpp | 16 +- .../backends/qemu/qemu_virtual_machine.h | 10 +- .../qemu/qemu_virtual_machine_factory.cpp | 4 +- .../backends/shared/base_snapshot.cpp | 20 +- src/platform/backends/shared/base_snapshot.h | 16 +- .../backends/shared/base_virtual_machine.cpp | 55 +++- .../backends/shared/base_virtual_machine.h | 19 +- .../shared/qemu_img_utils/qemu_img_utils.cpp | 13 +- src/sshfs_mount/sshfs_mount_handler.cpp | 9 +- src/utils/file_ops.cpp | 4 +- src/utils/utils.cpp | 3 +- tests/blueprint_test_lambdas.cpp | 20 +- tests/blueprint_test_lambdas.h | 9 +- tests/lxd/test_lxd_backend.cpp | 243 ++++++++++----- tests/lxd/test_lxd_image_vault.cpp | 178 ++++++++--- tests/lxd/test_lxd_mount_handler.cpp | 18 +- tests/mock_client_rpc.h | 28 +- tests/mock_virtual_machine.h | 12 +- tests/mock_vm_image_vault.h | 12 +- tests/stub_virtual_machine.h | 3 +- tests/stub_vm_image_vault.h | 8 +- tests/test_alias_dict.cpp | 6 +- tests/test_base_virtual_machine.cpp | 3 +- tests/test_cli_client.cpp | 9 +- tests/test_image_vault.cpp | 280 ++++++++++++++---- tests/test_output_formatter.cpp | 177 +++++++---- 63 files changed, 1281 insertions(+), 499 deletions(-) diff --git a/include/multipass/cli/format_utils.h b/include/multipass/cli/format_utils.h index 9b2b8627211..d0367276d99 100644 --- a/include/multipass/cli/format_utils.h +++ b/include/multipass/cli/format_utils.h @@ -47,15 +47,16 @@ void filter_aliases(google::protobuf::RepeatedPtrField // QDir operations virtual bool exists(const QDir& dir) const; virtual bool isReadable(const QDir& dir) const; - virtual QFileInfoList entryInfoList(const QDir& dir, const QStringList& nameFilters, + virtual QFileInfoList entryInfoList(const QDir& dir, + const QStringList& nameFilters, QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort) const; virtual bool mkpath(const QDir& dir, const QString& dirName) const; diff --git a/include/multipass/mount_handler.h b/include/multipass/mount_handler.h index 74e7fec5a9c..56167d6a499 100644 --- a/include/multipass/mount_handler.h +++ b/include/multipass/mount_handler.h @@ -84,7 +84,9 @@ class MountHandler : private DisabledCopyMove protected: MountHandler() = default; - MountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, VMMount mount_spec, + MountHandler(VirtualMachine* vm, + const SSHKeyProvider* ssh_key_provider, + VMMount mount_spec, const std::string& target) : vm{vm}, ssh_key_provider{ssh_key_provider}, mount_spec{std::move(mount_spec)}, target{target}, active{false} { diff --git a/include/multipass/sshfs_mount/sshfs_mount_handler.h b/include/multipass/sshfs_mount/sshfs_mount_handler.h index 9f98409b877..fe483b62e67 100644 --- a/include/multipass/sshfs_mount/sshfs_mount_handler.h +++ b/include/multipass/sshfs_mount/sshfs_mount_handler.h @@ -28,7 +28,9 @@ namespace multipass class SSHFSMountHandler : public MountHandler { public: - SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, + SSHFSMountHandler(VirtualMachine* vm, + const SSHKeyProvider* ssh_key_provider, + const std::string& target, VMMount mount_spec); ~SSHFSMountHandler() override; diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 21ce3e1189f..9a2d0d0468c 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -218,7 +218,8 @@ class Utils : public Singleton // virtual machine helpers virtual void wait_for_cloud_init(VirtualMachine* virtual_machine, std::chrono::milliseconds timeout, const SSHKeyProvider& key_provider) const; - virtual Path derive_instances_dir(const Path& data_dir, const Path& backend_directory_name, + virtual Path derive_instances_dir(const Path& data_dir, + const Path& backend_directory_name, const Path& instances_subdir) const; // system info helpers diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 589174bf7f8..bbb7cfd5b28 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -92,7 +92,8 @@ class VirtualMachine : private DisabledCopyMove virtual int get_num_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; virtual std::shared_ptr get_snapshot(const std::string& name) = 0; - virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + virtual std::shared_ptr take_snapshot(const VMSpecs& specs, + const std::string& name, const std::string& comment) = 0; virtual void delete_snapshot(const std::string& name) = 0; virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; diff --git a/include/multipass/vm_image_vault.h b/include/multipass/vm_image_vault.h index e4a24f1bc06..1f145818429 100644 --- a/include/multipass/vm_image_vault.h +++ b/include/multipass/vm_image_vault.h @@ -80,9 +80,13 @@ class VMImageVault : private DisabledCopyMove using PrepareAction = std::function; virtual ~VMImageVault() = default; - virtual VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, - const std::optional& checksum, const Path& save_dir) = 0; + virtual VMImage fetch_image(const FetchType& fetch_type, + const Query& query, + const PrepareAction& prepare, + const ProgressMonitor& monitor, + const bool unlock, + const std::optional& checksum, + const Path& save_dir) = 0; virtual void remove(const std::string& name) = 0; virtual bool has_record_for(const std::string& name) = 0; virtual void prune_expired_images() = 0; diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index f62d4c1ea59..e44d4cc70c1 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -95,7 +95,8 @@ QString cmd::Delete::description() const mp::ParseCode cmd::Delete::parse_args(mp::ArgParser* parser) { - parser->addPositionalArgument("name", "Names of instances and snapshots to delete", + parser->addPositionalArgument("name", + "Names of instances and snapshots to delete", "[.snapshot] [[.snapshot] ...]"); QCommandLineOption all_option(all_option_name, "Delete all instances and snapshots"); @@ -140,7 +141,9 @@ mp::ParseCode cmd::Delete::parse_instances_snapshots(mp::ArgParser* parser) return enforce_purged_snapshots(instances, snapshots, instance_found, snapshot_found); } -mp::ParseCode cmd::Delete::enforce_purged_snapshots(std::string& instances, std::string& snapshots, bool instance_found, +mp::ParseCode cmd::Delete::enforce_purged_snapshots(std::string& instances, + std::string& snapshots, + bool instance_found, bool snapshot_found) { if (snapshot_found && !request.purge()) @@ -148,7 +151,9 @@ mp::ParseCode cmd::Delete::enforce_purged_snapshots(std::string& instances, std: if (instance_found) cerr << fmt::format("{}:\n\n\tmultipass delete --purge {}\n\nYou can use a separate command to delete " "instances without purging them:\n\n\tmultipass delete {}\n", - no_purge_base_error_msg, snapshots, instances); + no_purge_base_error_msg, + snapshots, + instances); else cerr << fmt::format("{}.\n", no_purge_base_error_msg); diff --git a/src/client/cli/cmd/delete.h b/src/client/cli/cmd/delete.h index 2d06d76b8c7..3b33d98d066 100644 --- a/src/client/cli/cmd/delete.h +++ b/src/client/cli/cmd/delete.h @@ -46,7 +46,9 @@ class Delete final : public Command ParseCode parse_args(ArgParser* parser); ParseCode parse_instances_snapshots(ArgParser* parser); - ParseCode enforce_purged_snapshots(std::string& instances, std::string& snapshots, bool instance_found, + ParseCode enforce_purged_snapshots(std::string& instances, + std::string& snapshots, + bool instance_found, bool snapshot_found); }; } // namespace cmd diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 9a5885cef40..0f3e2df9fbf 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -58,7 +58,8 @@ QString cmd::Info::description() const mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) { - parser->addPositionalArgument("instance", "Names of instances or snapshots to display information about", + parser->addPositionalArgument("instance", + "Names of instances or snapshots to display information about", "[.snapshot] [[.snapshot] ...]"); QCommandLineOption all_option(all_option_name, "Display info for all instances"); @@ -69,7 +70,8 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) QCommandLineOption formatOption( format_option_name, "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", - format_option_name, "table"); + format_option_name, + "table"); parser->addOptions({all_option, noRuntimeInfoOption, formatOption}); diff --git a/src/client/cli/cmd/remote_settings_handler.cpp b/src/client/cli/cmd/remote_settings_handler.cpp index 319126c6d56..0676ef454c1 100644 --- a/src/client/cli/cmd/remote_settings_handler.cpp +++ b/src/client/cli/cmd/remote_settings_handler.cpp @@ -122,7 +122,10 @@ class RemoteSet : public RemoteSettingsCmd mp::AnimatedSpinner spinner{cout}; [[maybe_unused]] auto ret = - dispatch(&RpcMethod::set, set_request, on_success, on_failure, + dispatch(&RpcMethod::set, + set_request, + on_success, + on_failure, mp::make_reply_spinner_callback(spinner, cerr)); assert(ret == mp::ReturnCode::Ok && "should have thrown otherwise"); } diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 833eb8fd341..c80794b79a6 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -45,7 +45,10 @@ mp::ReturnCode cmd::Snapshot::run(mp::ArgParser* parser) }; spinner.start("Taking snapshot "); - return dispatch(&RpcMethod::snapshot, request, on_success, on_failure, + return dispatch(&RpcMethod::snapshot, + request, + on_success, + on_failure, make_logging_spinner_callback(spinner, cerr)); } @@ -72,8 +75,9 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) "names (see `help launch`). Default: \"snapshotN\", where N is one plus the " "number of snapshots that were ever taken for .", "name"); - QCommandLineOption comment_opt{ - {"comment", "c", "m"}, "An optional free comment to associate with the snapshot.", "comment"}; + QCommandLineOption comment_opt{{"comment", "c", "m"}, + "An optional free comment to associate with the snapshot.", + "comment"}; parser->addOptions({name_opt, comment_opt}); if (auto status = parser->commandParse(this); status != ParseCode::Ok) diff --git a/src/client/cli/formatter/csv_formatter.cpp b/src/client/cli/formatter/csv_formatter.cpp index 87d67c1f2d0..dbf17c86b71 100644 --- a/src/client/cli/formatter/csv_formatter.cpp +++ b/src/client/cli/formatter/csv_formatter.cpp @@ -73,14 +73,22 @@ std::string generate_snapshot_details(const mp::InfoReply reply) { const auto& fundamentals = info.snapshot_info().fundamentals(); - fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},", fundamentals.snapshot_name(), info.name(), - info.cpu_count(), info.disk_total(), info.memory_total()); + fmt::format_to(std::back_inserter(buf), + "{},{},{},{},{},", + fundamentals.snapshot_name(), + info.name(), + info.cpu_count(), + info.disk_total(), + info.memory_total()); fmt::format_to(std::back_inserter(buf), format_mounts(info.mount_info())); - fmt::format_to(std::back_inserter(buf), ",{},{},{},\"{}\"\n", + fmt::format_to(std::back_inserter(buf), + ",{},{},{},\"{}\"\n", google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp()), - fundamentals.parent(), fmt::join(info.snapshot_info().children(), ";"), fundamentals.comment()); + fundamentals.parent(), + fmt::join(info.snapshot_info().children(), ";"), + fundamentals.comment()); } return fmt::to_string(buf); @@ -101,18 +109,28 @@ std::string generate_instance_details(const mp::InfoReply reply) "outputting instance and snapshot details together is not supported in csv format"); const auto& instance_details = info.instance_info(); - fmt::format_to(std::back_inserter(buf), "{},{},{},{},{},{},{},{},{},{},{},{},", info.name(), + fmt::format_to(std::back_inserter(buf), + "{},{},{},{},{},{},{},{},{},{},{},{},", + info.name(), mp::format::status_string_for(info.instance_status()), instance_details.ipv4_size() ? instance_details.ipv4(0) : "", - instance_details.ipv6_size() ? instance_details.ipv6(0) : "", instance_details.current_release(), - instance_details.id(), instance_details.image_release(), instance_details.load(), - instance_details.disk_usage(), info.disk_total(), instance_details.memory_usage(), + instance_details.ipv6_size() ? instance_details.ipv6(0) : "", + instance_details.current_release(), + instance_details.id(), + instance_details.image_release(), + instance_details.load(), + instance_details.disk_usage(), + info.disk_total(), + instance_details.memory_usage(), info.memory_total()); fmt::format_to(std::back_inserter(buf), format_mounts(info.mount_info())); - fmt::format_to(std::back_inserter(buf), ",{},{},{}\n", fmt::join(instance_details.ipv4(), ";"), - info.cpu_count(), instance_details.num_snapshots()); + fmt::format_to(std::back_inserter(buf), + ",{},{},{}\n", + fmt::join(instance_details.ipv4(), ";"), + info.cpu_count(), + instance_details.num_snapshots()); } return fmt::to_string(buf); @@ -146,8 +164,12 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) for (const auto& item : mp::format::sorted(snapshot_list.snapshots())) { const auto& snapshot = item.fundamentals(); - fmt::format_to(std::back_inserter(buf), "{},{},{},\"{}\"\n", item.name(), snapshot.snapshot_name(), - snapshot.parent(), snapshot.comment()); + fmt::format_to(std::back_inserter(buf), + "{},{},{},\"{}\"\n", + item.name(), + snapshot.snapshot_name(), + snapshot.parent(), + snapshot.comment()); } return fmt::to_string(buf); diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index 0034349a8e0..a95de278c63 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -76,8 +76,9 @@ QJsonObject generate_snapshot_details(const mp::DetailedInfoItem& item) } snapshot_info.insert("mounts", mounts); - snapshot_info.insert("created", QString::fromStdString( - google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp()))); + snapshot_info.insert( + "created", + QString::fromStdString(google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp()))); snapshot_info.insert("parent", QString::fromStdString(fundamentals.parent())); QJsonArray children; diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index fd9ad781f61..8af28058770 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -78,14 +78,21 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) { if (mount != mount_paths.cbegin()) fmt::format_to(std::back_inserter(buf), "{:<16}", ""); - fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), - item.mount_info().longest_path_len(), mount->target_path()); + fmt::format_to(std::back_inserter(buf), + "{:{}} => {}\n", + mount->source_path(), + item.mount_info().longest_path_len(), + mount->target_path()); } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Created:", google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Parent:", fundamentals.parent().empty() ? "--" : fundamentals.parent()); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Created:", + google::protobuf::util::TimeUtil::ToString(fundamentals.creation_timestamp())); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Parent:", + fundamentals.parent().empty() ? "--" : fundamentals.parent()); auto children = item.snapshot_info().children(); fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Children:", children.empty() ? "--\n" : ""); @@ -97,7 +104,9 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) } // TODO@snapshots split and align string if it extends onto several lines - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Comment:", + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Comment:", fundamentals.comment().empty() ? "--" : std::regex_replace(fundamentals.comment(), newline, "$&" + std::string(16, ' '))); @@ -111,8 +120,10 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) const auto& instance_details = item.instance_info(); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Name:", item.name()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "State:", mp::format::status_string_for(item.instance_status())); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "State:", + mp::format::status_string_for(item.instance_status())); fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Snapshots:", instance_details.num_snapshots()); int ipv4_size = instance_details.ipv4_size(); @@ -129,25 +140,37 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "", instance_details.ipv6(i)); } - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Release:", instance_details.current_release().empty() ? "--" : instance_details.current_release()); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Release:", + instance_details.current_release().empty() ? "--" : instance_details.current_release()); fmt::format_to(std::back_inserter(buf), "{:<16}", "Image hash:"); if (instance_details.id().empty()) fmt::format_to(std::back_inserter(buf), "{}\n", "Not Available"); else - fmt::format_to(std::back_inserter(buf), "{}{}\n", instance_details.id().substr(0, 12), + fmt::format_to(std::back_inserter(buf), + "{}{}\n", + instance_details.id().substr(0, 12), !instance_details.image_release().empty() ? fmt::format(" (Ubuntu {})", instance_details.image_release()) : ""); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "CPU(s):", item.cpu_count().empty() ? "--" : item.cpu_count()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Load:", instance_details.load().empty() ? "--" : instance_details.load()); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Disk usage:", to_usage(instance_details.disk_usage(), item.disk_total())); - fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", - "Memory usage:", to_usage(instance_details.memory_usage(), item.memory_total())); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "CPU(s):", + item.cpu_count().empty() ? "--" : item.cpu_count()); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Load:", + instance_details.load().empty() ? "--" : instance_details.load()); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Disk usage:", + to_usage(instance_details.disk_usage(), item.disk_total())); + fmt::format_to(std::back_inserter(buf), + "{:<16}{}\n", + "Memory usage:", + to_usage(instance_details.memory_usage(), item.memory_total())); const auto& mount_paths = item.mount_info().mount_paths(); fmt::format_to(std::back_inserter(buf), "{:<16}{}", "Mounts:", mount_paths.empty() ? "--\n" : ""); @@ -156,8 +179,11 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) { if (mount != mount_paths.cbegin()) fmt::format_to(std::back_inserter(buf), "{:<16}", ""); - fmt::format_to(std::back_inserter(buf), "{:{}} => {}\n", mount->source_path(), - item.mount_info().longest_path_len(), mount->target_path()); + fmt::format_to(std::back_inserter(buf), + "{:{}} => {}\n", + mount->source_path(), + item.mount_info().longest_path_len(), + mount->target_path()); auto mount_maps = mount->mount_maps(); auto uid_mappings_size = mount_maps.uid_mappings_size(); @@ -168,7 +194,10 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) auto host_uid = uid_map_pair.host_id(); auto instance_uid = uid_map_pair.instance_id(); - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}", (i == 0) ? "UID map: " : "", (i == 0) ? 29 : 0, + fmt::format_to(std::back_inserter(buf), + "{:>{}}{}:{}{}", + (i == 0) ? "UID map: " : "", + (i == 0) ? 29 : 0, std::to_string(host_uid), (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid), (i == uid_mappings_size - 1) ? "\n" : ", "); @@ -180,9 +209,11 @@ std::string generate_instance_details(const mp::DetailedInfoItem& item) auto host_gid = gid_mapping->host_id(); auto instance_gid = gid_mapping->instance_id(); - fmt::format_to(std::back_inserter(buf), "{:>{}}{}:{}{}{}", + fmt::format_to(std::back_inserter(buf), + "{:>{}}{}:{}{}{}", (gid_mapping == mount_maps.gid_mappings().cbegin()) ? "GID map: " : "", - (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, std::to_string(host_gid), + (gid_mapping == mount_maps.gid_mappings().cbegin()) ? 29 : 0, + std::to_string(host_gid), (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid), (std::next(gid_mapping) != mount_maps.gid_mappings().cend()) ? ", " : "", (std::next(gid_mapping) == mount_maps.gid_mappings().cend()) ? "\n" : ""); @@ -203,14 +234,24 @@ std::string generate_instances_list(const mp::InstancesList& instance_list) const std::string name_col_header = "Name"; const auto name_column_width = mp::format::column_width( - instances.begin(), instances.end(), [](const auto& instance) -> int { return instance.name().length(); }, - name_col_header.length(), 24); + instances.begin(), + instances.end(), + [](const auto& instance) -> int { return instance.name().length(); }, + name_col_header.length(), + 24); const std::string::size_type state_column_width = 18; const std::string::size_type ip_column_width = 17; const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, "State", state_column_width, - "IPv4", ip_column_width, "Image"); + fmt::format_to(std::back_inserter(buf), + row_format, + name_col_header, + name_column_width, + "State", + state_column_width, + "IPv4", + ip_column_width, + "Image"); for (const auto& instance : mp::format::sorted(instance_list.instances())) { @@ -244,20 +285,31 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) const std::string name_col_header = "Instance", snapshot_col_header = "Snapshot", parent_col_header = "Parent", comment_col_header = "Comment"; const auto name_column_width = mp::format::column_width( - snapshots.begin(), snapshots.end(), [](const auto& snapshot) -> int { return snapshot.name().length(); }, + snapshots.begin(), + snapshots.end(), + [](const auto& snapshot) -> int { return snapshot.name().length(); }, name_col_header.length()); const auto snapshot_column_width = mp::format::column_width( - snapshots.begin(), snapshots.end(), + snapshots.begin(), + snapshots.end(), [](const auto& snapshot) -> int { return snapshot.fundamentals().snapshot_name().length(); }, snapshot_col_header.length()); const auto parent_column_width = mp::format::column_width( - snapshots.begin(), snapshots.end(), + snapshots.begin(), + snapshots.end(), [](const auto& snapshot) -> int { return snapshot.fundamentals().parent().length(); }, parent_col_header.length()); const auto row_format = "{:<{}}{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, snapshot_col_header, - snapshot_column_width, parent_col_header, parent_column_width, comment_col_header); + fmt::format_to(std::back_inserter(buf), + row_format, + name_col_header, + name_column_width, + snapshot_col_header, + snapshot_column_width, + parent_col_header, + parent_column_width, + comment_col_header); for (const auto& snapshot : mp::format::sorted(snapshot_list.snapshots())) { @@ -268,9 +320,14 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) if (std::regex_search(fundamentals.comment().begin(), fundamentals.comment().end(), match, newline)) max_comment_column_width = std::min((size_t)(match.position(1)) + 1, max_comment_column_width); - fmt::format_to(std::back_inserter(buf), row_format, snapshot.name(), name_column_width, - fundamentals.snapshot_name(), snapshot_column_width, - fundamentals.parent().empty() ? "--" : fundamentals.parent(), parent_column_width, + fmt::format_to(std::back_inserter(buf), + row_format, + snapshot.name(), + name_column_width, + fundamentals.snapshot_name(), + snapshot_column_width, + fundamentals.parent().empty() ? "--" : fundamentals.parent(), + parent_column_width, fundamentals.comment().empty() ? "--" : fundamentals.comment().length() > max_comment_column_width ? fmt::format("{}…", fundamentals.comment().substr(0, max_comment_column_width - 1)) @@ -337,15 +394,24 @@ std::string mp::TableFormatter::format(const NetworksReply& reply) const const std::string name_col_header = "Name", type_col_header = "Type", desc_col_header = "Description"; const auto name_column_width = mp::format::column_width( - interfaces.begin(), interfaces.end(), [](const auto& interface) -> int { return interface.name().length(); }, + interfaces.begin(), + interfaces.end(), + [](const auto& interface) -> int { return interface.name().length(); }, name_col_header.length()); const auto type_column_width = mp::format::column_width( - interfaces.begin(), interfaces.end(), [](const auto& interface) -> int { return interface.type().length(); }, + interfaces.begin(), + interfaces.end(), + [](const auto& interface) -> int { return interface.type().length(); }, type_col_header.length()); const auto row_format = "{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, name_col_header, name_column_width, type_col_header, - type_column_width, desc_col_header); + fmt::format_to(std::back_inserter(buf), + row_format, + name_col_header, + name_column_width, + type_col_header, + type_column_width, + desc_col_header); for (const auto& interface : format::sorted(reply.interfaces())) { @@ -419,10 +485,12 @@ std::string mp::TableFormatter::format(const mp::AliasDict& aliases) const auto width = [&aliases](const auto get_width, int header_width) -> int { return mp::format::column_width( - aliases.cbegin(), aliases.cend(), + aliases.cbegin(), + aliases.cend(), [&, get_width](const auto& ctx) -> int { return get_width(*std::max_element( - ctx.second.cbegin(), ctx.second.cend(), + ctx.second.cbegin(), + ctx.second.cend(), [&get_width](const auto& lhs, const auto& rhs) { return get_width(lhs) < get_width(rhs); })); }, header_width); @@ -438,7 +506,8 @@ std::string mp::TableFormatter::format(const mp::AliasDict& aliases) const const auto command_width = width([](const auto& alias) -> int { return alias.second.command.length(); }, command_col_header.length()); const auto context_width = mp::format::column_width( - aliases.cbegin(), aliases.cend(), + aliases.cbegin(), + aliases.cend(), [&aliases, &active_context](const auto& alias) -> int { return alias.first == aliases.active_context_name() ? alias.first.length() + active_context.length() : alias.first.length(); @@ -447,8 +516,16 @@ std::string mp::TableFormatter::format(const mp::AliasDict& aliases) const const auto row_format = "{:<{}}{:<{}}{:<{}}{:<{}}{:<}\n"; - fmt::format_to(std::back_inserter(buf), row_format, alias_col_header, alias_width, instance_col_header, - instance_width, command_col_header, command_width, context_col_header, context_width, + fmt::format_to(std::back_inserter(buf), + row_format, + alias_col_header, + alias_width, + instance_col_header, + instance_width, + command_col_header, + command_width, + context_col_header, + context_width, dir_col_header); for (const auto& [context_name, context_contents] : sort_dict(aliases)) diff --git a/src/client/cli/formatter/yaml_formatter.cpp b/src/client/cli/formatter/yaml_formatter.cpp index 4cd7ac0e1e7..7f263f0230f 100644 --- a/src/client/cli/formatter/yaml_formatter.cpp +++ b/src/client/cli/formatter/yaml_formatter.cpp @@ -144,7 +144,8 @@ YAML::Node generate_instance_details(const mp::DetailedInfoItem& item) auto instance_uid = uid_mapping.instance_id(); mount_node["uid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_uid), + fmt::format("{}:{}", + std::to_string(host_uid), (instance_uid == mp::default_id) ? "default" : std::to_string(instance_uid))); } for (const auto& gid_mapping : mount.mount_maps().gid_mappings()) @@ -153,7 +154,8 @@ YAML::Node generate_instance_details(const mp::DetailedInfoItem& item) auto instance_gid = gid_mapping.instance_id(); mount_node["gid_mappings"].push_back( - fmt::format("{}:{}", std::to_string(host_gid), + fmt::format("{}:{}", + std::to_string(host_gid), (instance_gid == mp::default_id) ? "default" : std::to_string(instance_gid))); } diff --git a/src/client/gui/gui_cmd.cpp b/src/client/gui/gui_cmd.cpp index 81c46eb69db..0d36c978b58 100644 --- a/src/client/gui/gui_cmd.cpp +++ b/src/client/gui/gui_cmd.cpp @@ -195,9 +195,9 @@ void cmd::GuiCmd::update_menu() for (auto it = instances_entries.cbegin(); it != instances_entries.cend(); ++it) { - auto instance = - std::find_if(reply.instance_list().instances().cbegin(), reply.instance_list().instances().cend(), - [it](const ListVMInstance& instance) { return it->first == instance.name(); }); + auto instance = std::find_if(reply.instance_list().instances().cbegin(), + reply.instance_list().instances().cend(), + [it](const ListVMInstance& instance) { return it->first == instance.name(); }); if (instance == reply.instance_list().instances().cend()) { diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e44ec7f5fac..a46f4f64a0c 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -444,7 +444,12 @@ auto fetch_image_for(const std::string& name, mp::VirtualMachineFactory& factory mp::Query query{name, "", false, "", mp::Query::Type::Alias, false}; - return vault.fetch_image(factory.fetch_type(), query, stub_prepare, stub_progress, false, std::nullopt, + return vault.fetch_image(factory.fetch_type(), + query, + stub_prepare, + stub_progress, + false, + std::nullopt, factory.get_instance_directory(name)); } @@ -1217,7 +1222,8 @@ auto timeout_for(const int requested_timeout, const int blueprint_timeout) } mp::SettingsHandler* register_instance_mod(std::unordered_map& vm_instance_specs, - InstanceTable& vm_instances, const InstanceTable& deleted_instances, + InstanceTable& vm_instances, + const InstanceTable& deleted_instances, const std::unordered_set& preparing_instances, std::function instance_persister) { @@ -1265,7 +1271,8 @@ void populate_snapshot_fundamentals(std::shared_ptr snapshot timestamp->set_nanos(snapshot->get_creation_timestamp().time().msec() * 1'000'000); } -void populate_mount_info(const std::unordered_map& mounts, mp::MountInfo* mount_info, +void populate_mount_info(const std::unordered_map& mounts, + mp::MountInfo* mount_info, bool& have_mounts) { mount_info->set_longest_path_len(0); @@ -1300,8 +1307,10 @@ void populate_mount_info(const std::unordered_map& mou } } -void populate_snapshot_info(mp::VirtualMachine& vm, std::shared_ptr snapshot, - mp::DetailedInfoItem* info, bool& have_mounts) +void populate_snapshot_info(mp::VirtualMachine& vm, + std::shared_ptr snapshot, + mp::DetailedInfoItem* info, + bool& have_mounts) { auto snapshot_info = info->mutable_snapshot_info(); auto fundamentals = snapshot_info->mutable_fundamentals(); @@ -1708,7 +1717,10 @@ try // clang-format on try { if (all_or_none) - populate_instance_info(vm, response.add_details(), request->no_runtime_information(), deleted, + populate_instance_info(vm, + response.add_details(), + request->no_runtime_information(), + deleted, have_mounts); for (const auto& snapshot : pick) @@ -1722,9 +1734,11 @@ try // clang-format on return grpc_status_for(errors); }; - auto [instance_selection, status] = - select_instances_and_react(operative_instances, deleted_instances, request->instances_snapshots(), - InstanceGroup::All, require_existing_instances_reaction); + auto [instance_selection, status] = select_instances_and_react(operative_instances, + deleted_instances, + request->instances_snapshots(), + InstanceGroup::All, + require_existing_instances_reaction); if (status.ok()) { @@ -2216,9 +2230,11 @@ try // clang-format on server}; DeleteReply response; - auto [instance_selection, status] = - select_instances_and_react(operative_instances, deleted_instances, request->instances_snapshots(), - InstanceGroup::All, require_existing_instances_reaction); + auto [instance_selection, status] = select_instances_and_react(operative_instances, + deleted_instances, + request->instances_snapshots(), + InstanceGroup::All, + require_existing_instances_reaction); if (status.ok()) { @@ -2452,10 +2468,13 @@ void mp::Daemon::snapshot(const mp::SnapshotRequest* request, try { mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), - *config->logger, server}; + *config->logger, + server}; const auto& instance_name = request->instance(); - auto [instance_trail, status] = find_instance_and_react(operative_instances, deleted_instances, instance_name, + auto [instance_trail, status] = find_instance_and_react(operative_instances, + deleted_instances, + instance_name, require_operative_instances_reaction); if (status.ok()) @@ -2504,12 +2523,15 @@ void mp::Daemon::restore(const mp::RestoreRequest* request, std::promise* status_promise) try { - mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), *config->logger, + mpl::ClientLogger logger{mpl::level_from(request->verbosity_level()), + *config->logger, server}; RestoreReply reply; const auto& instance_name = request->instance(); - auto [instance_trail, status] = find_instance_and_react(operative_instances, deleted_instances, instance_name, + auto [instance_trail, status] = find_instance_and_react(operative_instances, + deleted_instances, + instance_name, require_operative_instances_reaction); if (status.ok()) @@ -2548,7 +2570,8 @@ try const auto snapshot = vm_ptr->take_snapshot(vm_specs, "", fmt::format("Before restoring {}", request->snapshot())); - reply_msg(server, fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), + reply_msg(server, + fmt::format("Snapshot taken: {}.{}", instance_name, snapshot->get_name()), /* sticky = */ true); } } @@ -2900,9 +2923,13 @@ void mp::Daemon::create_vm(const CreateRequest* request, if (!vm_desc.image.id.empty()) checksum = vm_desc.image.id; - auto vm_image = - config->vault->fetch_image(fetch_type, query, prepare_action, progress_monitor, launch_from_blueprint, - checksum, config->factory->get_instance_directory(name)); + auto vm_image = config->vault->fetch_image(fetch_type, + query, + prepare_action, + progress_monitor, + launch_from_blueprint, + checksum, + config->factory->get_instance_directory(name)); const auto image_size = config->vault->minimum_image_size_for(vm_image.id); vm_desc.disk_space = compute_final_image_size( @@ -3136,9 +3163,13 @@ bool mp::Daemon::create_missing_mounts(std::unordered_map& } catch (const std::exception& e) { - mpl::log(mpl::Level::warning, category, - fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", mount_spec.source_path, target, - vm->vm_name, e.what())); + mpl::log(mpl::Level::warning, + category, + fmt::format(R"(Removing mount "{}" => "{}" from '{}': {})", + mount_spec.source_path, + target, + vm->vm_name, + e.what())); specs_it = mount_specs.erase(specs_it); // unordered_map so only iterators to erased element invalidated continue; @@ -3353,8 +3384,11 @@ void mp::Daemon::reply_msg(grpc::ServerReaderWriterInterface* se server->Write(reply); } -void mp::Daemon::populate_instance_info(VirtualMachine& vm, mp::DetailedInfoItem* info, bool no_runtime_info, - bool deleted, bool& have_mounts) +void mp::Daemon::populate_instance_info(VirtualMachine& vm, + mp::DetailedInfoItem* info, + bool no_runtime_info, + bool deleted, + bool& have_mounts) { const auto& name = vm.vm_name; auto instance_info = info->mutable_instance_info(); diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index efcf43b79f2..9b06b7cba4c 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -162,12 +162,14 @@ public slots: void stop_mounts(const std::string& name); // This returns whether any specs were updated (and need persisting) - bool update_mounts(VMSpecs& vm_specs, std::unordered_map& vm_mounts, + bool update_mounts(VMSpecs& vm_specs, + std::unordered_map& vm_mounts, VirtualMachine* vm); // This returns whether all required mount handlers were successfully created bool create_missing_mounts(std::unordered_map& mount_specs, - std::unordered_map& vm_mounts, VirtualMachine* vm); + std::unordered_map& vm_mounts, + VirtualMachine* vm); MountHandler::UPtr make_mount(VirtualMachine* vm, const std::string& target, const VMMount& mount); @@ -195,7 +197,10 @@ public slots: template void reply_msg(grpc::ServerReaderWriterInterface* server, std::string&& msg, bool sticky = false); - void populate_instance_info(VirtualMachine& vm, DetailedInfoItem* info, bool runtime_info, bool deleted, + void populate_instance_info(VirtualMachine& vm, + DetailedInfoItem* info, + bool runtime_info, + bool deleted, bool& have_mounts); std::unique_ptr config; diff --git a/src/daemon/daemon_rpc.cpp b/src/daemon/daemon_rpc.cpp index d41511fd444..ecfd56e6764 100644 --- a/src/daemon/daemon_rpc.cpp +++ b/src/daemon/daemon_rpc.cpp @@ -373,7 +373,8 @@ grpc::Status mp::DaemonRpc::snapshot(grpc::ServerContext* context, server->Read(&request); return verify_client_and_dispatch_operation( - std::bind(&DaemonRpc::on_snapshot, this, &request, server, std::placeholders::_1), client_cert_from(context)); + std::bind(&DaemonRpc::on_snapshot, this, &request, server, std::placeholders::_1), + client_cert_from(context)); } grpc::Status mp::DaemonRpc::restore(grpc::ServerContext* context, @@ -383,7 +384,8 @@ grpc::Status mp::DaemonRpc::restore(grpc::ServerContext* context, server->Read(&request); return verify_client_and_dispatch_operation( - std::bind(&DaemonRpc::on_restore, this, &request, server, std::placeholders::_1), client_cert_from(context)); + std::bind(&DaemonRpc::on_restore, this, &request, server, std::placeholders::_1), + client_cert_from(context)); } template diff --git a/src/daemon/daemon_rpc.h b/src/daemon/daemon_rpc.h index 76e195c1378..0e5c979a69e 100644 --- a/src/daemon/daemon_rpc.h +++ b/src/daemon/daemon_rpc.h @@ -98,9 +98,11 @@ class DaemonRpc : public QObject, public multipass::Rpc::Service, private Disabl void on_authenticate(const AuthenticateRequest* request, grpc::ServerReaderWriter* server, std::promise* status_promise); - void on_snapshot(const SnapshotRequest* request, grpc::ServerReaderWriter* server, + void on_snapshot(const SnapshotRequest* request, + grpc::ServerReaderWriter* server, std::promise* status_promise); - void on_restore(const RestoreRequest* request, grpc::ServerReaderWriter* server, + void on_restore(const RestoreRequest* request, + grpc::ServerReaderWriter* server, std::promise* status_promise); private: diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index 7f6d9d73a62..bb0cb6b7263 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -232,8 +232,10 @@ void persist_records(const T& records, const QString& path) } } // namespace -mp::DefaultVMImageVault::DefaultVMImageVault(std::vector image_hosts, URLDownloader* downloader, - const mp::Path& cache_dir_path, const mp::Path& data_dir_path, +mp::DefaultVMImageVault::DefaultVMImageVault(std::vector image_hosts, + URLDownloader* downloader, + const mp::Path& cache_dir_path, + const mp::Path& data_dir_path, const mp::days& days_to_expire) : BaseVMImageVault{image_hosts}, url_downloader{downloader}, @@ -251,9 +253,12 @@ mp::DefaultVMImageVault::~DefaultVMImageVault() url_downloader->abort_all_downloads(); } -mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, - const PrepareAction& prepare, const ProgressMonitor& monitor, - const bool unlock, const std::optional& checksum, +mp::VMImage mp::DefaultVMImageVault::fetch_image(const FetchType& fetch_type, + const Query& query, + const PrepareAction& prepare, + const ProgressMonitor& monitor, + const bool unlock, + const std::optional& checksum, const mp::Path& save_dir) { { @@ -532,7 +537,12 @@ void mp::DefaultVMImageVault::update_images(const FetchType& fetch_type, const P mpl::log(mpl::Level::info, category, fmt::format("Updating {} source image to latest", record.query.release)); try { - fetch_image(fetch_type, record.query, prepare, monitor, false, std::nullopt, + fetch_image(fetch_type, + record.query, + prepare, + monitor, + false, + std::nullopt, QFileInfo{record.image.image_path}.absolutePath()); // Remove old image @@ -630,7 +640,8 @@ mp::VMImage mp::DefaultVMImageVault::download_and_prepare_source_image( } } -QString mp::DefaultVMImageVault::extract_image_from(const VMImage& source_image, const ProgressMonitor& monitor, +QString mp::DefaultVMImageVault::extract_image_from(const VMImage& source_image, + const ProgressMonitor& monitor, const mp::Path& dest_dir) { MP_UTILS.make_dir(dest_dir); @@ -664,8 +675,10 @@ std::optional> mp::DefaultVMImageVault::get_image_future(co return std::nullopt; } -mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, const VMImage& prepared_image, - const std::string& id, const mp::Path& dest_dir) +mp::VMImage mp::DefaultVMImageVault::finalize_image_records(const Query& query, + const VMImage& prepared_image, + const std::string& id, + const mp::Path& dest_dir) { VMImage vm_image; diff --git a/src/daemon/default_vm_image_vault.h b/src/daemon/default_vm_image_vault.h index bea7e74c83a..2719f0420fe 100644 --- a/src/daemon/default_vm_image_vault.h +++ b/src/daemon/default_vm_image_vault.h @@ -45,13 +45,19 @@ class VaultRecord class DefaultVMImageVault final : public BaseVMImageVault { public: - DefaultVMImageVault(std::vector image_host, URLDownloader* downloader, - const multipass::Path& cache_dir_path, const multipass::Path& data_dir_path, + DefaultVMImageVault(std::vector image_host, + URLDownloader* downloader, + const multipass::Path& cache_dir_path, + const multipass::Path& data_dir_path, const multipass::days& days_to_expire); ~DefaultVMImageVault(); - VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + VMImage fetch_image(const FetchType& fetch_type, + const Query& query, + const PrepareAction& prepare, + const ProgressMonitor& monitor, + const bool unlock, + const std::optional& checksum, const Path& save_dir) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; @@ -67,7 +73,9 @@ class DefaultVMImageVault final : public BaseVMImageVault const PrepareAction& prepare, const ProgressMonitor& monitor); QString extract_image_from(const VMImage& source_image, const ProgressMonitor& monitor, const Path& dest_dir); std::optional> get_image_future(const std::string& id); - VMImage finalize_image_records(const Query& query, const VMImage& prepared_image, const std::string& id, + VMImage finalize_image_records(const Query& query, + const VMImage& prepared_image, + const std::string& id, const Path& dest_dir); void persist_image_records(); void persist_instance_records(); diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 545e06c0830..565445e7586 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -274,7 +274,8 @@ std::string management_ipv4_impl(std::optional& management_ip, } // namespace mp::LibVirtVirtualMachine::LibVirtVirtualMachine(const mp::VirtualMachineDescription& desc, - const std::string& bridge_name, mp::VMStatusMonitor& monitor, + const std::string& bridge_name, + mp::VMStatusMonitor& monitor, const mp::LibvirtWrapper::UPtr& libvirt_wrapper, const mp::Path& instance_dir) : BaseVirtualMachine{desc.vm_name, instance_dir}, @@ -555,9 +556,10 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) desc.disk_space = new_size; } -auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent) - -> std::shared_ptr +auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index 4d0ccf4920b..db4c475bc9d 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -35,8 +35,10 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine using DomainUPtr = std::unique_ptr; using NetworkUPtr = std::unique_ptr; - LibVirtVirtualMachine(const VirtualMachineDescription& desc, const std::string& bridge_name, - VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper, + LibVirtVirtualMachine(const VirtualMachineDescription& desc, + const std::string& bridge_name, + VMStatusMonitor& monitor, + const LibvirtWrapper::UPtr& libvirt_wrapper, const mp::Path& instance_dir); ~LibVirtVirtualMachine(); @@ -61,8 +63,10 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine protected: std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; - std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent) override; + std::shared_ptr make_specific_snapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent) override; private: DomainUPtr initialize_domain_info(virConnectPtr connection); diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp index dfdb429037b..87b26e4e518 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine_factory.cpp @@ -128,7 +128,10 @@ mp::VirtualMachine::UPtr mp::LibVirtVirtualMachineFactory::create_virtual_machin if (bridge_name.empty()) bridge_name = enable_libvirt_network(data_dir, libvirt_wrapper); - return std::make_unique(desc, bridge_name, monitor, libvirt_wrapper, + return std::make_unique(desc, + bridge_name, + monitor, + libvirt_wrapper, get_instance_directory(desc.vm_name)); } diff --git a/src/platform/backends/lxd/lxd_mount_handler.cpp b/src/platform/backends/lxd/lxd_mount_handler.cpp index d0815770f8b..31e532c4ec5 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.cpp +++ b/src/platform/backends/lxd/lxd_mount_handler.cpp @@ -27,8 +27,10 @@ constexpr int timeout_milliseconds = 30000; namespace multipass { -LXDMountHandler::LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, - const SSHKeyProvider* ssh_key_provider, const std::string& target_path, +LXDMountHandler::LXDMountHandler(mp::NetworkAccessManager* network_manager, + LXDVirtualMachine* lxd_virtual_machine, + const SSHKeyProvider* ssh_key_provider, + const std::string& target_path, VMMount mount_spec) : MountHandler{lxd_virtual_machine, ssh_key_provider, std::move(mount_spec), target_path}, network_manager{network_manager}, diff --git a/src/platform/backends/lxd/lxd_mount_handler.h b/src/platform/backends/lxd/lxd_mount_handler.h index cce12b48836..db6a6d76374 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.h +++ b/src/platform/backends/lxd/lxd_mount_handler.h @@ -26,8 +26,11 @@ namespace multipass class LXDMountHandler : public MountHandler { public: - LXDMountHandler(mp::NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, - const SSHKeyProvider* ssh_key_provider, const std::string& target_path, VMMount mount_spec); + LXDMountHandler(mp::NetworkAccessManager* network_manager, + LXDVirtualMachine* lxd_virtual_machine, + const SSHKeyProvider* ssh_key_provider, + const std::string& target_path, + VMMount mount_spec); ~LXDMountHandler() override; void activate_impl(ServerVariant server, std::chrono::milliseconds timeout) override; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index c19fe5a0c06..1230677214c 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -166,9 +166,12 @@ bool uses_default_id_mappings(const multipass::VMMount& mount) } // namespace -mp::LXDVirtualMachine::LXDVirtualMachine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor, - NetworkAccessManager* manager, const QUrl& base_url, - const QString& bridge_name, const QString& storage_pool, +mp::LXDVirtualMachine::LXDVirtualMachine(const VirtualMachineDescription& desc, + VMStatusMonitor& monitor, + NetworkAccessManager* manager, + const QUrl& base_url, + const QString& bridge_name, + const QString& storage_pool, const mp::Path& instance_dir) : BaseVirtualMachine{desc.vm_name, instance_dir}, name{QString::fromStdString(desc.vm_name)}, @@ -484,9 +487,10 @@ mp::LXDVirtualMachine::make_native_mount_handler(const SSHKeyProvider* ssh_key_p return std::make_unique(manager, this, ssh_key_provider, target, mount); } -auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent) - -> std::shared_ptr +auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index c956cf6dc1d..ffcd559d4d9 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -33,8 +33,12 @@ class VMStatusMonitor; class LXDVirtualMachine : public BaseVirtualMachine { public: - LXDVirtualMachine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor, NetworkAccessManager* manager, - const QUrl& base_url, const QString& bridge_name, const QString& storage_pool, + LXDVirtualMachine(const VirtualMachineDescription& desc, + VMStatusMonitor& monitor, + NetworkAccessManager* manager, + const QUrl& base_url, + const QString& bridge_name, + const QString& storage_pool, const mp::Path& instance_dir); ~LXDVirtualMachine() override; void stop() override; @@ -59,8 +63,10 @@ class LXDVirtualMachine : public BaseVirtualMachine protected: std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; - std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent) override; + std::shared_ptr make_specific_snapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent) override; private: const QString name; diff --git a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp index d7683b0239c..8c718cf5570 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine_factory.cpp @@ -84,7 +84,8 @@ mp::NetworkInterfaceInfo munch_network(std::map(desc, monitor, manager.get(), base_url, multipass_bridge_name, + return std::make_unique(desc, + monitor, + manager.get(), + base_url, + multipass_bridge_name, storage_pool, MP_UTILS.make_dir(get_instance_directory(desc.vm_name))); } diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.cpp b/src/platform/backends/lxd/lxd_vm_image_vault.cpp index ab8669fd2ee..f39a57a765a 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.cpp +++ b/src/platform/backends/lxd/lxd_vm_image_vault.cpp @@ -157,9 +157,12 @@ mp::LXDVMImageVault::LXDVMImageVault(std::vector image_hosts, URLD { } -mp::VMImage mp::LXDVMImageVault::fetch_image(const FetchType& fetch_type, const Query& query, - const PrepareAction& prepare, const ProgressMonitor& monitor, - const bool unlock, const std::optional& checksum, +mp::VMImage mp::LXDVMImageVault::fetch_image(const FetchType& fetch_type, + const Query& query, + const PrepareAction& prepare, + const ProgressMonitor& monitor, + const bool unlock, + const std::optional& checksum, const mp::Path& /* save_dir */) { // Look for an already existing instance and get its image info diff --git a/src/platform/backends/lxd/lxd_vm_image_vault.h b/src/platform/backends/lxd/lxd_vm_image_vault.h index ed4f63c8568..40f4f4e9f83 100644 --- a/src/platform/backends/lxd/lxd_vm_image_vault.h +++ b/src/platform/backends/lxd/lxd_vm_image_vault.h @@ -39,8 +39,12 @@ class LXDVMImageVault final : public BaseVMImageVault LXDVMImageVault(std::vector image_host, URLDownloader* downloader, NetworkAccessManager* manager, const QUrl& base_url, const QString& cache_dir_path, const multipass::days& days_to_expire); - VMImage fetch_image(const FetchType& fetch_type, const Query& query, const PrepareAction& prepare, - const ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, + VMImage fetch_image(const FetchType& fetch_type, + const Query& query, + const PrepareAction& prepare, + const ProgressMonitor& monitor, + const bool unlock, + const std::optional& checksum, const Path& /* save_dir */) override; void remove(const std::string& name) override; bool has_record_for(const std::string& name) override; diff --git a/src/platform/backends/qemu/qemu_mount_handler.cpp b/src/platform/backends/qemu/qemu_mount_handler.cpp index dd04a47fd32..3c042a3f3a8 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.cpp +++ b/src/platform/backends/qemu/qemu_mount_handler.cpp @@ -31,8 +31,10 @@ constexpr auto category = "qemu-mount-handler"; namespace multipass { -QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, - const std::string& target, VMMount mount_spec) +QemuMountHandler::QemuMountHandler(QemuVirtualMachine* vm, + const SSHKeyProvider* ssh_key_provider, + const std::string& target, + VMMount mount_spec) : MountHandler{vm, ssh_key_provider, std::move(mount_spec), target}, vm_mount_args{vm->modifiable_mount_args()}, // Create a reproducible unique mount tag for each mount. The cmd arg can only be 31 bytes long so part of the diff --git a/src/platform/backends/qemu/qemu_mount_handler.h b/src/platform/backends/qemu/qemu_mount_handler.h index 0e4c3bd6752..691aa657745 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.h +++ b/src/platform/backends/qemu/qemu_mount_handler.h @@ -27,7 +27,9 @@ namespace multipass class QemuMountHandler : public MountHandler { public: - QemuMountHandler(QemuVirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, const std::string& target, + QemuMountHandler(QemuVirtualMachine* vm, + const SSHKeyProvider* ssh_key_provider, + const std::string& target, VMMount mount_spec); ~QemuMountHandler() override; diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index abe2a3c27c8..4ad00eb361d 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -35,24 +35,30 @@ namespace std::unique_ptr make_capture_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-c", tag, image_path}, - /* src_img = */ "", image_path); + /* src_img = */ "", + image_path); } std::unique_ptr make_restore_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-a", tag, image_path}, - /* src_img = */ "", image_path); + /* src_img = */ "", + image_path); } std::unique_ptr make_delete_spec(const QString& tag, const mp::Path& image_path) { return std::make_unique(QStringList{"snapshot", "-d", tag, image_path}, - /* src_img = */ "", image_path); + /* src_img = */ "", + image_path); } } // namespace -mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent, VirtualMachineDescription& desc) +mp::QemuSnapshot::QemuSnapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent, + VirtualMachineDescription& desc) : BaseSnapshot(name, comment, specs, std::move(parent)), desc{desc}, image_path{desc.image.image_path} { } @@ -69,8 +75,10 @@ void mp::QemuSnapshot::capture_impl() // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) if (backend::instance_image_has_snapshot(image_path, tag)) - throw std::runtime_error{fmt::format( - "A snapshot with the same tag already exists in the image. Image: {}; tag: {})", image_path, tag)}; + throw std::runtime_error{ + fmt::format("A snapshot with the same tag already exists in the image. Image: {}; tag: {})", + image_path, + tag)}; mp::backend::checked_exec_qemu_img(make_capture_spec(tag, image_path)); } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 81480a1eb1b..fe7a0718843 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -32,8 +32,11 @@ class VirtualMachineDescription; class QemuSnapshot : public BaseSnapshot { public: - QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent, VirtualMachineDescription& desc); + QemuSnapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent, + VirtualMachineDescription& desc); QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc); protected: diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 57040cf98d7..6f78ce324bf 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -196,11 +196,14 @@ auto generate_metadata(const QStringList& platform_args, const QStringList& proc } } // namespace -mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, - VMStatusMonitor& monitor, const mp::Path& instance_dir) +mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc, + QemuPlatform* qemu_platform, + VMStatusMonitor& monitor, + const mp::Path& instance_dir) : BaseVirtualMachine{mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag) ? State::suspended : State::off, - desc.vm_name, instance_dir}, + desc.vm_name, + instance_dir}, desc{desc}, mac_addr{desc.default_mac_address}, username{desc.ssh_username}, @@ -618,9 +621,10 @@ mp::QemuVirtualMachine::MountArgs& mp::QemuVirtualMachine::modifiable_mount_args return mount_args; } -auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent) - -> std::shared_ptr +auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent) -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise return std::make_shared(name, comment, specs, std::move(parent), desc); diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index dfcfd7d2306..6d23b237c14 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -41,7 +41,9 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine public: using MountArgs = std::unordered_map>; - QemuVirtualMachine(const VirtualMachineDescription& desc, QemuPlatform* qemu_platform, VMStatusMonitor& monitor, + QemuVirtualMachine(const VirtualMachineDescription& desc, + QemuPlatform* qemu_platform, + VMStatusMonitor& monitor, const Path& instance_dir); ~QemuVirtualMachine(); @@ -76,8 +78,10 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine } std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; - std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent) override; + std::shared_ptr make_specific_snapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, + std::shared_ptr parent) override; private: void on_started(); diff --git a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp index 1981235b826..9b554a8e25c 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine_factory.cpp @@ -51,7 +51,9 @@ mp::QemuVirtualMachineFactory::QemuVirtualMachineFactory(QemuPlatform::UPtr qemu mp::VirtualMachine::UPtr mp::QemuVirtualMachineFactory::create_virtual_machine(const VirtualMachineDescription& desc, VMStatusMonitor& monitor) { - return std::make_unique(desc, qemu_platform.get(), monitor, + return std::make_unique(desc, + qemu_platform.get(), + monitor, get_instance_directory(desc.vm_name)); } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f09467069df..074455f2037 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -76,15 +76,21 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa catch (std::out_of_range&) { throw std::runtime_error{fmt::format("Missing snapshot parent. Snapshot name: {}; parent name: {}", - json["name"].toString(), parent_name)}; + json["name"].toString(), + parent_name)}; } } } // namespace -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, // NOLINT(modernize-pass-by-value) - const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, - MemorySize disk_space, VirtualMachine::State state, - std::unordered_map mounts, QJsonObject metadata, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-pass-by-value) + const std::string& comment, // NOLINT(modernize-pass-by-value) + const QDateTime& creation_timestamp, + int num_cores, + MemorySize mem_size, + MemorySize disk_space, + VirtualMachine::State state, + std::unordered_map mounts, + QJsonObject metadata, std::shared_ptr parent) : name{name}, comment{comment}, @@ -107,7 +113,9 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comme throw std::runtime_error{fmt::format("Invalid disk size for snapshot: {}", disk_bytes)}; } -mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, +mp::BaseSnapshot::BaseSnapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, std::shared_ptr parent) : BaseSnapshot{name, comment, diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 126e19183b6..98807087395 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -34,7 +34,9 @@ struct VMSpecs; class BaseSnapshot : public Snapshot { public: - BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, + BaseSnapshot(const std::string& name, + const std::string& comment, + const VMSpecs& specs, std::shared_ptr parent); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); @@ -76,9 +78,15 @@ class BaseSnapshot : public Snapshot { }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); - BaseSnapshot(const std::string& name, const std::string& get_comment, const QDateTime& creation_timestamp, - int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, - std::unordered_map mounts, QJsonObject metadata, + BaseSnapshot(const std::string& name, + const std::string& get_comment, + const QDateTime& creation_timestamp, + int num_cores, + MemorySize mem_size, + MemorySize disk_space, + VirtualMachine::State state, + std::unordered_map mounts, + QJsonObject metadata, std::shared_ptr parent); private: diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index a8e70b2564b..4c4415ad98d 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -98,7 +98,8 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted namespace multipass { -BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, +BaseVirtualMachine::BaseVirtualMachine(VirtualMachine::State state, + const std::string& vm_name, const mp::Path& instance_dir) : VirtualMachine(state, vm_name, instance_dir){}; @@ -148,8 +149,9 @@ auto BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista const std::unique_lock lock{snapshot_mutex}; ret.reserve(snapshots.size()); - std::transform(std::cbegin(snapshots), std::cend(snapshots), std::back_inserter(ret), - [](const auto& pair) { return pair.second; }); + std::transform(std::cbegin(snapshots), std::cend(snapshots), std::back_inserter(ret), [](const auto& pair) { + return pair.second; + }); return ret; } @@ -172,7 +174,8 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& na return std::const_pointer_cast(std::as_const(*this).get_snapshot(name)); } -void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, +void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, + std::shared_ptr& old_head, int old_count) { if (old_head != head_snapshot) @@ -200,7 +203,8 @@ auto BaseVirtualMachine::make_take_snapshot_rollback(SnapshotMap::iterator it) }); } -std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, const std::string& name, +std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, + const std::string& name, const std::string& comment) { std::string snapshot_name; @@ -254,7 +258,8 @@ auto BaseVirtualMachine::make_deleted_head_rollback(const Path& head_path, const }); } -void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, const bool& wrote_head, +void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, + const bool& wrote_head, std::shared_ptr& old_head) { if (head_snapshot != old_head) @@ -351,8 +356,10 @@ void BaseVirtualMachine::load_snapshots() { std::unique_lock lock{snapshot_mutex}; - auto snapshot_files = MP_FILEOPS.entryInfoList(instance_dir, {QString{"*.%1"}.arg(snapshot_extension)}, - QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); + auto snapshot_files = MP_FILEOPS.entryInfoList(instance_dir, + {QString{"*.%1"}.arg(snapshot_extension)}, + QDir::Filter::Files | QDir::Filter::Readable, + QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) load_snapshot_from_file(finfo.filePath()); @@ -399,8 +406,11 @@ void BaseVirtualMachine::log_latest_snapshot(LockT lock) const auto name = head_snapshot->get_name(); lock.unlock(); // unlock earlier - mpl::log(log_detail_lvl, vm_name, - fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", name, parent_name, + mpl::log(log_detail_lvl, + vm_name, + fmt::format(R"(New snapshot: "{}"; Descendant of: "{}"; Total snapshots: {})", + name, + parent_name, num_snapshots)); } } @@ -415,7 +425,8 @@ void BaseVirtualMachine::load_snapshot_from_file(const QString& filename) const auto& data = MP_FILEOPS.read_all(file); if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) - throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), + throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", + file.fileName(), parse_error.errorString())}; else if (json.isEmpty()) throw std::runtime_error{fmt::v9::format("Empty snapshot JSON: {}", file.fileName())}; @@ -438,13 +449,18 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) auto BaseVirtualMachine::make_head_file_rollback(const Path& head_path, QFile& head_file) const { - return sg::make_scope_guard([this, &head_path, &head_file, old_head = head_snapshot->get_parents_name(), + return sg::make_scope_guard([this, + &head_path, + &head_file, + old_head = head_snapshot->get_parents_name(), existed = head_file.exists()]() noexcept { head_file_rollback_helper(head_path, head_file, old_head, existed); }); } -void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, QFile& head_file, const std::string& old_head, +void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, + QFile& head_file, + const std::string& old_head, bool existed) const { // best effort, ignore returns @@ -498,13 +514,20 @@ std::string BaseVirtualMachine::generate_snapshot_name() const auto BaseVirtualMachine::make_restore_rollback(const Path& head_path, VMSpecs& specs) { return sg::make_scope_guard([this, &head_path, old_head = head_snapshot, old_specs = specs, &specs]() noexcept { - top_catch_all(vm_name, &BaseVirtualMachine::restore_rollback_helper, this, head_path, old_head, old_specs, + top_catch_all(vm_name, + &BaseVirtualMachine::restore_rollback_helper, + this, + head_path, + old_head, + old_specs, specs); }); } -void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, const std::shared_ptr& old_head, - const VMSpecs& old_specs, VMSpecs& specs) +void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, + const std::shared_ptr& old_head, + const VMSpecs& old_specs, + VMSpecs& specs) { // best effort only old_head->apply(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 0e2d7aed3a8..7e255a77ff7 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -64,7 +64,8 @@ class BaseVirtualMachine : public VirtualMachine // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor - std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, + std::shared_ptr take_snapshot(const VMSpecs& specs, + const std::string& name, const std::string& comment) override; void delete_snapshot(const std::string& name) override; void restore_snapshot(const std::string& name, VMSpecs& specs) override; @@ -73,7 +74,8 @@ class BaseVirtualMachine : public VirtualMachine protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; - virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + virtual std::shared_ptr make_specific_snapshot(const std::string& name, + const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) = 0; @@ -91,7 +93,9 @@ class BaseVirtualMachine : public VirtualMachine void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count); auto make_head_file_rollback(const Path& head_path, QFile& head_file) const; - void head_file_rollback_helper(const Path& head_path, QFile& head_file, const std::string& old_head, + void head_file_rollback_helper(const Path& head_path, + QFile& head_file, + const std::string& old_head, bool existed) const; void persist_head_snapshot() const; @@ -99,12 +103,15 @@ class BaseVirtualMachine : public VirtualMachine std::string generate_snapshot_name() const; auto make_restore_rollback(const Path& head_path, VMSpecs& specs); - void restore_rollback_helper(const Path& head_path, const std::shared_ptr& old_head, - const VMSpecs& old_specs, VMSpecs& specs); + void restore_rollback_helper(const Path& head_path, + const std::shared_ptr& old_head, + const VMSpecs& old_specs, + VMSpecs& specs); bool updated_deleted_head(std::shared_ptr& snapshot, const Path& head_path); auto make_deleted_head_rollback(const Path& head_path, const bool& wrote_head); - void deleted_head_rollback_helper(const Path& head_path, const bool& wrote_head, + void deleted_head_rollback_helper(const Path& head_path, + const bool& wrote_head, std::shared_ptr& old_head); void update_parents(std::shared_ptr& deleted_parent, diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 189fa45a727..9c170f2fb24 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -33,16 +33,18 @@ namespace mp = multipass; namespace mpp = mp::platform; auto mp::backend::checked_exec_qemu_img(std::unique_ptr spec, - const std::string& custom_error_prefix, std::optional timeout) - -> Process::UPtr + const std::string& custom_error_prefix, + std::optional timeout) -> Process::UPtr { auto process = mpp::make_process(std::move(spec)); auto process_state = timeout ? process->execute(*timeout) : process->execute(); if (!process_state.completed_successfully()) { - throw std::runtime_error(fmt::format("{}: qemu-img failed ({}) with output:\n{}", custom_error_prefix, - process_state.failure_message(), process->read_all_standard_error())); + throw std::runtime_error(fmt::format("{}: qemu-img failed ({}) with output:\n{}", + custom_error_prefix, + process_state.failure_message(), + process->read_all_standard_error())); } return process; @@ -54,7 +56,8 @@ void mp::backend::resize_instance_image(const MemorySize& disk_space, const mp:: QStringList qemuimg_parameters{{"resize", image_path, disk_size}}; checked_exec_qemu_img(std::make_unique(qemuimg_parameters, "", image_path), - "Cannot resize instance image", mp::image_resize_timeout); + "Cannot resize instance image", + mp::image_resize_timeout); } mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) diff --git a/src/sshfs_mount/sshfs_mount_handler.cpp b/src/sshfs_mount/sshfs_mount_handler.cpp index 9b308bfed9a..62693f6b248 100644 --- a/src/sshfs_mount/sshfs_mount_handler.cpp +++ b/src/sshfs_mount/sshfs_mount_handler.cpp @@ -111,8 +111,10 @@ catch (const mp::ExitlessSSHProcessException&) namespace multipass { -SSHFSMountHandler::SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* ssh_key_provider, - const std::string& target, VMMount mount_spec) +SSHFSMountHandler::SSHFSMountHandler(VirtualMachine* vm, + const SSHKeyProvider* ssh_key_provider, + const std::string& target, + VMMount mount_spec) : MountHandler{vm, ssh_key_provider, std::move(mount_spec), target}, process{nullptr}, config{"", @@ -125,7 +127,8 @@ SSHFSMountHandler::SSHFSMountHandler(VirtualMachine* vm, const SSHKeyProvider* s this->mount_spec.gid_mappings, this->mount_spec.uid_mappings} { - mpl::log(mpl::Level::info, category, + mpl::log(mpl::Level::info, + category, fmt::format("initializing mount {} => {} in '{}'", this->mount_spec.source_path, target, vm->vm_name)); } diff --git a/src/utils/file_ops.cpp b/src/utils/file_ops.cpp index b5010777e20..d97f881e1b4 100644 --- a/src/utils/file_ops.cpp +++ b/src/utils/file_ops.cpp @@ -34,7 +34,9 @@ bool mp::FileOps::isReadable(const QDir& dir) const return dir.isReadable(); } -QFileInfoList multipass::FileOps::entryInfoList(const QDir& dir, const QStringList& nameFilters, QDir::Filters filters, +QFileInfoList multipass::FileOps::entryInfoList(const QDir& dir, + const QStringList& nameFilters, + QDir::Filters filters, QDir::SortFlags sort) const { return dir.entryInfoList(nameFilters, filters, sort); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 85c7d42fbbc..f7f0732a98c 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -690,7 +690,8 @@ void mp::utils::set_owner_for(mp::SSHSession& session, const std::string& root, vm_group, relative_target.substr(0, relative_target.find_first_of('/')))); } -mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir, const mp::Path& backend_directory_name, +mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir, + const mp::Path& backend_directory_name, const mp::Path& instances_subdir) const { if (backend_directory_name.isEmpty()) diff --git a/tests/blueprint_test_lambdas.cpp b/tests/blueprint_test_lambdas.cpp index 91d2bb8a533..304efd08c08 100644 --- a/tests/blueprint_test_lambdas.cpp +++ b/tests/blueprint_test_lambdas.cpp @@ -34,14 +34,22 @@ namespace mp = multipass; namespace mpt = multipass::test; -std::function, const mp::Path&)> +std::function, + const mp::Path&)> mpt::fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum) { - return [&release, &remote, must_have_checksum]( - const mp::FetchType& fetch_type, const mp::Query& query, const mp::VMImageVault::PrepareAction& prepare, - const mp::ProgressMonitor& monitor, const bool unlock, const std::optional& checksum, - const mp::Path& save_dir) { + return [&release, &remote, must_have_checksum](const mp::FetchType& fetch_type, + const mp::Query& query, + const mp::VMImageVault::PrepareAction& prepare, + const mp::ProgressMonitor& monitor, + const bool unlock, + const std::optional& checksum, + const mp::Path& save_dir) { EXPECT_EQ(query.release, release); if (remote.empty()) { diff --git a/tests/blueprint_test_lambdas.h b/tests/blueprint_test_lambdas.h index 8deb2952a9f..f046c6d92df 100644 --- a/tests/blueprint_test_lambdas.h +++ b/tests/blueprint_test_lambdas.h @@ -38,8 +38,13 @@ class MemorySize; namespace test { -std::function, const multipass::Path&)> +std::function, + const multipass::Path&)> fetch_image_lambda(const std::string& release, const std::string& remote, const bool must_have_checksum = false); std::function diff --git a/tests/lxd/test_lxd_backend.cpp b/tests/lxd/test_lxd_backend.cpp index ca3fd40612d..77641ed4b9d 100644 --- a/tests/lxd/test_lxd_backend.cpp +++ b/tests/lxd/test_lxd_backend.cpp @@ -446,9 +446,13 @@ TEST_F(LXDBackend, creates_in_stopped_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_TRUE(vm_created); EXPECT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -495,9 +499,13 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_start) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); machine.start(); @@ -546,9 +554,13 @@ TEST_F(LXDBackend, machine_persists_and_sets_state_on_shutdown) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_CALL(mock_monitor, persist_state_for(_, _)).Times(2); machine.shutdown(); @@ -592,9 +604,13 @@ TEST_F(LXDBackend, machine_persists_internal_stopped_state_on_destruction) }); { - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; ASSERT_EQ(machine.state, mp::VirtualMachine::State::running); } // Simulate multipass exiting by having the vm destruct @@ -649,9 +665,13 @@ TEST_F(LXDBackend, machine_does_not_update_state_in_dtor) // create in its own scope so the dtor is called { - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; } EXPECT_TRUE(vm_shutdown); @@ -707,9 +727,13 @@ TEST_F(LXDBackend, machineLogsNotFoundExceptionInDtor) // create in its own scope so the dtor is called { - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; machine.shutdown(); } @@ -757,9 +781,13 @@ TEST_F(LXDBackend, does_not_call_stop_when_snap_refresh_is_detected) // create in its own scope so the dtor is called { - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; } EXPECT_FALSE(stop_requested); @@ -803,9 +831,13 @@ TEST_F(LXDBackend, calls_stop_when_snap_refresh_does_not_exist) // create in its own scope so the dtor is called { - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; } EXPECT_TRUE(stop_requested); @@ -888,9 +920,13 @@ TEST_F(LXDBackend, posts_expected_data_when_creating_instance) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; } TEST_F(LXDBackend, prepare_source_image_does_not_modify) @@ -960,7 +996,8 @@ TEST_F(LXDBackend, unimplemented_functions_logs_trace_message) const std::string name{"foo"}; EXPECT_CALL(*logger_scope.mock_logger, - log(Eq(mpl::Level::trace), mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), + log(Eq(mpl::Level::trace), + mpt::MockLogger::make_cstring_matcher(StrEq("lxd factory")), mpt::MockLogger::make_cstring_matcher( StrEq(fmt::format("No further resources to remove for \"{}\"", name))))); @@ -1102,9 +1139,13 @@ TEST_P(LXDNetworkInfoSuite, returns_expected_network_info) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(mpt::StubSSHKeyProvider()), "10.217.27.168"); EXPECT_TRUE(machine.ipv6().empty()); @@ -1148,9 +1189,13 @@ TEST_F(LXDBackend, ssh_hostname_timeout_throws_and_sets_unknown_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_THROW(machine.ssh_hostname(std::chrono::milliseconds(1)), std::runtime_error); EXPECT_EQ(machine.state, mp::VirtualMachine::State::unknown); @@ -1187,9 +1232,13 @@ TEST_F(LXDBackend, no_ip_address_returns_unknown) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_EQ(machine.management_ipv4(mpt::StubSSHKeyProvider()), "UNKNOWN"); } @@ -1531,9 +1580,13 @@ TEST_F(LXDBackend, unsupported_suspend_throws) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; MP_EXPECT_THROW_THAT(machine.suspend(), std::runtime_error, mpt::match_what(StrEq("suspend is currently not supported"))); @@ -1565,9 +1618,13 @@ TEST_F(LXDBackend, start_while_frozen_unfreezes) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::info), mpt::MockLogger::make_cstring_matcher(StrEq("pied-piper-valley")), @@ -1594,9 +1651,13 @@ TEST_F(LXDBackend, shutdown_while_stopped_does_nothing_and_logs_debug) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::stopped); @@ -1627,9 +1688,13 @@ TEST_F(LXDBackend, shutdown_while_frozen_does_nothing_and_logs_info) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, mock_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + mock_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; ASSERT_EQ(machine.current_state(), mp::VirtualMachine::State::suspended); @@ -1679,9 +1744,13 @@ TEST_F(LXDBackend, ensure_vm_running_does_not_throw_starting) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; machine.start(); @@ -1733,9 +1802,13 @@ TEST_F(LXDBackend, shutdown_while_starting_throws_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; machine.start(); @@ -1787,9 +1860,13 @@ TEST_F(LXDBackend, start_failure_while_starting_throws_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; machine.start(); @@ -1842,9 +1919,13 @@ TEST_F(LXDBackend, reboots_while_starting_does_not_throw_and_sets_correct_state) return new mpt::MockLocalSocketReply(mpt::not_found_data, QNetworkReply::ContentNotFoundError); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; machine.start(); @@ -1866,9 +1947,13 @@ TEST_F(LXDBackend, current_state_connection_error_logs_warning_and_sets_unknown_ throw mp::LocalSocketConnectionException(exception_message); }); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_CALL(*logger_scope.mock_logger, log(Eq(mpl::Level::warning), mpt::MockLogger::make_cstring_matcher(StrEq("pied-piper-valley")), @@ -1921,9 +2006,13 @@ TEST_P(LXDInstanceStatusTestSuite, lxd_state_returns_expected_VirtualMachine_sta } } - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; EXPECT_EQ(machine.current_state(), expected_state); } @@ -2154,9 +2243,13 @@ TEST_F(LXDBackend, posts_extra_network_devices) auto json_matcher = ResultOf(&extract_devices, devices_matcher); setup_vm_creation_expectations(*mock_network_access_manager, request_data_matcher(json_matcher)); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; } TEST_F(LXDBackend, posts_network_data_config_if_available) @@ -2173,9 +2266,13 @@ TEST_F(LXDBackend, posts_network_data_config_if_available) setup_vm_creation_expectations(*mock_network_access_manager, request_data_matcher(json_matcher)); - mp::LXDVirtualMachine machine{ - default_description, stub_monitor, mock_network_access_manager.get(), base_url, bridge_name, - default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine machine{default_description, + stub_monitor, + mock_network_access_manager.get(), + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; } namespace diff --git a/tests/lxd/test_lxd_image_vault.cpp b/tests/lxd/test_lxd_image_vault.cpp index 2934d31191d..cea8b0ac051 100644 --- a/tests/lxd/test_lxd_image_vault.cpp +++ b/tests/lxd/test_lxd_image_vault.cpp @@ -95,8 +95,13 @@ TEST_F(LXDImageVault, instance_exists_fetch_returns_expected_image_info) base_url, cache_dir.path(), mp::days{0}}; mp::VMImage image; - EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -123,8 +128,13 @@ TEST_F(LXDImageVault, instance_exists_custom_image_returns_expected_image_info) base_url, cache_dir.path(), mp::days{0}}; mp::VMImage image; - EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_EQ(image.id, "6937ddd3f4c3329182855843571fc91ae4fee24e8e0eb0f7cdcf2c22feed4dab"); EXPECT_EQ(image.original_release, "Snapcraft builder for Core 20"); @@ -152,8 +162,13 @@ TEST_F(LXDImageVault, instance_exists_uses_cached_release_title) base_url, cache_dir.path(), mp::days{0}}; mp::VMImage image; - EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "Fake Title"); @@ -182,8 +197,13 @@ TEST_F(LXDImageVault, instance_exists_no_cached_release_title_info_for_fails) base_url, cache_dir.path(), mp::days{0}}; mp::VMImage image; - EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, ""); @@ -212,8 +232,13 @@ TEST_F(LXDImageVault, returns_expected_info_with_valid_remote) base_url, cache_dir.path(), mp::days{0}}; mp::VMImage image; - EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_EQ(image.id, mpt::default_id); EXPECT_EQ(image.original_release, "18.04 LTS"); @@ -241,11 +266,17 @@ TEST_F(LXDImageVault, throws_with_invalid_alias) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()), - std::runtime_error, - mpt::match_what(StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", - alias, "release")))); + MP_EXPECT_THROW_THAT( + image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()), + std::runtime_error, + mpt::match_what( + StrEq(fmt::format("Unable to find an image matching \"{}\" in remote \"{}\".", alias, "release")))); } TEST_F(LXDImageVault, throws_with_invalid_remote) @@ -260,8 +291,13 @@ TEST_F(LXDImageVault, throws_with_invalid_remote) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()), + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()), std::runtime_error, mpt::match_what(HasSubstr(fmt::format("Remote \'{}\' is not found.", remote)))); } @@ -290,8 +326,13 @@ TEST_F(LXDImageVault, does_not_download_if_image_exists) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); } TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) @@ -327,8 +368,13 @@ TEST_F(LXDImageVault, instance_exists_missing_image_does_not_download_image) base_url, cache_dir.path(), mp::days{0}}; mp::VMImage image; - EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, - false, std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image = image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_FALSE(download_requested); EXPECT_EQ(image.original_release, mpt::default_release_info); } @@ -354,8 +400,13 @@ TEST_F(LXDImageVault, requests_download_if_image_does_not_exist) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); EXPECT_TRUE(download_requested); } @@ -382,8 +433,13 @@ TEST_F(LXDImageVault, sets_fingerprint_with_hash_query) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "e3b0c44298fc1c1", false, "release", mp::Query::Type::Alias}; - EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path())); + EXPECT_NO_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path())); } TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) @@ -421,8 +477,13 @@ TEST_F(LXDImageVault, download_deletes_and_throws_on_cancel) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, - std::nullopt, save_dir.path()), + EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + monitor, + false, + std::nullopt, + save_dir.path()), mp::AbortedDownloadException); EXPECT_TRUE(delete_requested); @@ -459,8 +520,13 @@ TEST_F(LXDImageVault, percent_complete_returns_negative_on_metadata_download) mp::LXDVMImageVault image_vault{hosts, &stub_url_downloader, mock_network_access_manager.get(), base_url, cache_dir.path(), mp::days{0}}; - EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, monitor, false, - std::nullopt, save_dir.path()), + EXPECT_THROW(image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + monitor, + false, + std::nullopt, + save_dir.path()), mp::AbortedDownloadException); } @@ -834,8 +900,13 @@ TEST_F(LXDImageVault, custom_image_found_returns_expected_info) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "snapcraft", false, "release", mp::Query::Type::Alias}; - auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()); EXPECT_EQ(image.id, mpt::lxd_snapcraft_image_id); EXPECT_EQ(image.original_release, mpt::snapcraft_release_info); @@ -892,8 +963,13 @@ TEST_F(LXDImageVault, custom_image_downloads_and_creates_correct_upload) base_url, cache_dir.path(), mp::days{0}}; const mp::Query query{"", "custom", false, "release", mp::Query::Type::Alias}; - auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()); EXPECT_EQ(image.id, mpt::lxd_custom_image_id); EXPECT_EQ(image.original_release, mpt::custom_release_info); @@ -917,8 +993,13 @@ TEST_F(LXDImageVault, fetch_image_unable_to_connect_logs_error_and_returns_blank mpt::MockLogger::make_cstring_matcher( StrEq(fmt::format("{} - returning blank image info", exception_message))))); - auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()); EXPECT_TRUE(image.id.empty()); EXPECT_TRUE(image.original_release.empty()); @@ -1045,8 +1126,13 @@ TEST_F(LXDImageVault, http_based_image_downloads_and_creates_correct_upload) const std::string download_url{"http://www.foo.com/images/foo.img"}; const mp::Query query{"", download_url, false, "", mp::Query::Type::HttpDownload}; - auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1111,8 +1197,13 @@ TEST_F(LXDImageVault, file_based_fetch_copies_image_and_returns_expected_info) auto current_time = QDateTime::currentDateTime(); const mp::Query query{"", file.url().toStdString(), false, "", mp::Query::Type::LocalFile}; - auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()); + auto image = image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()); EXPECT_EQ(image.id, "bc5a973bd6f2bef30658fb51177cf5e506c1d60958a4c97813ee26416dc368da"); @@ -1133,8 +1224,13 @@ TEST_F(LXDImageVault, invalid_local_file_image_throws) const std::string missing_file{"/foo"}; const mp::Query query{"", fmt::format("file://{}", missing_file), false, "", mp::Query::Type::LocalFile}; - MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, save_dir.path()), + MP_EXPECT_THROW_THAT(image_vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + save_dir.path()), std::runtime_error, mpt::match_what(StrEq(fmt::format("Custom image `{}` does not exist.", missing_file)))); } diff --git a/tests/lxd/test_lxd_mount_handler.cpp b/tests/lxd/test_lxd_mount_handler.cpp index 8c6608c79da..abb12969605 100644 --- a/tests/lxd/test_lxd_mount_handler.cpp +++ b/tests/lxd/test_lxd_mount_handler.cpp @@ -168,8 +168,13 @@ TEST_F(LXDMountHandlerTestFixture, stopThrowsIfVMIsRunning) TEST_P(LXDMountHandlerInvalidGidUidParameterTests, mountWithGidOrUid) { mpt::TempDir instance_dir{}; - mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, - bridge_name, default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine lxd_vm{default_description, + stub_monitor, + &mock_network_access_manager, + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; const auto& [host_gid, instance_gid, host_uid, instance_uid] = GetParam(); const mp::VMMount vm_mount{ source_path, {{host_gid, instance_gid}}, {{host_uid, instance_uid}}, mp::VMMount::MountType::Native}; @@ -186,8 +191,13 @@ INSTANTIATE_TEST_SUITE_P(mountWithGidOrUidInstantiation, LXDMountHandlerInvalidG TEST_P(LXDMountHandlerValidGidUidParameterTests, mountWithGidOrUid) { mpt::TempDir instance_dir{}; - mp::LXDVirtualMachine lxd_vm{default_description, stub_monitor, &mock_network_access_manager, base_url, - bridge_name, default_storage_pool, instance_dir.path()}; + mp::LXDVirtualMachine lxd_vm{default_description, + stub_monitor, + &mock_network_access_manager, + base_url, + bridge_name, + default_storage_pool, + instance_dir.path()}; const auto& [host_gid, host_uid] = GetParam(); const int default_instance_id = -1; const mp::VMMount vm_mount{source_path, diff --git a/tests/mock_client_rpc.h b/tests/mock_client_rpc.h index a8d9c93c06a..35da28d0b3d 100644 --- a/tests/mock_client_rpc.h +++ b/tests/mock_client_rpc.h @@ -188,18 +188,30 @@ class MockRpcStub : public multipass::Rpc::StubInterface (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), PrepareAsyncauthenticateRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); - MOCK_METHOD((grpc::ClientReaderWriterInterface*), snapshotRaw, - (grpc::ClientContext * context), (override)); + MOCK_METHOD((grpc::ClientReaderWriterInterface*), + snapshotRaw, + (grpc::ClientContext * context), + (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), - AsyncsnapshotRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), (override)); + AsyncsnapshotRaw, + (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), + (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), - PrepareAsyncsnapshotRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); - MOCK_METHOD((grpc::ClientReaderWriterInterface*), restoreRaw, - (grpc::ClientContext * context), (override)); + PrepareAsyncsnapshotRaw, + (grpc::ClientContext * context, grpc::CompletionQueue* cq), + (override)); + MOCK_METHOD((grpc::ClientReaderWriterInterface*), + restoreRaw, + (grpc::ClientContext * context), + (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), - AsyncrestoreRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), (override)); + AsyncrestoreRaw, + (grpc::ClientContext * context, grpc::CompletionQueue* cq, void* tag), + (override)); MOCK_METHOD((grpc::ClientAsyncReaderWriterInterface*), - PrepareAsyncrestoreRaw, (grpc::ClientContext * context, grpc::CompletionQueue* cq), (override)); + PrepareAsyncrestoreRaw, + (grpc::ClientContext * context, grpc::CompletionQueue* cq), + (override)); }; } // namespace multipass::test diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index a012b47b325..15b7edace65 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -71,14 +71,18 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, update_cpus, (int), (override)); MOCK_METHOD(void, resize_memory, (const MemorySize&), (override)); MOCK_METHOD(void, resize_disk, (const MemorySize&), (override)); - MOCK_METHOD(std::unique_ptr, make_native_mount_handler, - (const SSHKeyProvider*, const std::string&, const VMMount&), (override)); + MOCK_METHOD(std::unique_ptr, + make_native_mount_handler, + (const SSHKeyProvider*, const std::string&, const VMMount&), + (override)); MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); MOCK_METHOD(int, get_num_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (override)); - MOCK_METHOD(std::shared_ptr, take_snapshot, - (const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(std::shared_ptr, + take_snapshot, + (const VMSpecs&, const std::string&, const std::string&), + (override)); MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (), (override)); diff --git a/tests/mock_vm_image_vault.h b/tests/mock_vm_image_vault.h index 435319f3e0e..0c85d83de5c 100644 --- a/tests/mock_vm_image_vault.h +++ b/tests/mock_vm_image_vault.h @@ -43,9 +43,15 @@ class MockVMImageVault : public VMImageVault ON_CALL(*this, minimum_image_size_for(_)).WillByDefault(Return(MemorySize{"1048576"})); }; - MOCK_METHOD(VMImage, fetch_image, - (const FetchType&, const Query&, const PrepareAction&, const ProgressMonitor&, const bool, - const std::optional&, const mp::Path&), + MOCK_METHOD(VMImage, + fetch_image, + (const FetchType&, + const Query&, + const PrepareAction&, + const ProgressMonitor&, + const bool, + const std::optional&, + const mp::Path&), (override)); MOCK_METHOD(void, remove, (const std::string&), (override)); MOCK_METHOD(bool, has_record_for, (const std::string&), (override)); diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 1e36d78d76f..33c2022a433 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -119,7 +119,8 @@ struct StubVirtualMachine final : public multipass::VirtualMachine { } - std::unique_ptr make_native_mount_handler(const SSHKeyProvider*, const std::string&, + std::unique_ptr make_native_mount_handler(const SSHKeyProvider*, + const std::string&, const VMMount&) override { return std::make_unique(); diff --git a/tests/stub_vm_image_vault.h b/tests/stub_vm_image_vault.h index 535390510a7..71f5ccfbf20 100644 --- a/tests/stub_vm_image_vault.h +++ b/tests/stub_vm_image_vault.h @@ -28,8 +28,12 @@ namespace test { struct StubVMImageVault final : public multipass::VMImageVault { - multipass::VMImage fetch_image(const multipass::FetchType&, const multipass::Query&, const PrepareAction& prepare, - const multipass::ProgressMonitor&, const bool, const std::optional&, + multipass::VMImage fetch_image(const multipass::FetchType&, + const multipass::Query&, + const PrepareAction& prepare, + const multipass::ProgressMonitor&, + const bool, + const std::optional&, const multipass::Path&) override { return prepare({dummy_image.name(), {}, {}, {}, {}, {}}); diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index f05e48dd7a0..d8ea96471f7 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -470,8 +470,10 @@ TEST_P(FormatterTestsuite, table) const std::string csv_head{"Alias,Instance,Command,Working directory,Context\n"}; -INSTANTIATE_TEST_SUITE_P(AliasDictionary, FormatterTestsuite, - Values(std::make_tuple(AliasesVector{}, csv_head, +INSTANTIATE_TEST_SUITE_P(AliasDictionary, + FormatterTestsuite, + Values(std::make_tuple(AliasesVector{}, + csv_head, "{\n" " \"active-context\": \"default\",\n" " \"contexts\": {\n" diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index c2ac3c920e5..27b7e6c2646 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -120,7 +120,8 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine } protected: - std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, + std::shared_ptr make_specific_snapshot(const std::string& name, + const std::string& comment, const mp::VMSpecs& specs, std::shared_ptr parent) override { diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 6fbf77b54c7..83859b7b53f 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -136,11 +136,13 @@ struct MockDaemonRpc : public mp::DaemonRpc (grpc::ServerContext * context, (grpc::ServerReaderWriter * server)), (override)); - MOCK_METHOD(grpc::Status, snapshot, + MOCK_METHOD(grpc::Status, + snapshot, (grpc::ServerContext * context, (grpc::ServerReaderWriter * server)), (override)); - MOCK_METHOD(grpc::Status, restore, + MOCK_METHOD(grpc::Status, + restore, (grpc::ServerContext * context, (grpc::ServerReaderWriter * server)), (override)); @@ -350,7 +352,8 @@ struct Client : public Test } template - auto check_request_and_return(const Matcher& matcher, const grpc::Status& status, + auto check_request_and_return(const Matcher& matcher, + const grpc::Status& status, const ReplyType& reply = ReplyType{}) { return [&matcher, &status, reply = std::move(reply)](grpc::ServerReaderWriter* server) { diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index 3b815841728..23ea05aa987 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -178,8 +178,13 @@ struct ImageVault : public testing::Test TEST_F(ImageVault, downloads_image) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_TRUE(url_downloader.downloaded_urls.contains(host.image.url())); @@ -188,8 +193,13 @@ TEST_F(ImageVault, downloads_image) TEST_F(ImageVault, returned_image_contains_instance_name) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_TRUE(vm_image.image_path.contains(QString::fromStdString(instance_name))); } @@ -203,8 +213,13 @@ TEST_F(ImageVault, calls_prepare) prepare_called = true; return source_image; }; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_TRUE(prepare_called); } @@ -217,10 +232,20 @@ TEST_F(ImageVault, records_instanced_images) ++prepare_called_count; return source_image; }; - auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); - auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); + auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -236,13 +261,23 @@ TEST_F(ImageVault, caches_prepared_images) ++prepare_called_count; return source_image; }; - auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image1 = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; - auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, - std::nullopt, save_dir.filePath(QString::fromStdString(another_query.name))); + auto vm_image2 = vault.fetch_image(mp::FetchType::ImageOnly, + another_query, + prepare, + stub_monitor, + false, + std::nullopt, + save_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -260,12 +295,22 @@ TEST_F(ImageVault, remembers_instance_images) }; mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -281,15 +326,24 @@ TEST_F(ImageVault, remembers_prepared_images) }; mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); auto another_query = default_query; another_query.name = "valley-pied-piper-chat"; mp::DefaultVMImageVault another_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image2 = - another_vault.fetch_image(mp::FetchType::ImageOnly, another_query, prepare, stub_monitor, false, std::nullopt, - save_dir.filePath(QString::fromStdString(another_query.name))); + auto vm_image2 = another_vault.fetch_image(mp::FetchType::ImageOnly, + another_query, + prepare, + stub_monitor, + false, + std::nullopt, + save_dir.filePath(QString::fromStdString(another_query.name))); EXPECT_THAT(url_downloader.downloaded_files.size(), Eq(1)); EXPECT_THAT(prepare_called_count, Eq(1)); @@ -310,8 +364,13 @@ TEST_F(ImageVault, uses_image_from_prepare) }; mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); const auto image_data = mp::utils::contents_of(vm_image.image_path); EXPECT_THAT(image_data, StrEq(expected_data)); @@ -329,8 +388,13 @@ TEST_F(ImageVault, image_purged_expired) mpt::make_file_with_content(file_name); return {file_name, source_image.id, "", "", "", {}}; }; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -350,8 +414,13 @@ TEST_F(ImageVault, image_exists_not_expired) mpt::make_file_with_content(file_name); return {file_name, source_image.id, "", "", "", {}}; }; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); EXPECT_TRUE(QFileInfo::exists(file_name)); @@ -386,7 +455,12 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_fetch_copies_image_an query.release = file.url().toStdString(); query.query_type = mp::Query::Type::LocalFile; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir); EXPECT_TRUE(QFileInfo::exists(vm_image.image_path)); @@ -401,7 +475,12 @@ TEST_F(ImageVault, invalid_custom_image_file_throws) query.release = "file://foo"; query.query_type = mp::Query::Type::LocalFile; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir), std::runtime_error); } @@ -424,8 +503,13 @@ TEST_F(ImageVault, missing_downloaded_image_throws) { mpt::StubURLDownloader stub_url_downloader; mp::DefaultVMImageVault vault{hosts, &stub_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir), mp::CreateImageException); } @@ -433,8 +517,13 @@ TEST_F(ImageVault, hash_mismatch_throws) { BadURLDownloader bad_url_downloader; mp::DefaultVMImageVault vault{hosts, &bad_url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir), mp::CreateImageException); } @@ -446,7 +535,12 @@ TEST_F(ImageVault, invalid_remote_throws) query.remote_name = "foo"; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir), std::runtime_error); } @@ -459,7 +553,12 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(invalid_image_alias_throw)) query.release = "foo"; - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir), mp::CreateImageException); } @@ -473,8 +572,13 @@ TEST_F(ImageVault, valid_remote_and_alias_returns_valid_image_info) query.remote_name = "release"; mp::VMImage image; - EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir)); + EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir)); EXPECT_THAT(image.original_release, Eq("18.04 LTS")); EXPECT_THAT(image.id, Eq("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); @@ -489,8 +593,13 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i mp::Query query{instance_name, image_url, false, "", mp::Query::Type::HttpDownload}; mp::VMImage image; - EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir)); + EXPECT_NO_THROW(image = vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir)); // Hash is based on image url EXPECT_THAT(image.id, Eq("7404f51c9b4f40312fa048a0ad36e07b74b718a2d3a5a08e8cca906c69059ddf")); @@ -500,7 +609,12 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(http_download_returns_expected_i TEST_F(ImageVault, image_update_creates_new_dir_and_removes_old) { mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir); auto original_file{url_downloader.downloaded_files[0]}; @@ -532,8 +646,13 @@ TEST_F(ImageVault, aborted_download_throws) running_url_downloader.abort_all_downloads(); - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir), mp::AbortedDownloadException); } @@ -545,8 +664,13 @@ TEST_F(ImageVault, minimum_image_size_returns_expected_size) auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -567,7 +691,12 @@ TEST_F(ImageVault, DISABLE_ON_WINDOWS_AND_MACOS(file_based_minimum_size_returns_ query.release = file.url().toStdString(); query.query_type = mp::Query::Type::LocalFile; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir); const auto size = vault.minimum_image_size_for(vm_image.id); @@ -591,8 +720,13 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_crashes) auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("with output")))); @@ -605,8 +739,13 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_cannot_find_the_i auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(AllOf(HasSubstr("qemu-img failed"), HasSubstr("Could not find")))); @@ -619,8 +758,13 @@ TEST_F(ImageVault, minimum_image_size_throws_when_qemuimg_info_does_not_understa auto mock_factory_scope = inject_fake_qemuimg_callback(qemuimg_exit_status, qemuimg_output); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; - auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir); + auto vm_image = vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir); MP_EXPECT_THROW_THAT(vault.minimum_image_size_for(vm_image.id), std::runtime_error, mpt::match_what(HasSubstr("Could not obtain image's virtual size"))); @@ -691,7 +835,12 @@ TEST_F(ImageVault, updateImagesLogsWarningOnUnsupportedImage) { mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir); EXPECT_CALL(host, info_for(_)).WillOnce(Throw(mp::UnsupportedImageException(default_query.release))); @@ -709,7 +858,12 @@ TEST_F(ImageVault, updateImagesLogsWarningOnEmptyVault) { mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(mpl::Level::warning); mp::DefaultVMImageVault vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{1}}; - vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, std::nullopt, + vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir); EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); @@ -730,8 +884,13 @@ TEST_F(ImageVault, fetchLocalImageThrowsOnEmptyVault) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, default_query, stub_prepare, stub_monitor, false, - std::nullopt, instance_dir), + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + default_query, + stub_prepare, + stub_monitor, + false, + std::nullopt, + instance_dir), mp::ImageNotFoundException); } @@ -742,7 +901,12 @@ TEST_F(ImageVault, fetchRemoteImageThrowsOnMissingKernel) EXPECT_CALL(host, info_for(_)).WillOnce(Return(std::nullopt)); - EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, query, stub_prepare, stub_monitor, false, std::nullopt, + EXPECT_THROW(vault.fetch_image(mp::FetchType::ImageOnly, + query, + stub_prepare, + stub_monitor, + false, + std::nullopt, instance_dir), mp::ImageNotFoundException); } diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 0c359607d41..7c55444c19f 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -813,30 +813,35 @@ const auto multiple_mixed_instances_and_snapshots_info_reply = const std::vector orderable_list_info_formatter_outputs{ {&table_formatter, &empty_list_reply, "No instances found.\n", "table_list_empty"}, {&table_formatter, &empty_list_snapshot_reply, "No snapshots found.\n", "table_list_snapshot_empty"}, - {&table_formatter, &single_instance_list_reply, + {&table_formatter, + &single_instance_list_reply, "Name State IPv4 Image\n" "foo Running 10.168.32.2 Ubuntu 16.04 LTS\n" " 200.3.123.30\n", "table_list_single"}, - {&table_formatter, &multiple_instances_list_reply, + {&table_formatter, + &multiple_instances_list_reply, "Name State IPv4 Image\n" "bogus-instance Running 10.21.124.56 Ubuntu 16.04 LTS\n" "bombastic Stopped -- Ubuntu 18.04 LTS\n", "table_list_multiple"}, - {&table_formatter, &unsorted_list_reply, + {&table_formatter, + &unsorted_list_reply, "Name State IPv4 Image\n" "trusty-190611-1529 Deleted -- Not Available\n" "trusty-190611-1535 Stopped -- Ubuntu N/A\n" "trusty-190611-1539 Suspended -- Not Available\n" "trusty-190611-1542 Running -- Ubuntu N/A\n", "table_list_unsorted"}, - {&table_formatter, &single_snapshot_list_reply, + {&table_formatter, + &single_snapshot_list_reply, "Instance Snapshot Parent Comment\n" "foo snapshot1 -- This is a sample comment\n", "table_list_single_snapshot"}, - {&table_formatter, &multiple_snapshots_list_reply, + {&table_formatter, + &multiple_snapshots_list_reply, "Instance Snapshot Parent Comment\n" "hale-roller pristine -- A first snapshot\n" "hale-roller rocking pristine A very long comment that should be truncated by t…\n" @@ -846,7 +851,8 @@ const std::vector orderable_list_info_formatter_outputs{ "table_list_multiple_snapshots"}, {&table_formatter, &empty_info_reply, "\n", "table_info_empty"}, - {&table_formatter, &single_instance_info_reply, + {&table_formatter, + &single_instance_info_reply, "Name: foo\n" "State: Running\n" "Snapshots: 0\n" @@ -867,7 +873,8 @@ const std::vector orderable_list_info_formatter_outputs{ " UID map: 1000:1000\n" " GID map: 1000:1000\n", "table_info_single_instance"}, - {&table_formatter, &multiple_instances_info_reply, + {&table_formatter, + &multiple_instances_info_reply, "Name: bogus-instance\n" "State: Running\n" "Snapshots: 1\n" @@ -893,7 +900,8 @@ const std::vector orderable_list_info_formatter_outputs{ "Memory usage: --\n" "Mounts: --\n", "table_info_multiple_instances"}, - {&table_formatter, &single_snapshot_info_reply, + {&table_formatter, + &single_snapshot_info_reply, "Snapshot: snapshot2\n" "Instance: bogus-instance\n" "Size: 128MiB\n" @@ -910,7 +918,8 @@ const std::vector orderable_list_info_formatter_outputs{ " new\r\n" " lines.\n", "table_info_single_snapshot"}, - {&table_formatter, &multiple_snapshots_info_reply, + {&table_formatter, + &multiple_snapshots_info_reply, "Snapshot: snapshot2\n" "Instance: bogus-instance\n" "CPU(s): 2\n" @@ -934,7 +943,8 @@ const std::vector orderable_list_info_formatter_outputs{ "Children: --\n" "Comment: Captured by EHT\n", "table_info_multiple_snapshots"}, - {&table_formatter, &mixed_instance_and_snapshot_info_reply, + {&table_formatter, + &mixed_instance_and_snapshot_info_reply, "Name: bombastic\n" "State: Stopped\n" "Snapshots: 3\n" @@ -959,7 +969,8 @@ const std::vector orderable_list_info_formatter_outputs{ " snapshot4\n" "Comment: --\n", "table_info_mixed_instance_and_snapshot"}, - {&table_formatter, &multiple_mixed_instances_and_snapshots_info_reply, + {&table_formatter, + &multiple_mixed_instances_and_snapshots_info_reply, "Name: bogus-instance\n" "State: Running\n" "Snapshots: 2\n" @@ -1019,16 +1030,19 @@ const std::vector orderable_list_info_formatter_outputs{ "table_info_multiple_mixed_instances_and_snapshots"}, {&csv_formatter, &empty_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n", "csv_list_empty"}, - {&csv_formatter, &single_instance_list_reply, + {&csv_formatter, + &single_instance_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n" "foo,Running,10.168.32.2,fdde:2681:7a2::4ca,Ubuntu 16.04 LTS,\"10.168.32.2,200.3.123.30\"\n", "csv_list_single"}, - {&csv_formatter, &multiple_instances_list_reply, + {&csv_formatter, + &multiple_instances_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n" "bogus-instance,Running,10.21.124.56,,Ubuntu 16.04 LTS,\"10.21.124.56\"\n" "bombastic,Stopped,,,Ubuntu 18.04 LTS,\"\"\n", "csv_list_multiple"}, - {&csv_formatter, &unsorted_list_reply, + {&csv_formatter, + &unsorted_list_reply, "Name,State,IPv4,IPv6,Release,AllIPv4\n" "trusty-190611-1529,Deleted,,,Not Available,\"\"\n" "trusty-190611-1535,Stopped,,,Ubuntu N/A,\"\"\n" @@ -1036,9 +1050,12 @@ const std::vector orderable_list_info_formatter_outputs{ "trusty-190611-1542,Running,,,Ubuntu N/A,\"\"\n", "csv_list_unsorted"}, {&csv_formatter, &empty_list_snapshot_reply, "Instance,Snapshot,Parent,Comment\n", "csv_list_snapshot_empty"}, - {&csv_formatter, &single_snapshot_list_reply, - "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,\"This is a sample comment\"\n", "csv_list_single_snapshot"}, - {&csv_formatter, &multiple_snapshots_list_reply, + {&csv_formatter, + &single_snapshot_list_reply, + "Instance,Snapshot,Parent,Comment\nfoo,snapshot1,,\"This is a sample comment\"\n", + "csv_list_single_snapshot"}, + {&csv_formatter, + &multiple_snapshots_list_reply, "Instance,Snapshot,Parent,Comment\nhale-roller,pristine,,\"A first " "snapshot\"\nhale-roller,rocking,pristine,\"A very long comment that should be truncated by the table " "formatter\"\nhale-roller,rolling,pristine,\"Loaded with stuff\"\nprosperous-spadefish,snapshot2,,\"Before " @@ -1047,7 +1064,8 @@ const std::vector orderable_list_info_formatter_outputs{ "csv_list_multiple_snapshots"}, {&csv_formatter, &empty_info_reply, "", "csv_info_empty"}, - {&csv_formatter, &single_instance_info_reply, + {&csv_formatter, + &single_instance_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nfoo,Running,10.168.32.2,2001:67c:1562:8007::aac:423a,Ubuntu " "16.04.3 " @@ -1055,21 +1073,24 @@ const std::vector orderable_list_info_formatter_outputs{ "0.15,1288490188,5153960756,60817408,1503238554,/home/user/foo => foo;/home/user/test_dir " "=> test_dir,10.168.32.2;200.3.123.29,1,0\n", "csv_info_single_instance"}, - {&csv_formatter, &single_snapshot_info_reply, + {&csv_formatter, + &single_snapshot_info_reply, "Snapshot,Instance,CPU(s),Disk space,Memory " "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,2,4.9GiB,0.9GiB,/home/user/source " "=> " "source;/home/user => Home,1972-01-01T10:00:20.021Z,snapshot1,snapshot3;snapshot4,\"This is a comment with " "some\nnew\r\nlines.\"\n", "csv_info_single_snapshot_info_reply"}, - {&csv_formatter, &multiple_snapshots_info_reply, + {&csv_formatter, + &multiple_snapshots_info_reply, "Snapshot,Instance,CPU(s),Disk space,Memory " "size,Mounts,Created,Parent,Children,Comment\nsnapshot2,bogus-instance,2,4.9GiB,0.9GiB,/home/user/source => " "source;/home/user => " "Home,1972-01-01T10:00:20.021Z,snapshot1,snapshot3;snapshot4,\"\"\nblack-hole,messier-87,1,1024GiB,128GiB,," "2019-04-10T11:59:59Z,,,\"Captured by EHT\"\n", "csv_info_multiple_snapshot_info_reply"}, - {&csv_formatter, &multiple_instances_info_reply, + {&csv_formatter, + &multiple_instances_info_reply, "Name,State,Ipv4,Ipv6,Release,Image hash,Image release,Load,Disk usage,Disk total,Memory " "usage,Memory total,Mounts,AllIPv4,CPU(s),Snapshots\nbogus-instance,Running,10.21.124.56,,Ubuntu 16.04.3 " "LTS,1797c5c82016c1e65f4008fcf89deae3a044ef76087a9ec5b907c6d64a3609ac,16.04 LTS,0.03 0.10 " @@ -1079,7 +1100,8 @@ const std::vector orderable_list_info_formatter_outputs{ "csv_info_multiple_instances"}, {&yaml_formatter, &empty_list_reply, "\n", "yaml_list_empty"}, - {&yaml_formatter, &single_instance_list_reply, + {&yaml_formatter, + &single_instance_list_reply, "foo:\n" " - state: Running\n" " ipv4:\n" @@ -1087,7 +1109,8 @@ const std::vector orderable_list_info_formatter_outputs{ " - 200.3.123.30\n" " release: Ubuntu 16.04 LTS\n", "yaml_list_single"}, - {&yaml_formatter, &multiple_instances_list_reply, + {&yaml_formatter, + &multiple_instances_list_reply, "bogus-instance:\n" " - state: Running\n" " ipv4:\n" @@ -1099,7 +1122,8 @@ const std::vector orderable_list_info_formatter_outputs{ " []\n" " release: Ubuntu 18.04 LTS\n", "yaml_list_multiple"}, - {&yaml_formatter, &unsorted_list_reply, + {&yaml_formatter, + &unsorted_list_reply, "trusty-190611-1529:\n" " - state: Deleted\n" " ipv4:\n" @@ -1122,13 +1146,15 @@ const std::vector orderable_list_info_formatter_outputs{ " release: Ubuntu N/A\n", "yaml_list_unsorted"}, {&yaml_formatter, &empty_list_snapshot_reply, "\n", "yaml_list_snapshot_empty"}, - {&yaml_formatter, &single_snapshot_list_reply, + {&yaml_formatter, + &single_snapshot_list_reply, "foo:\n" " - snapshot1:\n" " - parent: ~\n" " comment: This is a sample comment\n", "yaml_list_single_snapshot"}, - {&yaml_formatter, &multiple_snapshots_list_reply, + {&yaml_formatter, + &multiple_snapshots_list_reply, "hale-roller:\n" " - pristine:\n" " - parent: ~\n" @@ -1149,7 +1175,8 @@ const std::vector orderable_list_info_formatter_outputs{ "yaml_list_multiple_snapshots"}, {&yaml_formatter, &empty_info_reply, "errors:\n - ~\n", "yaml_info_empty"}, - {&yaml_formatter, &single_instance_info_reply, + {&yaml_formatter, + &single_instance_info_reply, "errors:\n" " - ~\n" "foo:\n" @@ -1187,7 +1214,8 @@ const std::vector orderable_list_info_formatter_outputs{ " - \"1000:1000\"\n" " source_path: /home/user/test_dir\n", "yaml_info_single_instance"}, - {&yaml_formatter, &multiple_instances_info_reply, + {&yaml_formatter, + &multiple_instances_info_reply, "errors:\n" " - ~\n" "bogus-instance:\n" @@ -1235,7 +1263,8 @@ const std::vector orderable_list_info_formatter_outputs{ " []\n" " mounts: ~\n", "yaml_info_multiple_instances"}, - {&yaml_formatter, &single_snapshot_info_reply, + {&yaml_formatter, + &single_snapshot_info_reply, "errors:\n" " - ~\n" "bogus-instance:\n" @@ -1257,7 +1286,8 @@ const std::vector orderable_list_info_formatter_outputs{ " - snapshot4\n" " comment: \"This is a comment with some\\nnew\\r\\nlines.\"\n", "yaml_info_single_snapshot_info_reply"}, - {&yaml_formatter, &multiple_snapshots_info_reply, + {&yaml_formatter, + &multiple_snapshots_info_reply, "errors:\n" " - ~\n" "bogus-instance:\n" @@ -1292,7 +1322,8 @@ const std::vector orderable_list_info_formatter_outputs{ " []\n" " comment: Captured by EHT\n", "yaml_info_multiple_snapshots_info_reply"}, - {&yaml_formatter, &mixed_instance_and_snapshot_info_reply, + {&yaml_formatter, + &mixed_instance_and_snapshot_info_reply, "errors:\n" " - ~\n" "bombastic:\n" @@ -1331,7 +1362,8 @@ const std::vector orderable_list_info_formatter_outputs{ " - snapshot4\n" " comment: ~\n", "yaml_info_mixed_instance_and_snapshot_info_reply"}, - {&yaml_formatter, &multiple_mixed_instances_and_snapshots_info_reply, + {&yaml_formatter, + &multiple_mixed_instances_and_snapshots_info_reply, "errors:\n" " - ~\n" "bogus-instance:\n" @@ -1422,13 +1454,15 @@ const std::vector orderable_list_info_formatter_outputs{ "yaml_info_multiple_mixed_instances_and_snapshots"}}; const std::vector non_orderable_list_info_formatter_outputs{ - {&json_formatter, &empty_list_reply, + {&json_formatter, + &empty_list_reply, "{\n" " \"list\": [\n" " ]\n" "}\n", "json_list_empty"}, - {&json_formatter, &single_instance_list_reply, + {&json_formatter, + &single_instance_list_reply, "{\n" " \"list\": [\n" " {\n" @@ -1443,7 +1477,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " ]\n" "}\n", "json_list_single"}, - {&json_formatter, &multiple_instances_list_reply, + {&json_formatter, + &multiple_instances_list_reply, "{\n" " \"list\": [\n" " {\n" @@ -1464,7 +1499,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " ]\n" "}\n", "json_list_multiple"}, - {&json_formatter, &single_snapshot_list_reply, + {&json_formatter, + &single_snapshot_list_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1478,7 +1514,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_list_single_snapshot"}, - {&json_formatter, &multiple_snapshots_list_reply, + {&json_formatter, + &multiple_snapshots_list_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1510,7 +1547,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_list_multiple_snapshots"}, - {&json_formatter, &empty_info_reply, + {&json_formatter, + &empty_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1518,7 +1556,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_empty"}, - {&json_formatter, &single_instance_info_reply, + {&json_formatter, + &single_instance_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1573,7 +1612,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_single_instance"}, - {&json_formatter, &multiple_instances_info_reply, + {&json_formatter, + &multiple_instances_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1638,7 +1678,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_multiple_instances"}, - {&json_formatter, &single_snapshot_info_reply, + {&json_formatter, + &single_snapshot_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1671,7 +1712,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_single_snapshot_info_reply"}, - {&json_formatter, &multiple_snapshots_info_reply, + {&json_formatter, + &multiple_snapshots_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1721,7 +1763,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_multiple_snapshots_info_reply"}, - {&json_formatter, &mixed_instance_and_snapshot_info_reply, + {&json_formatter, + &mixed_instance_and_snapshot_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1774,7 +1817,8 @@ const std::vector non_orderable_list_info_formatter_outputs{ " }\n" "}\n", "json_info_mixed_instance_and_snapshot_info_reply"}, - {&json_formatter, &multiple_mixed_instances_and_snapshots_info_reply, + {&json_formatter, + &multiple_mixed_instances_and_snapshots_info_reply, "{\n" " \"errors\": [\n" " ],\n" @@ -1895,47 +1939,56 @@ const std::vector non_orderable_list_info_formatter_outputs{ const std::vector non_orderable_networks_formatter_outputs{ {&table_formatter, &empty_networks_reply, "No network interfaces found.\n", "table_networks_empty"}, - {&table_formatter, &one_short_line_networks_reply, + {&table_formatter, + &one_short_line_networks_reply, "Name Type Description\n" "en0 eth Ether\n", "table_networks_one_short_line"}, - {&table_formatter, &one_long_line_networks_reply, + {&table_formatter, + &one_long_line_networks_reply, "Name Type Description\n" "enp3s0 ethernet Amazingly fast and robust ethernet adapter\n", "table_networks_one_long_line"}, - {&table_formatter, &multiple_lines_networks_reply, + {&table_formatter, + &multiple_lines_networks_reply, "Name Type Description\n" "en0 eth Ether\n" "wlx0123456789ab wifi Wireless\n", "table_networks_multiple_lines"}, {&csv_formatter, &empty_networks_reply, "Name,Type,Description\n", "csv_networks_empty"}, - {&csv_formatter, &one_short_line_networks_reply, + {&csv_formatter, + &one_short_line_networks_reply, "Name,Type,Description\n" "en0,eth,\"Ether\"\n", "csv_networks_one_short_line"}, - {&csv_formatter, &one_long_line_networks_reply, + {&csv_formatter, + &one_long_line_networks_reply, "Name,Type,Description\n" "enp3s0,ethernet,\"Amazingly fast and robust ethernet adapter\"\n", "csv_networks_one_long_line"}, - {&csv_formatter, &multiple_lines_networks_reply, + {&csv_formatter, + &multiple_lines_networks_reply, "Name,Type,Description\n" "en0,eth,\"Ether\"\n" "wlx0123456789ab,wifi,\"Wireless\"\n", "csv_networks_multiple_lines"}, {&yaml_formatter, &empty_networks_reply, "\n", "yaml_networks_empty"}, - {&yaml_formatter, &one_short_line_networks_reply, + {&yaml_formatter, + &one_short_line_networks_reply, "en0:\n" " - type: eth\n" " description: Ether\n", "yaml_networks_one_short_line"}, - {&yaml_formatter, &one_long_line_networks_reply, + {&yaml_formatter, + &one_long_line_networks_reply, "enp3s0:\n" " - type: ethernet\n" " description: Amazingly fast and robust ethernet adapter\n", "yaml_networks_one_long_line"}, - {&yaml_formatter, &multiple_lines_networks_reply, + {&yaml_formatter, + &multiple_lines_networks_reply, "en0:\n" " - type: eth\n" " description: Ether\n" @@ -1944,13 +1997,15 @@ const std::vector non_orderable_networks_formatter_outputs{ " description: Wireless\n", "yaml_networks_multiple_lines"}, - {&json_formatter, &empty_networks_reply, + {&json_formatter, + &empty_networks_reply, "{\n" " \"list\": [\n" " ]\n" "}\n", "json_networks_empty"}, - {&json_formatter, &one_short_line_networks_reply, + {&json_formatter, + &one_short_line_networks_reply, "{\n" " \"list\": [\n" " {\n" @@ -1961,7 +2016,8 @@ const std::vector non_orderable_networks_formatter_outputs{ " ]\n" "}\n", "json_networks_one_short_line"}, - {&json_formatter, &one_long_line_networks_reply, + {&json_formatter, + &one_long_line_networks_reply, "{\n" " \"list\": [\n" " {\n" @@ -1972,7 +2028,8 @@ const std::vector non_orderable_networks_formatter_outputs{ " ]\n" "}\n", "json_networks_one_long_line"}, - {&json_formatter, &multiple_lines_networks_reply, + {&json_formatter, + &multiple_lines_networks_reply, "{\n" " \"list\": [\n" " {\n" @@ -2403,14 +2460,16 @@ TEST_P(PetenvFormatterSuite, pet_env_first_in_output) mp::InfoReply reply_copy; if (prepend) { - add_petenv_to_reply(reply_copy, dynamic_cast(formatter), + add_petenv_to_reply(reply_copy, + dynamic_cast(formatter), test_name.find("snapshot") != std::string::npos); reply_copy.MergeFrom(*input); } else { reply_copy.CopyFrom(*input); - add_petenv_to_reply(reply_copy, dynamic_cast(formatter), + add_petenv_to_reply(reply_copy, + dynamic_cast(formatter), test_name.find("snapshot") != std::string::npos); } output = formatter->format(reply_copy); From 6449a56a595e8cd41ece0567bcb7e93e1daa4dca Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 29 Sep 2023 09:14:58 -0500 Subject: [PATCH 431/627] [cli] hide --all option and allow empty args --- src/client/cli/cmd/info.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 0f3e2df9fbf..bce9cff095e 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -63,6 +63,7 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) "[.snapshot] [[.snapshot] ...]"); QCommandLineOption all_option(all_option_name, "Display info for all instances"); + all_option.setFlags(QCommandLineOption::HiddenFromHelp); QCommandLineOption noRuntimeInfoOption( "no-runtime-information", "Retrieve from the daemon only the information obtained without running commands on the instance"); @@ -81,7 +82,7 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) status = handle_format_option(parser, &chosen_formatter, cerr); - status = check_for_name_and_all_option_conflict(parser, cerr); + status = check_for_name_and_all_option_conflict(parser, cerr, true); if (status != ParseCode::Ok) return status; From ac9ec26b1e5dd3c21d5ac439831e737f30e12c46 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 29 Sep 2023 09:15:28 -0500 Subject: [PATCH 432/627] [bash] remove --all from bash completions --- completions/bash/multipass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 6e9aa70e08a..4b0493872f6 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -200,7 +200,7 @@ _multipass_complete() opts="${opts} --working-directory --no-map-working-directory" ;; "info") - _add_nonrepeating_args "--all --format" + _add_nonrepeating_args "--format" ;; "list"|"ls"|"networks"|"aliases") _add_nonrepeating_args "--format" From 0bc84b69dc08133626cf16690c680f6775fb27e7 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 29 Sep 2023 09:51:53 -0500 Subject: [PATCH 433/627] [tests] align tests with deprecated flag --- tests/test_cli_client.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 83859b7b53f..db3de12a9df 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -1718,9 +1718,10 @@ TEST_F(Client, help_cmd_help_ok) } // info cli tests -TEST_F(Client, info_cmd_fails_no_args) +TEST_F(Client, infoCmdOkNoArgs) { - EXPECT_THAT(send_command({"info"}), Eq(mp::ReturnCode::CommandLineError)); + EXPECT_CALL(mock_daemon, info(_, _)); + EXPECT_THAT(send_command({"info"}), Eq(mp::ReturnCode::Ok)); } TEST_F(Client, info_cmd_ok_with_one_arg) From f0d462f3183a6b8d1e11175104592973ead4e221 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 12:03:46 +0100 Subject: [PATCH 434/627] [cli] Signal deprecation of `info --all` --- src/client/cli/cmd/info.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index bce9cff095e..7d618b9ad3b 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -86,6 +86,10 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) if (status != ParseCode::Ok) return status; + if (parser->isSet(all_option_name)) + cerr << "Warning: the `--all` flag for the `info` command is deprecated. Please use `info` with no positional " + "arguments for the same effect.\n"; + bool instance_found = false, snapshot_found = false; for (const auto& item : add_instance_and_snapshot_names(parser)) { From 4e694a70dbd1733a265727db362d195e072472bd Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 17:09:59 +0100 Subject: [PATCH 435/627] [cli] Update messages in launch for consistency Start "error" and "warning" messages in `launch` with a capital, and send them to cerr rather than cout. This makes it consistent with `info`. --- src/client/cli/cmd/launch.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/client/cli/cmd/launch.cpp b/src/client/cli/cmd/launch.cpp index 6d8a76f2813..baca36117d4 100644 --- a/src/client/cli/cmd/launch.cpp +++ b/src/client/cli/cmd/launch.cpp @@ -318,7 +318,7 @@ mp::ParseCode cmd::Launch::parse_args(mp::ArgParser* parser) if (!conversion_pass || cpu_count < 1) { - fmt::print(cerr, "error: Invalid CPU count '{}', need a positive integer value.\n", cpu_text); + fmt::print(cerr, "Error: invalid CPU count '{}', need a positive integer value.\n", cpu_text); return ParseCode::CommandLineError; } @@ -329,14 +329,14 @@ mp::ParseCode cmd::Launch::parse_args(mp::ArgParser* parser) { if (parser->isSet(memOption) && parser->isSet(memOptionDeprecated)) { - cerr << "error: Invalid option(s) used for memory allocation. Please use \"--memory\" to specify " - "amount of memory to allocate.\n"; + cerr << "Error: invalid option(s) used for memory allocation. Please use \"--memory\" to specify amount of " + "memory to allocate.\n"; return ParseCode::CommandLineError; } if (parser->isSet(memOptionDeprecated)) - cout << "warning: \"--mem\" long option will be deprecated in favour of \"--memory\" in a future release. " - "Please update any scripts, etc.\n"; + cerr << "Warning: the \"--mem\" long option is deprecated in favour of \"--memory\". Please update any " + "scripts, etc.\n"; auto arg_mem_size = parser->isSet(memOption) ? parser->value(memOption).toStdString() : parser->value(memOptionDeprecated).toStdString(); @@ -484,7 +484,8 @@ mp::ReturnCode cmd::Launch::request_launch(const ArgParser* parser) } if (warning_aliases.size()) - cout << fmt::format("Warning: unable to create {} {}.\n", warning_aliases.size() == 1 ? "alias" : "aliases", + cerr << fmt::format("Warning: unable to create {} {}.\n", + warning_aliases.size() == 1 ? "alias" : "aliases", fmt::join(warning_aliases, ", ")); for (const auto& workspace_to_be_created : reply.workspaces_to_be_created()) From 6fa606b4c77133edcc78bef96aa330d9fd64f786 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 18:02:17 +0100 Subject: [PATCH 436/627] [daemon] Register (null) handler for snapshot mod --- src/daemon/daemon.cpp | 16 ++++++++++++++-- src/daemon/daemon.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a46f4f64a0c..bad797605cf 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1231,6 +1231,14 @@ mp::SettingsHandler* register_instance_mod(std::unordered_map& operative_instances, + const std::unordered_map& deleted_instances, + const std::unordered_set& preparing_instances) +{ + return nullptr; // TODO@ricab +} + // Erase any outdated mount handlers for a given VM bool prune_obsolete_mounts(const std::unordered_map& mount_specs, std::unordered_map& vm_mounts) @@ -1341,7 +1349,8 @@ mp::Daemon::Daemon(std::unique_ptr the_config) mp::utils::backend_directory_path(config->cache_directory, config->factory->get_backend_directory_name()))}, daemon_rpc{config->server_address, *config->cert_provider, config->client_cert_store.get()}, instance_mod_handler{register_instance_mod(vm_instance_specs, operative_instances, deleted_instances, - preparing_instances, [this] { persist_instances(); })} + preparing_instances, [this] { persist_instances(); })}, + snapshot_mod_handler{register_snapshot_mod(operative_instances, deleted_instances, preparing_instances)} { connect_rpc(daemon_rpc, *this); std::vector invalid_specs; @@ -1495,7 +1504,10 @@ mp::Daemon::Daemon(std::unique_ptr the_config) mp::Daemon::~Daemon() { - mp::top_catch_all(category, [this] { MP_SETTINGS.unregister_handler(instance_mod_handler); }); + mp::top_catch_all(category, [this] { + MP_SETTINGS.unregister_handler(instance_mod_handler); + MP_SETTINGS.unregister_handler(snapshot_mod_handler); + }); } void mp::Daemon::create(const CreateRequest* request, diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 9b06b7cba4c..4d78fa29c6e 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -219,6 +219,7 @@ public slots: std::unordered_set preparing_instances; QFuture image_update_future; SettingsHandler* instance_mod_handler; + SettingsHandler* snapshot_mod_handler; std::unordered_map> mounts; }; } // namespace multipass From b1ec3154247423a3f83d668d8e03828a71c7d13a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 17:38:17 +0100 Subject: [PATCH 437/627] [tests] Fix tests to suit message updates --- tests/test_cli_client.cpp | 6 +++--- tests/test_daemon.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index db3de12a9df..b2aea98c88f 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -1031,11 +1031,11 @@ TEST_F(Client, launch_cmd_memory_fails_duplicate_options) TEST_F(Client, launch_cmd_memory_deprecated_option_warning) { - std::stringstream cout_stream; + std::stringstream cerr_stream; EXPECT_CALL(mock_daemon, launch(_, _)); - EXPECT_THAT(send_command({"launch", "--mem", "2048M"}, cout_stream, trash_stream), Eq(mp::ReturnCode::Ok)); - EXPECT_NE(std::string::npos, cout_stream.str().find("warning: \"--mem\"")) << "cout has: " << cout_stream.str(); + EXPECT_THAT(send_command({"launch", "--mem", "2048M"}, trash_stream, cerr_stream), Eq(mp::ReturnCode::Ok)); + EXPECT_NE(std::string::npos, cerr_stream.str().find("Warning: the \"--mem\"")) << "cout has: " << cerr_stream.str(); } TEST_F(Client, launch_cmd_cpu_option_ok) diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 3ec0bfe0b80..18201a4d368 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -1030,10 +1030,10 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotOverwriteAliasesIf config_builder.vault = std::move(mock_image_vault); mp::Daemon daemon{config_builder.build()}; - std::stringstream cout_stream; - send_command({"launch", name}, cout_stream); + std::stringstream cout_stream, cerr_stream; + send_command({"launch", name}, cout_stream, cerr_stream); - EXPECT_THAT(cout_stream.str(), HasSubstr("Warning: unable to create alias " + alias_name)); + EXPECT_THAT(cerr_stream.str(), HasSubstr("Warning: unable to create alias " + alias_name)); cout_stream.str(""); send_command({"aliases", "--format=csv"}, cout_stream); From 5aa192a8a123c97e38db3e295f7479ac39803539 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 17:55:24 +0100 Subject: [PATCH 438/627] [settings] Add bare-bones SnapshotSettingsHandler --- src/daemon/CMakeLists.txt | 1 + src/daemon/snapshot_settings_handler.cpp | 39 ++++++++++++++++++++ src/daemon/snapshot_settings_handler.h | 45 ++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/daemon/snapshot_settings_handler.cpp create mode 100644 src/daemon/snapshot_settings_handler.h diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 7b692da0137..cd555926205 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(daemon STATIC daemon_rpc.cpp default_vm_image_vault.cpp instance_settings_handler.cpp + snapshot_settings_handler.cpp ubuntu_image_host.cpp) include_directories(daemon diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp new file mode 100644 index 00000000000..b9b7b1da44c --- /dev/null +++ b/src/daemon/snapshot_settings_handler.cpp @@ -0,0 +1,39 @@ +/* + * 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 "snapshot_settings_handler.h" + +multipass::SnapshotSettingsHandler::SnapshotSettingsHandler( + std::unordered_map& operative_instances, + const std::unordered_map& deleted_instances, + const std::unordered_set& preparing_instances) +{ +} + +std::set multipass::SnapshotSettingsHandler::keys() const +{ + return std::set{}; +} + +QString multipass::SnapshotSettingsHandler::get(const QString& key) const +{ + return QString{}; +} + +void multipass::SnapshotSettingsHandler::set(const QString& key, const QString& val) +{ +} diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h new file mode 100644 index 00000000000..ea2c0522845 --- /dev/null +++ b/src/daemon/snapshot_settings_handler.h @@ -0,0 +1,45 @@ +/* + * 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_SNAPSHOT_SETTINGS_HANDLER_H +#define MULTIPASS_SNAPSHOT_SETTINGS_HANDLER_H + +#include +#include +#include +#include + +#include +#include +#include + +namespace multipass +{ +class SnapshotSettingsHandler : public SettingsHandler +{ +public: + SnapshotSettingsHandler(std::unordered_map& operative_instances, + const std::unordered_map& deleted_instances, + const std::unordered_set& preparing_instances); + + std::set keys() const override; + QString get(const QString& key) const override; + void set(const QString& key, const QString& val) override; +}; +} // namespace multipass + +#endif // MULTIPASS_SNAPSHOT_SETTINGS_HANDLER_H From b45d48f6f8a29280b64b451d243fa0b33b3dedf3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 18:02:43 +0100 Subject: [PATCH 439/627] [daemon] Register SnapshotSettingsHandler --- src/daemon/daemon.cpp | 4 +++- src/daemon/snapshot_settings_handler.cpp | 3 +++ src/daemon/snapshot_settings_handler.h | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index bad797605cf..e4f0ee4f755 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -18,6 +18,7 @@ #include "daemon.h" #include "base_cloud_init_config.h" #include "instance_settings_handler.h" +#include "snapshot_settings_handler.h" #include #include @@ -1236,7 +1237,8 @@ register_snapshot_mod(std::unordered_map const std::unordered_map& deleted_instances, const std::unordered_set& preparing_instances) { - return nullptr; // TODO@ricab + return MP_SETTINGS.register_handler( + std::make_unique(operative_instances, deleted_instances, preparing_instances)); } // Erase any outdated mount handlers for a given VM diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index b9b7b1da44c..f2a80efecab 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -21,6 +21,9 @@ multipass::SnapshotSettingsHandler::SnapshotSettingsHandler( std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, const std::unordered_set& preparing_instances) + : operative_instances{operative_instances}, + deleted_instances{deleted_instances}, + preparing_instances{preparing_instances} { } diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index ea2c0522845..58037018500 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -39,6 +39,12 @@ class SnapshotSettingsHandler : public SettingsHandler std::set keys() const override; QString get(const QString& key) const override; void set(const QString& key, const QString& val) override; + +private: + // references, careful + std::unordered_map& operative_instances; + const std::unordered_map& deleted_instances; + const std::unordered_set& preparing_instances; }; } // namespace multipass From 81174553199b900fad1ef093b23ceda0da00146e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 18:26:59 +0100 Subject: [PATCH 440/627] [settings] Rename field for consistency Rename `vm_instances` as `operative_instances` in `InstanceSettingsHandler` field/parameter/argument, for consistency with `Daemon` and `SnapshotSettingsHandler`. --- src/daemon/daemon.cpp | 4 ++-- src/daemon/instance_settings_handler.cpp | 6 +++--- src/daemon/instance_settings_handler.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e4f0ee4f755..355db4a54c2 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1223,13 +1223,13 @@ auto timeout_for(const int requested_timeout, const int blueprint_timeout) } mp::SettingsHandler* register_instance_mod(std::unordered_map& vm_instance_specs, - InstanceTable& vm_instances, + InstanceTable& operative_instances, const InstanceTable& deleted_instances, const std::unordered_set& preparing_instances, std::function instance_persister) { return MP_SETTINGS.register_handler(std::make_unique( - vm_instance_specs, vm_instances, deleted_instances, preparing_instances, std::move(instance_persister))); + vm_instance_specs, operative_instances, deleted_instances, preparing_instances, std::move(instance_persister))); } mp::SettingsHandler* diff --git a/src/daemon/instance_settings_handler.cpp b/src/daemon/instance_settings_handler.cpp index 8c8f54be3e1..f375f636b4f 100644 --- a/src/daemon/instance_settings_handler.cpp +++ b/src/daemon/instance_settings_handler.cpp @@ -163,11 +163,11 @@ mp::InstanceSettingsException::InstanceSettingsException(const std::string& reas mp::InstanceSettingsHandler::InstanceSettingsHandler( std::unordered_map& vm_instance_specs, - std::unordered_map& vm_instances, + std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, const std::unordered_set& preparing_instances, std::function instance_persister) : vm_instance_specs{vm_instance_specs}, - vm_instances{vm_instances}, + operative_instances{operative_instances}, deleted_instances{deleted_instances}, preparing_instances{preparing_instances}, instance_persister{std::move(instance_persister)} @@ -231,7 +231,7 @@ void mp::InstanceSettingsHandler::set(const QString& key, const QString& val) auto mp::InstanceSettingsHandler::modify_instance(const std::string& instance_name) -> VirtualMachine& { - auto ret = pick_instance(vm_instances, instance_name, Operation::Modify, deleted_instances); + auto ret = pick_instance(operative_instances, instance_name, Operation::Modify, deleted_instances); assert(ret && "can't have null instance"); return *ret; diff --git a/src/daemon/instance_settings_handler.h b/src/daemon/instance_settings_handler.h index 10f8cc43b72..91720e2d375 100644 --- a/src/daemon/instance_settings_handler.h +++ b/src/daemon/instance_settings_handler.h @@ -38,7 +38,7 @@ class InstanceSettingsHandler : public SettingsHandler { public: InstanceSettingsHandler(std::unordered_map& vm_instance_specs, - std::unordered_map& vm_instances, + std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, const std::unordered_set& preparing_instances, std::function instance_persister); @@ -55,7 +55,7 @@ class InstanceSettingsHandler : public SettingsHandler private: // references, careful std::unordered_map& vm_instance_specs; - std::unordered_map& vm_instances; + std::unordered_map& operative_instances; const std::unordered_map& deleted_instances; const std::unordered_set& preparing_instances; std::function instance_persister; From bc70cf72f1f12fe2bd6874bd9fd9134f07b57013 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 21:38:48 +0100 Subject: [PATCH 441/627] [settings] Implement snapshot settings keys --- src/daemon/snapshot_settings_handler.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index f2a80efecab..fa8675dde1c 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -17,6 +17,16 @@ #include "snapshot_settings_handler.h" +#include + +#include + +namespace +{ +constexpr auto name_suffix = "name"; +constexpr auto comment_suffix = "comment"; +} // namespace + multipass::SnapshotSettingsHandler::SnapshotSettingsHandler( std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, @@ -29,7 +39,17 @@ multipass::SnapshotSettingsHandler::SnapshotSettingsHandler( std::set multipass::SnapshotSettingsHandler::keys() const { - return std::set{}; + static const auto key_template = QStringLiteral("%1.%2.%3.%4").arg(daemon_settings_root); + std::set ret; + + const auto& const_operative_instances = operative_instances; + for (const auto* instance_map : {&const_operative_instances, &deleted_instances}) + for (const auto& [vm_name, vm] : *instance_map) + for (const auto& snapshot : vm->view_snapshots()) + for (const auto& suffix : {name_suffix, comment_suffix}) + ret.insert(key_template.arg(vm_name.c_str(), snapshot->get_name().c_str(), suffix)); + + return ret; } QString multipass::SnapshotSettingsHandler::get(const QString& key) const From 3d44f15b3b5583c4330ae67c9fc746f642e694bc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 18:38:00 +0100 Subject: [PATCH 442/627] [settings] Use shorthand for multipass namespace --- src/daemon/snapshot_settings_handler.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index fa8675dde1c..b71fac3f45c 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -21,13 +21,15 @@ #include +namespace mp = multipass; + namespace { constexpr auto name_suffix = "name"; constexpr auto comment_suffix = "comment"; } // namespace -multipass::SnapshotSettingsHandler::SnapshotSettingsHandler( +mp::SnapshotSettingsHandler::SnapshotSettingsHandler( std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, const std::unordered_set& preparing_instances) @@ -37,7 +39,7 @@ multipass::SnapshotSettingsHandler::SnapshotSettingsHandler( { } -std::set multipass::SnapshotSettingsHandler::keys() const +std::set mp::SnapshotSettingsHandler::keys() const { static const auto key_template = QStringLiteral("%1.%2.%3.%4").arg(daemon_settings_root); std::set ret; @@ -52,11 +54,11 @@ std::set multipass::SnapshotSettingsHandler::keys() const return ret; } -QString multipass::SnapshotSettingsHandler::get(const QString& key) const +QString mp::SnapshotSettingsHandler::get(const QString& key) const { return QString{}; } -void multipass::SnapshotSettingsHandler::set(const QString& key, const QString& val) +void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) { } From 6caef761564a2eed05f363f6585e1f7e027396aa Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 18:45:24 +0100 Subject: [PATCH 443/627] [settings] Avoid repeated `QString::arg` call --- src/daemon/instance_settings_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/instance_settings_handler.cpp b/src/daemon/instance_settings_handler.cpp index f375f636b4f..fe647679eae 100644 --- a/src/daemon/instance_settings_handler.cpp +++ b/src/daemon/instance_settings_handler.cpp @@ -181,7 +181,7 @@ std::set mp::InstanceSettingsHandler::keys() const std::set ret; for (const auto& item : vm_instance_specs) for (const auto& suffix : {cpus_suffix, mem_suffix, disk_suffix}) - ret.insert(key_template.arg(item.first.c_str()).arg(suffix)); + ret.insert(key_template.arg(item.first.c_str(), suffix)); return ret; } From 4ddee4aac4cfc523f1025a04a05e77c11226620f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 22:00:53 +0100 Subject: [PATCH 444/627] [settings] Implement SnapshotSettingsHandler::get Implement getting of snapshot settings in terms of other stub functions. (Doesn't work yet.) --- src/daemon/snapshot_settings_handler.cpp | 22 +++++++++++++++++++++- src/daemon/snapshot_settings_handler.h | 5 +++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index b71fac3f45c..f8a6911c5f7 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -27,6 +27,11 @@ namespace { constexpr auto name_suffix = "name"; constexpr auto comment_suffix = "comment"; + +std::tuple parse_key(const QString& key) +{ + return {"fake_instance", "fake_snapshot", "fake_property"}; // TODO@no-merge +} } // namespace mp::SnapshotSettingsHandler::SnapshotSettingsHandler( @@ -56,9 +61,24 @@ std::set mp::SnapshotSettingsHandler::keys() const QString mp::SnapshotSettingsHandler::get(const QString& key) const { - return QString{}; + auto [instance_name, snapshot_name, property] = parse_key(key); + + auto snapshot = find_snapshot(instance_name, snapshot_name.toStdString()); + + if (property == name_suffix) + return snapshot_name; // not very useful, but for completeness + + assert(property == comment_suffix); + return QString::fromStdString(snapshot->get_comment()); } void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) { } + +auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name, + const std::string& snapshot_name) const + -> const std::shared_ptr +{ + return nullptr; +} diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 58037018500..248b7f31984 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -29,6 +29,7 @@ namespace multipass { + class SnapshotSettingsHandler : public SettingsHandler { public: @@ -40,6 +41,10 @@ class SnapshotSettingsHandler : public SettingsHandler QString get(const QString& key) const override; void set(const QString& key, const QString& val) override; +private: + const std::shared_ptr find_snapshot(const std::string& instance_name, + const std::string& snapshot_name) const; + private: // references, careful std::unordered_map& operative_instances; From a62b6d9fe5b9ffdb532efe7604726add7273854c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 20:55:57 +0100 Subject: [PATCH 445/627] [settings] Add a SnapshotSettingsException --- src/daemon/snapshot_settings_handler.cpp | 8 ++++++++ src/daemon/snapshot_settings_handler.h | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index f8a6911c5f7..ec26c2d056c 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -16,8 +16,10 @@ */ #include "snapshot_settings_handler.h" +#include "multipass/exceptions/snapshot_exceptions.h" #include +#include #include @@ -34,6 +36,12 @@ std::tuple parse_key(const QString& key) } } // namespace +mp::SnapshotSettingsException::SnapshotSettingsException(const std::string& missing_instance, const std::string& detail) + : SettingsException{ + fmt::format("Cannot access snapshot settings for instance {}; reason: {}", missing_instance, detail)} +{ +} + mp::SnapshotSettingsHandler::SnapshotSettingsHandler( std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 248b7f31984..316bbe5dbbc 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -51,6 +51,13 @@ class SnapshotSettingsHandler : public SettingsHandler const std::unordered_map& deleted_instances; const std::unordered_set& preparing_instances; }; + +class SnapshotSettingsException : public SettingsException +{ +public: + SnapshotSettingsException(const std::string& missing_instance, const std::string& detail); +}; + } // namespace multipass #endif // MULTIPASS_SNAPSHOT_SETTINGS_HANDLER_H From f9a266a10b15d3b4929c75b1fcac46db28808cc6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 22:02:39 +0100 Subject: [PATCH 446/627] [settings] Implement snapshot finding Implement `find_snapshot` finding in `SnapshotSettingsHandler`, in terms of hollow `find_instance`. --- src/daemon/snapshot_settings_handler.cpp | 8 +++++++- src/daemon/snapshot_settings_handler.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index ec26c2d056c..4e7df8fab3f 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -82,11 +82,17 @@ QString mp::SnapshotSettingsHandler::get(const QString& key) const void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) { + // TODO@no-merge } auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name, const std::string& snapshot_name) const -> const std::shared_ptr { - return nullptr; + return find_instance(instance_name).get_snapshot(snapshot_name); // TODO@ricab try-catch NoSuchSnapshot +} + +auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name) const -> const VirtualMachine& +{ + throw std::logic_error{"TODO"}; // TODO@ricab complete } diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 316bbe5dbbc..525e9f17715 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -44,6 +44,7 @@ class SnapshotSettingsHandler : public SettingsHandler private: const std::shared_ptr find_snapshot(const std::string& instance_name, const std::string& snapshot_name) const; + const VirtualMachine& find_instance(const std::string& instance_name) const; private: // references, careful From 6478339d3eab30e27e335d411fa0f755a1b311ce Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 21:03:55 +0100 Subject: [PATCH 447/627] [settings] Forbid snapshot settings on preparing Forbid snapshot settings on preparing instances. --- src/daemon/snapshot_settings_handler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 4e7df8fab3f..79d0075b460 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -94,5 +94,8 @@ auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name) const -> const VirtualMachine& { + if (preparing_instances.find(instance_name) != preparing_instances.end()) + throw SnapshotSettingsException{instance_name, "instance is being prepared"}; + throw std::logic_error{"TODO"}; // TODO@ricab complete } From 0faddbc1e3d7b23c18cd83b211837520ba2f624f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 21:10:53 +0100 Subject: [PATCH 448/627] [settings] Throw when a snapshot isn't found Throw when trying to get snapshot settings for a snapshot that isn't there. --- src/daemon/snapshot_settings_handler.cpp | 18 +++++++++++++++--- src/daemon/snapshot_settings_handler.h | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 79d0075b460..affd6652475 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -29,6 +29,7 @@ namespace { constexpr auto name_suffix = "name"; constexpr auto comment_suffix = "comment"; +constexpr auto common_exception_msg = "Cannot access snapshot settings"; std::tuple parse_key(const QString& key) { @@ -37,8 +38,12 @@ std::tuple parse_key(const QString& key) } // namespace mp::SnapshotSettingsException::SnapshotSettingsException(const std::string& missing_instance, const std::string& detail) - : SettingsException{ - fmt::format("Cannot access snapshot settings for instance {}; reason: {}", missing_instance, detail)} + : SettingsException{fmt::format("{}; instance: {}; reason: {}", common_exception_msg, missing_instance, detail)} +{ +} + +mp::SnapshotSettingsException::SnapshotSettingsException(const std::string& detail) + : SettingsException{fmt::format("{}; reason: {}", common_exception_msg, detail)} { } @@ -89,7 +94,14 @@ auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name const std::string& snapshot_name) const -> const std::shared_ptr { - return find_instance(instance_name).get_snapshot(snapshot_name); // TODO@ricab try-catch NoSuchSnapshot + try + { + return find_instance(instance_name).get_snapshot(snapshot_name); + } + catch (const NoSuchSnapshot& e) + { + throw SnapshotSettingsException{e.what()}; + } } auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name) const -> const VirtualMachine& diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 525e9f17715..5f126831673 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -57,6 +57,7 @@ class SnapshotSettingsException : public SettingsException { public: SnapshotSettingsException(const std::string& missing_instance, const std::string& detail); + SnapshotSettingsException(const std::string& detail); }; } // namespace multipass From 2259e61ee9aa8068aee0dfa88693eb1070fbf543 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 21:13:15 +0100 Subject: [PATCH 449/627] [settings] A couple of cosmetic fixes --- src/daemon/instance_settings_handler.cpp | 2 +- src/daemon/instance_settings_handler.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/daemon/instance_settings_handler.cpp b/src/daemon/instance_settings_handler.cpp index fe647679eae..a95b4ecaf0e 100644 --- a/src/daemon/instance_settings_handler.cpp +++ b/src/daemon/instance_settings_handler.cpp @@ -206,7 +206,7 @@ void mp::InstanceSettingsHandler::set(const QString& key, const QString& val) auto [instance_name, property] = parse_key(key); if (preparing_instances.find(instance_name) != preparing_instances.end()) - throw InstanceSettingsException{operation_msg(Operation::Modify), instance_name, "Instance is being prepared"}; + throw InstanceSettingsException{operation_msg(Operation::Modify), instance_name, "instance is being prepared"}; auto& instance = modify_instance(instance_name); // we need this first, to refuse updating deleted instances auto& spec = modify_spec(instance_name); diff --git a/src/daemon/instance_settings_handler.h b/src/daemon/instance_settings_handler.h index 91720e2d375..9a17253e1c5 100644 --- a/src/daemon/instance_settings_handler.h +++ b/src/daemon/instance_settings_handler.h @@ -34,6 +34,7 @@ namespace multipass { + class InstanceSettingsHandler : public SettingsHandler { public: From a061b999f46212d3645edb5cf64b3910a3d1ab20 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 21:52:59 +0100 Subject: [PATCH 450/627] [settings] Fix return types for helpers Fix return types of `find_snapshot` and `find_instance`: return `shared_ptr` to `const` in both cases. --- src/daemon/snapshot_settings_handler.cpp | 7 ++++--- src/daemon/snapshot_settings_handler.h | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index affd6652475..a4854e8e4c6 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -92,11 +92,11 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name, const std::string& snapshot_name) const - -> const std::shared_ptr + -> std::shared_ptr { try { - return find_instance(instance_name).get_snapshot(snapshot_name); + return find_instance(instance_name)->get_snapshot(snapshot_name); } catch (const NoSuchSnapshot& e) { @@ -104,7 +104,8 @@ auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name } } -auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name) const -> const VirtualMachine& +auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name) const + -> std::shared_ptr { if (preparing_instances.find(instance_name) != preparing_instances.end()) throw SnapshotSettingsException{instance_name, "instance is being prepared"}; diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 5f126831673..cd1c8b22db8 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -42,9 +42,9 @@ class SnapshotSettingsHandler : public SettingsHandler void set(const QString& key, const QString& val) override; private: - const std::shared_ptr find_snapshot(const std::string& instance_name, - const std::string& snapshot_name) const; - const VirtualMachine& find_instance(const std::string& instance_name) const; + std::shared_ptr find_snapshot(const std::string& instance_name, + const std::string& snapshot_name) const; + std::shared_ptr find_instance(const std::string& instance_name) const; private: // references, careful From 2804f9945a7180e382568a464ba4ba48cb2cb5a8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 12 Sep 2023 21:57:25 +0100 Subject: [PATCH 451/627] [settings] Implement instance finding Implement instance finding for snapshot settings. --- src/daemon/snapshot_settings_handler.cpp | 15 +++++++++++++-- src/daemon/snapshot_settings_handler.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index a4854e8e4c6..637dba79e01 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -62,7 +62,6 @@ std::set mp::SnapshotSettingsHandler::keys() const static const auto key_template = QStringLiteral("%1.%2.%3.%4").arg(daemon_settings_root); std::set ret; - const auto& const_operative_instances = operative_instances; for (const auto* instance_map : {&const_operative_instances, &deleted_instances}) for (const auto& [vm_name, vm] : *instance_map) for (const auto& snapshot : vm->view_snapshots()) @@ -110,5 +109,17 @@ auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name if (preparing_instances.find(instance_name) != preparing_instances.end()) throw SnapshotSettingsException{instance_name, "instance is being prepared"}; - throw std::logic_error{"TODO"}; // TODO@ricab complete + for (const auto* instance_map : {&const_operative_instances, &deleted_instances}) + { + try + { + return instance_map->at(instance_name); + } + catch (std::out_of_range&) + { + continue; // we're OK reading snapshot properties of deleted instances + } + } + + throw SnapshotSettingsException{instance_name, "no such instance"}; } diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index cd1c8b22db8..4dd01f9fbf5 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -51,6 +51,7 @@ class SnapshotSettingsHandler : public SettingsHandler std::unordered_map& operative_instances; const std::unordered_map& deleted_instances; const std::unordered_set& preparing_instances; + const std::unordered_map& const_operative_instances = operative_instances; }; class SnapshotSettingsException : public SettingsException From e933ca9e22d424d52c731c320bdc64ef6010e527 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 13 Sep 2023 12:23:27 +0100 Subject: [PATCH 452/627] [settings] Implement snapshot-settings key parsing --- src/daemon/snapshot_settings_handler.cpp | 31 +++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 637dba79e01..50226db93f6 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -31,9 +31,38 @@ constexpr auto name_suffix = "name"; constexpr auto comment_suffix = "comment"; constexpr auto common_exception_msg = "Cannot access snapshot settings"; +QRegularExpression make_key_regex() +{ + const auto instance_pattern = QStringLiteral("(?.+)"); + const auto snapshot_pattern = QStringLiteral("(?.+)"); + + const auto property_template = QStringLiteral("(?%1)"); + const auto either_prop = QStringList{name_suffix, comment_suffix}.join("|"); + const auto property_pattern = property_template.arg(either_prop); + + const auto key_template = QStringLiteral(R"(%1\.%2\.%3\.%4)"); + const auto key_pattern = + key_template.arg(mp::daemon_settings_root, instance_pattern, snapshot_pattern, property_pattern); + + return QRegularExpression{QRegularExpression::anchoredPattern(key_pattern)}; +} + std::tuple parse_key(const QString& key) { - return {"fake_instance", "fake_snapshot", "fake_property"}; // TODO@no-merge + static const auto key_regex = make_key_regex(); + + auto match = key_regex.match(key); + if (match.hasMatch()) + { + auto instance = match.captured("instance"); + auto snapshot = match.captured("snapshot"); + auto property = match.captured("property"); + + assert(!instance.isEmpty() && !snapshot.isEmpty() && !property.isEmpty()); + return {instance.toStdString(), snapshot, property.toStdString()}; + } + + throw mp::UnrecognizedSettingException{key}; } } // namespace From 67a55644b29387c90536a346d1c5ac42b9ea788f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 21:10:24 +0100 Subject: [PATCH 453/627] [settings] Implement setting snapshot properties Implement setting snapshot names and comments, in terms of other hollow functions that still need implementing. --- include/multipass/virtual_machine.h | 1 + src/daemon/snapshot_settings_handler.cpp | 26 ++++++++++++++++++- src/daemon/snapshot_settings_handler.h | 4 +++ .../backends/shared/base_virtual_machine.cpp | 5 ++++ .../backends/shared/base_virtual_machine.h | 1 + tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 4 +++ 7 files changed, 41 insertions(+), 1 deletion(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index bbb7cfd5b28..0a93bd210dc 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -95,6 +95,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; + virtual void rename_snapshot(const std::string& old_name, const std::string& new_name) = 0; virtual void delete_snapshot(const std::string& name) = 0; virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots() = 0; diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 50226db93f6..9b16c1c548e 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -115,7 +115,20 @@ QString mp::SnapshotSettingsHandler::get(const QString& key) const void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) { - // TODO@no-merge + auto [instance_name, snapshot_name, property] = parse_key(key); + + if (property == name_suffix) + { + // TODO@no-merge need to verify name validity/uniqueness and update map + modify_vm(instance_name)->rename_snapshot(snapshot_name.toStdString(), val.toStdString()); + } + else + { + assert(property == comment_suffix); + auto [vm, snapshot] = modify_snapshot(instance_name, snapshot_name.toStdString()); + snapshot->set_comment(val.toStdString()); + } + // TODO@no-merge persist (ideally would happen automatically in setters) } auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name, @@ -152,3 +165,14 @@ auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name throw SnapshotSettingsException{instance_name, "no such instance"}; } + +auto mp::SnapshotSettingsHandler::modify_vm(const std::string& instance_name) -> std::shared_ptr +{ + return nullptr; // TODO@no-merge +} + +auto mp::SnapshotSettingsHandler::modify_snapshot(const std::string& instance_name, const std::string& snapshot_name) + -> std::pair, std::shared_ptr> +{ + return {nullptr, nullptr}; // TODO@no-merge +} diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 4dd01f9fbf5..b25872a1df8 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -46,6 +46,10 @@ class SnapshotSettingsHandler : public SettingsHandler const std::string& snapshot_name) const; std::shared_ptr find_instance(const std::string& instance_name) const; + std::shared_ptr modify_vm(const std::string& instance_name); + std::pair, std::shared_ptr> + modify_snapshot(const std::string& instance_name, const std::string& snapshot_name); + private: // references, careful std::unordered_map& operative_instances; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4c4415ad98d..2dceccfb38e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -337,6 +337,11 @@ void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_paren } } +void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std::string& new_name) +{ + // TODO@no-merge +} + void BaseVirtualMachine::delete_snapshot(const std::string& name) { std::unique_lock lock{snapshot_mutex}; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 7e255a77ff7..efd362407a4 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -71,6 +71,7 @@ class BaseVirtualMachine : public VirtualMachine void restore_snapshot(const std::string& name, VMSpecs& specs) override; void load_snapshots() override; std::vector get_childrens_names(const Snapshot* parent) const override; + void rename_snapshot(const std::string& old_name, const std::string& new_name) override; protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 15b7edace65..4a7fc507444 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -83,6 +83,7 @@ struct MockVirtualMachineT : public T take_snapshot, (const VMSpecs&, const std::string&, const std::string&), (override)); + MOCK_METHOD(void, rename_snapshot, (const std::string& old_name, const std::string& new_name), (override)); MOCK_METHOD(void, delete_snapshot, (const std::string& name), (override)); MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); MOCK_METHOD(void, load_snapshots, (), (override)); diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 33c2022a433..66025046d71 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -151,6 +151,10 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + void rename_snapshot(const std::string& old_name, const std::string& new_name) override + { + } + void delete_snapshot(const std::string&) override { } From 1b5963a20fe536134e577603f26f89aa8dabf88c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 21:10:00 +0100 Subject: [PATCH 454/627] [settings] Factor out string conversions --- src/daemon/snapshot_settings_handler.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 9b16c1c548e..393e89eb627 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -116,17 +116,19 @@ QString mp::SnapshotSettingsHandler::get(const QString& key) const void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) { auto [instance_name, snapshot_name, property] = parse_key(key); + auto snapshot_name_stdstr = snapshot_name.toStdString(); + auto val_stdstr = val.toStdString(); if (property == name_suffix) { // TODO@no-merge need to verify name validity/uniqueness and update map - modify_vm(instance_name)->rename_snapshot(snapshot_name.toStdString(), val.toStdString()); + modify_vm(instance_name)->rename_snapshot(snapshot_name_stdstr, val_stdstr); } else { assert(property == comment_suffix); - auto [vm, snapshot] = modify_snapshot(instance_name, snapshot_name.toStdString()); - snapshot->set_comment(val.toStdString()); + auto [vm, snapshot] = modify_snapshot(instance_name, snapshot_name_stdstr); + snapshot->set_comment(val_stdstr); } // TODO@no-merge persist (ideally would happen automatically in setters) } From 9d84aea56f1fcc208d8ad9f22aff44b6afc2d50c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 21:33:49 +0100 Subject: [PATCH 455/627] [settings] Forbid bad snapshot names --- src/daemon/snapshot_settings_handler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 393e89eb627..d03ba81ef3d 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -17,6 +17,7 @@ #include "snapshot_settings_handler.h" #include "multipass/exceptions/snapshot_exceptions.h" +#include "multipass/utils.h" #include #include @@ -121,7 +122,10 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) if (property == name_suffix) { - // TODO@no-merge need to verify name validity/uniqueness and update map + if (val_stdstr.empty() || !mp::utils::valid_hostname(val_stdstr)) + throw mp::InvalidSettingException{key, val, "Invalid snapshot name."}; + + // TODO@no-merge need to verify name uniqueness and update map modify_vm(instance_name)->rename_snapshot(snapshot_name_stdstr, val_stdstr); } else From eda5582432b70a41332c0955a606b50f5412f1ca Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 23:08:13 +0100 Subject: [PATCH 456/627] [settings] Homogenize akin method names --- src/daemon/snapshot_settings_handler.cpp | 4 ++-- src/daemon/snapshot_settings_handler.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index d03ba81ef3d..54203661acc 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -126,7 +126,7 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) throw mp::InvalidSettingException{key, val, "Invalid snapshot name."}; // TODO@no-merge need to verify name uniqueness and update map - modify_vm(instance_name)->rename_snapshot(snapshot_name_stdstr, val_stdstr); + modify_instance(instance_name)->rename_snapshot(snapshot_name_stdstr, val_stdstr); } else { @@ -172,7 +172,7 @@ auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name throw SnapshotSettingsException{instance_name, "no such instance"}; } -auto mp::SnapshotSettingsHandler::modify_vm(const std::string& instance_name) -> std::shared_ptr +auto mp::SnapshotSettingsHandler::modify_instance(const std::string& instance_name) -> std::shared_ptr { return nullptr; // TODO@no-merge } diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index b25872a1df8..52afec1c386 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -46,9 +46,9 @@ class SnapshotSettingsHandler : public SettingsHandler const std::string& snapshot_name) const; std::shared_ptr find_instance(const std::string& instance_name) const; - std::shared_ptr modify_vm(const std::string& instance_name); std::pair, std::shared_ptr> modify_snapshot(const std::string& instance_name, const std::string& snapshot_name); + std::shared_ptr modify_instance(const std::string& instance_name); private: // references, careful From a1a564a92ad2aa8d795e940057cb78b05d0cb19b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 23:14:03 +0100 Subject: [PATCH 457/627] [settings] Implement finding VMs for modification --- src/daemon/snapshot_settings_handler.cpp | 24 +++++++++++++++++------- src/daemon/snapshot_settings_handler.h | 3 +-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 54203661acc..30f9d8564cd 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -92,6 +92,7 @@ std::set mp::SnapshotSettingsHandler::keys() const static const auto key_template = QStringLiteral("%1.%2.%3.%4").arg(daemon_settings_root); std::set ret; + const auto& const_operative_instances = operative_instances; for (const auto* instance_map : {&const_operative_instances, &deleted_instances}) for (const auto& [vm_name, vm] : *instance_map) for (const auto& snapshot : vm->view_snapshots()) @@ -151,30 +152,39 @@ auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name } } -auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name) const +auto mp::SnapshotSettingsHandler::find_instance(const std::string& instance_name, bool deleted_ok) const -> std::shared_ptr { if (preparing_instances.find(instance_name) != preparing_instances.end()) throw SnapshotSettingsException{instance_name, "instance is being prepared"}; - for (const auto* instance_map : {&const_operative_instances, &deleted_instances}) + try + { + return operative_instances.at(instance_name); + } + catch (std::out_of_range&) { + std::string error{"No such instance"}; + try { - return instance_map->at(instance_name); + const auto& del = deleted_instances.at(instance_name); + if (deleted_ok) + return del; + + error = "Instance is deleted"; } catch (std::out_of_range&) { - continue; // we're OK reading snapshot properties of deleted instances } - } - throw SnapshotSettingsException{instance_name, "no such instance"}; + throw SnapshotSettingsException{instance_name, error}; + } } auto mp::SnapshotSettingsHandler::modify_instance(const std::string& instance_name) -> std::shared_ptr { - return nullptr; // TODO@no-merge + return std::const_pointer_cast(find_instance(instance_name, false)); } auto mp::SnapshotSettingsHandler::modify_snapshot(const std::string& instance_name, const std::string& snapshot_name) diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 52afec1c386..8310b229a14 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -44,7 +44,7 @@ class SnapshotSettingsHandler : public SettingsHandler private: std::shared_ptr find_snapshot(const std::string& instance_name, const std::string& snapshot_name) const; - std::shared_ptr find_instance(const std::string& instance_name) const; + std::shared_ptr find_instance(const std::string& instance_name, bool deleted_ok = true) const; std::pair, std::shared_ptr> modify_snapshot(const std::string& instance_name, const std::string& snapshot_name); @@ -55,7 +55,6 @@ class SnapshotSettingsHandler : public SettingsHandler std::unordered_map& operative_instances; const std::unordered_map& deleted_instances; const std::unordered_set& preparing_instances; - const std::unordered_map& const_operative_instances = operative_instances; }; class SnapshotSettingsException : public SettingsException From 4ef8261790df1e33500207c8145d5d00a0e4852f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 23:15:04 +0100 Subject: [PATCH 458/627] [vm] Implement snapshot renaming --- src/daemon/snapshot_settings_handler.cpp | 1 - .../backends/shared/base_virtual_machine.cpp | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 30f9d8564cd..406c3ce9ca8 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -126,7 +126,6 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) if (val_stdstr.empty() || !mp::utils::valid_hostname(val_stdstr)) throw mp::InvalidSettingException{key, val, "Invalid snapshot name."}; - // TODO@no-merge need to verify name uniqueness and update map modify_instance(instance_name)->rename_snapshot(snapshot_name_stdstr, val_stdstr); } else diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 2dceccfb38e..750772d19ef 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -339,7 +339,24 @@ void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_paren void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std::string& new_name) { - // TODO@no-merge + if (old_name == new_name) + return; + + std::unique_lock lock{snapshot_mutex}; + + auto old_it = snapshots.find(old_name); + if (old_it == snapshots.end()) + throw NoSuchSnapshot{vm_name, old_name}; + + if (snapshots.find(new_name) != snapshots.end()) + throw SnapshotNameTaken{vm_name, new_name}; + + auto snapshot_node = snapshots.extract(old_it); + snapshot_node.key() = new_name; + snapshot_node.mapped()->set_name(new_name); + + snapshots.insert(std::move(snapshot_node)); + // TODO@no-merge persist! } void BaseVirtualMachine::delete_snapshot(const std::string& name) From 72661d126d8238a4e3a615c390b4074341469fdc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 23:23:21 +0100 Subject: [PATCH 459/627] [settings] Shortcut naming snapshot with same name --- src/daemon/snapshot_settings_handler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 406c3ce9ca8..1454f6169cb 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -123,6 +123,9 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) if (property == name_suffix) { + if (snapshot_name == val) + return; + if (val_stdstr.empty() || !mp::utils::valid_hostname(val_stdstr)) throw mp::InvalidSettingException{key, val, "Invalid snapshot name."}; From a320d496120c6086762505df6e050c1b315f54ca Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 29 Sep 2023 23:34:30 +0100 Subject: [PATCH 460/627] [vm] Implement "best-effort transactional" rename --- src/platform/backends/shared/base_virtual_machine.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 750772d19ef..33c87e411e5 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -352,10 +352,12 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: throw SnapshotNameTaken{vm_name, new_name}; auto snapshot_node = snapshots.extract(old_it); - snapshot_node.key() = new_name; - snapshot_node.mapped()->set_name(new_name); + auto reinsert_guard = sg::make_scope_guard([this, &snapshot_node]() noexcept { + top_catch_all(vm_name, [this, &snapshot_node] { snapshots.insert(std::move(snapshot_node)); }); + }); // we want this to execute both on failure and success - snapshots.insert(std::move(snapshot_node)); + snapshot_node.mapped()->set_name(new_name); + snapshot_node.key() = new_name; // TODO@no-merge persist! } From 12cf1be91f086ba1e4fdf8542b972fc7d878952e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 2 Oct 2023 17:59:05 +0100 Subject: [PATCH 461/627] [settings] Simplify helper return Simplify the return of `SnapshotSettingsHandler::modify_snapshot`, dropping the VM portion. Rely only on the snapshot for modifications that don't intersect with other snapshots (e.g. comment). This will require the snapshot to be able to persist itself. --- src/daemon/snapshot_settings_handler.cpp | 6 +++--- src/daemon/snapshot_settings_handler.h | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 1454f6169cb..a767f7e6805 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -134,7 +134,7 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) else { assert(property == comment_suffix); - auto [vm, snapshot] = modify_snapshot(instance_name, snapshot_name_stdstr); + auto snapshot = modify_snapshot(instance_name, snapshot_name_stdstr); snapshot->set_comment(val_stdstr); } // TODO@no-merge persist (ideally would happen automatically in setters) @@ -190,7 +190,7 @@ auto mp::SnapshotSettingsHandler::modify_instance(const std::string& instance_na } auto mp::SnapshotSettingsHandler::modify_snapshot(const std::string& instance_name, const std::string& snapshot_name) - -> std::pair, std::shared_ptr> + -> std::shared_ptr { - return {nullptr, nullptr}; // TODO@no-merge + return nullptr; // TODO@no-merge } diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 8310b229a14..4c6c67f94cd 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -46,8 +46,7 @@ class SnapshotSettingsHandler : public SettingsHandler const std::string& snapshot_name) const; std::shared_ptr find_instance(const std::string& instance_name, bool deleted_ok = true) const; - std::pair, std::shared_ptr> - modify_snapshot(const std::string& instance_name, const std::string& snapshot_name); + std::shared_ptr modify_snapshot(const std::string& instance_name, const std::string& snapshot_name); std::shared_ptr modify_instance(const std::string& instance_name); private: From 4d0b236ef08b0c47f462eaba7114215ab01d3ed5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 2 Oct 2023 18:03:57 +0100 Subject: [PATCH 462/627] [settings] Implement finding a snapshot for mod --- src/daemon/snapshot_settings_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index a767f7e6805..e092a8edce3 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -192,5 +192,5 @@ auto mp::SnapshotSettingsHandler::modify_instance(const std::string& instance_na auto mp::SnapshotSettingsHandler::modify_snapshot(const std::string& instance_name, const std::string& snapshot_name) -> std::shared_ptr { - return nullptr; // TODO@no-merge + return std::const_pointer_cast(find_snapshot(instance_name, snapshot_name)); } From 7c3338e875bc94875ede458a291b875dad160196 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 2 Oct 2023 18:16:40 +0100 Subject: [PATCH 463/627] [vm] Improve renaming rollback Cover the unlikely case that assigning the map node's key throws for any reason. Update TODOs. --- src/platform/backends/shared/base_snapshot.h | 10 ++++++++++ src/platform/backends/shared/base_virtual_machine.cpp | 11 ++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 98807087395..536817c7b01 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -179,36 +179,46 @@ inline void multipass::BaseSnapshot::set_name(const std::string& n) { const std::unique_lock lock{mutex}; name = n; + // TODO@no-merge persist! } inline void multipass::BaseSnapshot::set_comment(const std::string& c) { const std::unique_lock lock{mutex}; comment = c; + // TODO@no-merge persist! } inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) { const std::unique_lock lock{mutex}; parent = std::move(p); + // TODO@no-merge persist! + // TODO@no-merge stop persisting elsewhere } inline void multipass::BaseSnapshot::capture() { const std::unique_lock lock{mutex}; capture_impl(); + // TODO@no-merge persist! + // TODO@no-merge stop persisting elsewhere } inline void multipass::BaseSnapshot::erase() { const std::unique_lock lock{mutex}; erase_impl(); + // TODO@no-merge persist! + // TODO@no-merge stop persisting elsewhere } inline void multipass::BaseSnapshot::apply() { const std::unique_lock lock{mutex}; apply_impl(); + // TODO@no-merge persist! + // TODO@no-merge stop persisting elsewhere } #endif // MULTIPASS_BASE_SNAPSHOT_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 33c87e411e5..7c75f4a9cac 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -353,12 +353,17 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: auto snapshot_node = snapshots.extract(old_it); auto reinsert_guard = sg::make_scope_guard([this, &snapshot_node]() noexcept { - top_catch_all(vm_name, [this, &snapshot_node] { snapshots.insert(std::move(snapshot_node)); }); + top_catch_all(vm_name, [this, &snapshot_node] { + const auto& current_name = snapshot_node.mapped()->get_name(); + if (auto& key = snapshot_node.key(); key != current_name) + key = current_name; // best-effort rollback (this is very unlikely to fail) + + snapshots.insert(std::move(snapshot_node)); + }); }); // we want this to execute both on failure and success - snapshot_node.mapped()->set_name(new_name); snapshot_node.key() = new_name; - // TODO@no-merge persist! + snapshot_node.mapped()->set_name(new_name); } void BaseVirtualMachine::delete_snapshot(const std::string& name) From 56d935e9c90ac95c22a8f8ffd4400d1c9431e799 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 2 Oct 2023 18:31:52 +0100 Subject: [PATCH 464/627] [vm] Extract reinsert guard from snapshot renaming --- .../backends/shared/base_virtual_machine.cpp | 25 ++++++++++++------- .../backends/shared/base_virtual_machine.h | 5 +++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 7c75f4a9cac..79b613dd1d3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -337,6 +337,21 @@ void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_paren } } +template +auto BaseVirtualMachine::make_reinsert_guard(NodeT& snapshot_node) +{ + return sg::make_scope_guard([this, &snapshot_node]() noexcept { + top_catch_all(vm_name, [this, &snapshot_node] { + const auto& current_name = snapshot_node.mapped()->get_name(); + if (auto& key = snapshot_node.key(); key != current_name) + key = current_name; // best-effort rollback (this is very unlikely to fail) + + snapshots.insert(std::move(snapshot_node)); + }); + }); + ; +} + void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std::string& new_name) { if (old_name == new_name) @@ -352,15 +367,7 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: throw SnapshotNameTaken{vm_name, new_name}; auto snapshot_node = snapshots.extract(old_it); - auto reinsert_guard = sg::make_scope_guard([this, &snapshot_node]() noexcept { - top_catch_all(vm_name, [this, &snapshot_node] { - const auto& current_name = snapshot_node.mapped()->get_name(); - if (auto& key = snapshot_node.key(); key != current_name) - key = current_name; // best-effort rollback (this is very unlikely to fail) - - snapshots.insert(std::move(snapshot_node)); - }); - }); // we want this to execute both on failure and success + auto reinsert_guard = make_reinsert_guard(snapshot_node); // we want this to execute both on failure and success snapshot_node.key() = new_name; snapshot_node.mapped()->set_name(new_name); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index efd362407a4..9331cf36a86 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -67,11 +67,11 @@ class BaseVirtualMachine : public VirtualMachine std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) override; + void rename_snapshot(const std::string& old_name, const std::string& new_name) override; void delete_snapshot(const std::string& name) override; void restore_snapshot(const std::string& name, VMSpecs& specs) override; void load_snapshots() override; std::vector get_childrens_names(const Snapshot* parent) const override; - void rename_snapshot(const std::string& old_name, const std::string& new_name) override; protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; @@ -103,6 +103,9 @@ class BaseVirtualMachine : public VirtualMachine void persist_head_snapshot_name(const Path& head_path) const; std::string generate_snapshot_name() const; + template + auto make_reinsert_guard(NodeT& snapshot_node); + auto make_restore_rollback(const Path& head_path, VMSpecs& specs); void restore_rollback_helper(const Path& head_path, const std::shared_ptr& old_head, From 80dad4c1f037728afb8fc9d2c519660b997b8bda Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 17:42:29 +0100 Subject: [PATCH 465/627] [snapshot] Require VM param to construct snapshots In preparation for the snapshot to derive the file name to persist/read itself to/from. --- src/platform/backends/qemu/qemu_snapshot.cpp | 3 ++- src/platform/backends/qemu/qemu_snapshot.h | 1 + src/platform/backends/qemu/qemu_virtual_machine.cpp | 2 +- src/platform/backends/shared/base_snapshot.cpp | 3 ++- src/platform/backends/shared/base_snapshot.h | 3 ++- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 4ad00eb361d..b9854a3de05 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -58,8 +58,9 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent, + QemuVirtualMachine& vm, VirtualMachineDescription& desc) - : BaseSnapshot(name, comment, specs, std::move(parent)), desc{desc}, image_path{desc.image.image_path} + : BaseSnapshot(name, comment, specs, std::move(parent), vm), desc{desc}, image_path{desc.image.image_path} { } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index fe7a0718843..31b7a236f7a 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -36,6 +36,7 @@ class QemuSnapshot : public BaseSnapshot const std::string& comment, const VMSpecs& specs, std::shared_ptr parent, + QemuVirtualMachine& vm, VirtualMachineDescription& desc); QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc); diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 6f78ce324bf..5ba9e03db89 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -627,7 +627,7 @@ auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, std::shared_ptr parent) -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise - return std::make_shared(name, comment, specs, std::move(parent), desc); + return std::make_shared(name, comment, specs, std::move(parent), *this, desc); } auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 074455f2037..f8586c1dae4 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -116,7 +116,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent) + std::shared_ptr parent, + VirtualMachine& vm) : BaseSnapshot{name, comment, QDateTime::currentDateTimeUtc(), diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 536817c7b01..427acd9ba79 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -37,7 +37,8 @@ class BaseSnapshot : public Snapshot BaseSnapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent); + std::shared_ptr parent, + VirtualMachine& vm); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); std::string get_name() const override; From b77966be3827fd97841ff10f22e6df52b11be7a2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 18:08:00 +0100 Subject: [PATCH 466/627] [vm] Add snapshot-count getter --- include/multipass/virtual_machine.h | 1 + src/platform/backends/shared/base_virtual_machine.h | 7 +++++++ tests/mock_virtual_machine.h | 1 + tests/stub_virtual_machine.h | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 0a93bd210dc..f256530ef34 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -100,6 +100,7 @@ class VirtualMachine : private DisabledCopyMove virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots() = 0; virtual std::vector get_childrens_names(const Snapshot* parent) const = 0; + virtual int get_snapshot_count() const = 0; VirtualMachine::State state; const std::string vm_name; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 9331cf36a86..ec832b234f6 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -72,6 +72,7 @@ class BaseVirtualMachine : public VirtualMachine void restore_snapshot(const std::string& name, VMSpecs& specs) override; void load_snapshots() override; std::vector get_childrens_names(const Snapshot* parent) const override; + int get_snapshot_count() const override; protected: virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; @@ -134,4 +135,10 @@ class BaseVirtualMachine : public VirtualMachine } // namespace multipass +inline int multipass::BaseVirtualMachine::get_snapshot_count() const +{ + const std::unique_lock lock{snapshot_mutex}; + return snapshot_count; +} + #endif // MULTIPASS_BASE_VIRTUAL_MACHINE_H diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 4a7fc507444..b70b12becc4 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -88,6 +88,7 @@ struct MockVirtualMachineT : public T MOCK_METHOD(void, restore_snapshot, (const std::string&, VMSpecs&), (override)); 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)); std::unique_ptr tmp_dir; }; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 66025046d71..0dba61f8591 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -172,6 +172,11 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + int get_snapshot_count() const override + { + return 0; + } + StubSnapshot snapshot; std::unique_ptr tmp_dir; }; From 5046a7c4fe4fc254969477e1c1198c79f18770dc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 17:54:46 +0100 Subject: [PATCH 467/627] [snapshot] Add an index field to snapshots In preparation for the snapshot to derive the file name to persist/read itself to/from. --- .../backends/shared/base_snapshot.cpp | 32 +++++++++++-------- src/platform/backends/shared/base_snapshot.h | 4 ++- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f8586c1dae4..1fb6d95c6b9 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -82,7 +82,8 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa } } // namespace -mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-pass-by-value) +mp::BaseSnapshot::BaseSnapshot(int index, + const std::string& name, // NOLINT(modernize-pass-by-value) const std::string& comment, // NOLINT(modernize-pass-by-value) const QDateTime& creation_timestamp, int num_cores, @@ -92,7 +93,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p std::unordered_map mounts, QJsonObject metadata, std::shared_ptr parent) - : name{name}, + : index{index}, + name{name}, comment{comment}, creation_timestamp{creation_timestamp}, num_cores{num_cores}, @@ -118,7 +120,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const VMSpecs& specs, std::shared_ptr parent, VirtualMachine& vm) - : BaseSnapshot{name, + : BaseSnapshot{vm.get_snapshot_count() + 1, + name, comment, QDateTime::currentDateTimeUtc(), specs.num_cores, @@ -137,17 +140,18 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) } mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm) - : BaseSnapshot{json["name"].toString().toStdString(), // name - json["comment"].toString().toStdString(), // comment - QDateTime::fromString(json["creation_timestamp"].toString(), - Qt::ISODateWithMs), // creation_timestamp - json["num_cores"].toInt(), // num_cores - MemorySize{json["mem_size"].toString().toStdString()}, // mem_size - MemorySize{json["disk_space"].toString().toStdString()}, // disk_space - static_cast(json["state"].toInt()), // state - load_mounts(json["mounts"].toArray()), // mounts - json["metadata"].toObject(), // metadata - find_parent(json, vm)} // parent + : BaseSnapshot{ + 0, // TODO@ricab derive index + json["name"].toString().toStdString(), // name + json["comment"].toString().toStdString(), // comment + QDateTime::fromString(json["creation_timestamp"].toString(), Qt::ISODateWithMs), // creation_timestamp + json["num_cores"].toInt(), // num_cores + MemorySize{json["mem_size"].toString().toStdString()}, // mem_size + MemorySize{json["disk_space"].toString().toStdString()}, // disk_space + static_cast(json["state"].toInt()), // state + load_mounts(json["mounts"].toArray()), // mounts + json["metadata"].toObject(), // metadata + find_parent(json, vm)} // parent { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 427acd9ba79..d391bda53ba 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -79,7 +79,8 @@ class BaseSnapshot : public Snapshot { }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); - BaseSnapshot(const std::string& name, + BaseSnapshot(int index, + const std::string& name, const std::string& get_comment, const QDateTime& creation_timestamp, int num_cores, @@ -91,6 +92,7 @@ class BaseSnapshot : public Snapshot std::shared_ptr parent); private: + int index; std::string name; std::string comment; From 2e1746ba73641971424f6781fbebf91b7e1b0623 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 18:16:31 +0100 Subject: [PATCH 468/627] [vm] Add instance directory accessor --- include/multipass/virtual_machine.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index f256530ef34..7cfa3a03622 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -102,6 +102,8 @@ class VirtualMachine : private DisabledCopyMove virtual std::vector get_childrens_names(const Snapshot* parent) const = 0; virtual int get_snapshot_count() const = 0; + QDir instance_directory() const; + VirtualMachine::State state; const std::string vm_name; std::condition_variable state_wait; @@ -118,4 +120,10 @@ class VirtualMachine : private DisabledCopyMove : VirtualMachine(State::off, vm_name, instance_dir){}; }; } // namespace multipass + +inline QDir multipass::VirtualMachine::instance_directory() const +{ + return instance_dir; // TODO this should probably only be known at the level of the base VM +} + #endif // MULTIPASS_VIRTUAL_MACHINE_H From 7c5fb529a5b6a510190296c74df798adb06568df Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 18:28:34 +0100 Subject: [PATCH 469/627] [snapshot] Add a directory field to snapshots So that they know where to save/read themselves to/from. --- src/platform/backends/shared/base_snapshot.cpp | 6 +++++- src/platform/backends/shared/base_snapshot.h | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 1fb6d95c6b9..4571c1bfa3c 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -83,6 +83,7 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa } // namespace mp::BaseSnapshot::BaseSnapshot(int index, + const QDir& storage_dir, const std::string& name, // NOLINT(modernize-pass-by-value) const std::string& comment, // NOLINT(modernize-pass-by-value) const QDateTime& creation_timestamp, @@ -94,6 +95,7 @@ mp::BaseSnapshot::BaseSnapshot(int index, QJsonObject metadata, std::shared_ptr parent) : index{index}, + storage_dir{storage_dir}, name{name}, comment{comment}, creation_timestamp{creation_timestamp}, @@ -121,6 +123,7 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, std::shared_ptr parent, VirtualMachine& vm) : BaseSnapshot{vm.get_snapshot_count() + 1, + vm.instance_directory(), name, comment, QDateTime::currentDateTimeUtc(), @@ -141,7 +144,8 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm) : BaseSnapshot{ - 0, // TODO@ricab derive index + 0, // TODO@ricab derive index + vm.instance_directory(), json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment QDateTime::fromString(json["creation_timestamp"].toString(), Qt::ISODateWithMs), // creation_timestamp diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index d391bda53ba..a594c120a0b 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -80,6 +80,7 @@ class BaseSnapshot : public Snapshot }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); BaseSnapshot(int index, + const QDir& storage_dir, const std::string& name, const std::string& get_comment, const QDateTime& creation_timestamp, @@ -93,6 +94,8 @@ class BaseSnapshot : public Snapshot private: int index; + QDir storage_dir; + std::string name; std::string comment; From 383af1ee81dfd63a5e14f700d8238bee7ab1eb23 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 18:20:47 +0100 Subject: [PATCH 470/627] [snapshot] Add assert for constructor precondition --- src/platform/backends/shared/base_snapshot.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 4571c1bfa3c..c4bb8fa49c7 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -107,6 +107,8 @@ mp::BaseSnapshot::BaseSnapshot(int index, metadata{std::move(metadata)}, parent{std::move(parent)} { + assert(index > 0 && "snapshot indices need to start at 1"); + if (name.empty()) throw std::runtime_error{"Snapshot names cannot be empty"}; if (num_cores < 1) From d00687d57bf2f29a414b64f67ea38ac3f8d43337 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 16:56:11 +0100 Subject: [PATCH 471/627] [snapshot] Add a persist method Add a method (hollow, for now) for snapshots to persist themselves. Expose it publicly, provisionally, with a view to hiding it in the future. --- include/multipass/snapshot.h | 3 ++- src/platform/backends/shared/base_snapshot.cpp | 9 +++++++-- src/platform/backends/shared/base_snapshot.h | 1 + tests/stub_snapshot.h | 4 ++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index c220e344829..c34a4f28f51 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -54,7 +54,8 @@ class Snapshot : private DisabledCopyMove virtual const std::unordered_map& get_mounts() const noexcept = 0; virtual const QJsonObject& get_metadata() const noexcept = 0; - virtual QJsonObject serialize() const = 0; + virtual QJsonObject serialize() const = 0; // TODO@no-merge remove + virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file virtual void set_comment(const std::string&) = 0; diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index c4bb8fa49c7..230c564cc49 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -161,7 +161,7 @@ mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMac { } -QJsonObject multipass::BaseSnapshot::serialize() const +QJsonObject mp::BaseSnapshot::serialize() const { QJsonObject ret, snapshot{}; const std::unique_lock lock{mutex}; @@ -220,7 +220,12 @@ QJsonObject multipass::BaseSnapshot::serialize() const return ret; } -QString multipass::BaseSnapshot::derive_id() const +void mp::BaseSnapshot::persist() const +{ + // TODO@no-merge +} + +QString mp::BaseSnapshot::derive_id() const { return snapshot_template.arg(get_name().c_str()); } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index a594c120a0b..6628282485e 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -58,6 +58,7 @@ class BaseSnapshot : public Snapshot const QJsonObject& get_metadata() const noexcept override; QJsonObject serialize() const override; + void persist() const override; void set_name(const std::string& n) override; void set_comment(const std::string& c) override; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 4546d07c777..00537c52bf3 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -92,6 +92,10 @@ struct StubSnapshot : public Snapshot return {}; } + void persist() const override + { + } + void set_name(const std::string&) override { } From a46df75d3c37611a25b21adb70d8c794cfdedfc0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 19:10:16 +0100 Subject: [PATCH 472/627] [snapshot] Implement `Snapshot::persist` --- .../backends/shared/base_snapshot.cpp | 22 ++++++++++++++++++- src/platform/backends/shared/base_snapshot.h | 1 + .../backends/shared/base_virtual_machine.cpp | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 230c564cc49..03aaa67f4de 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -17,11 +17,13 @@ #include "base_snapshot.h" #include "daemon/vm_specs.h" // TODO@snapshots move this +#include "multipass/json_utils.h" #include // TODO@snapshots may be able to drop after extracting JSON utilities #include #include // TODO@snapshots may be able to drop after extracting JSON utilities +#include #include @@ -29,9 +31,22 @@ namespace mp = multipass; namespace { +constexpr auto snapshot_extension = "snapshot.json"; +constexpr auto index_digits = 4; // these two go together +constexpr auto max_snapshots = 1000; const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char that can't be part of the name, to avoid confusion */ +QString derive_index_string(int index) +{ + return QString{"%1"}.arg(index, index_digits, 10, QLatin1Char('0')); +} + +QString derive_snapshot_filename(const QString& index, const QString& name) +{ + return QString{"%1-%2.%3"}.arg(index, name, snapshot_extension); +} + std::unordered_map load_mounts(const QJsonArray& json) { std::unordered_map mounts; @@ -222,7 +237,12 @@ QJsonObject mp::BaseSnapshot::serialize() const void mp::BaseSnapshot::persist() const { - // TODO@no-merge + const std::unique_lock lock{mutex}; + + // TODO@no-merge rollback + const auto snapshot_filename = derive_snapshot_filename(derive_index_string(index), QString::fromStdString(name)); + auto snapshot_filepath = storage_dir.filePath(snapshot_filename); + mp::write_json(serialize(), snapshot_filepath); } QString mp::BaseSnapshot::derive_id() const diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 6628282485e..aadf75c205d 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -186,6 +186,7 @@ inline void multipass::BaseSnapshot::set_name(const std::string& n) { const std::unique_lock lock{mutex}; name = n; + // TODO@no-merge delete current file // TODO@no-merge persist! } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 79b613dd1d3..208ee64105c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -39,6 +39,8 @@ namespace mp = multipass; namespace mpl = multipass::logging; namespace mpu = multipass::utils; +// TODO@no-merge prune everything related to individual snapshot persistence + namespace { using St = mp::VirtualMachine::State; From ff94fb662dfc83acc75cdeed0530aaef51a0e877 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 19:13:10 +0100 Subject: [PATCH 473/627] [snapshot] Tag new fields as const --- src/platform/backends/shared/base_snapshot.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index aadf75c205d..cca6bdad4ba 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -94,13 +94,12 @@ class BaseSnapshot : public Snapshot std::shared_ptr parent); private: - int index; - QDir storage_dir; - std::string name; std::string comment; // This class is non-copyable and having these const simplifies thread safety + const int index; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const QDir storage_dir; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QDateTime creation_timestamp; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const MemorySize mem_size; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) From bb2166830710fd25164342d3dbf1d10832d9fc54 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 19:15:33 +0100 Subject: [PATCH 474/627] [snapshot] Fix param name --- src/platform/backends/shared/base_snapshot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index cca6bdad4ba..0b878b1d7d5 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -83,7 +83,7 @@ class BaseSnapshot : public Snapshot BaseSnapshot(int index, const QDir& storage_dir, const std::string& name, - const std::string& get_comment, + const std::string& comment, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, From d985ea8579d2a83eba2d849f5dacc595a45efb25 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 19:28:25 +0100 Subject: [PATCH 475/627] [snapshot] Reorder fields and ctor params --- src/platform/backends/qemu/qemu_snapshot.cpp | 4 +- src/platform/backends/qemu/qemu_snapshot.h | 2 +- .../backends/qemu/qemu_virtual_machine.cpp | 2 +- .../backends/shared/base_snapshot.cpp | 40 +++++++++---------- src/platform/backends/shared/base_snapshot.h | 15 ++++--- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index b9854a3de05..77994c3db06 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -56,11 +56,11 @@ std::unique_ptr make_delete_spec(const QString& tag, con mp::QemuSnapshot::QemuSnapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent, + const VMSpecs& specs, QemuVirtualMachine& vm, VirtualMachineDescription& desc) - : BaseSnapshot(name, comment, specs, std::move(parent), vm), desc{desc}, image_path{desc.image.image_path} + : BaseSnapshot(name, comment, std::move(parent), specs, vm), desc{desc}, image_path{desc.image.image_path} { } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 31b7a236f7a..09cdcd5d014 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -34,8 +34,8 @@ class QemuSnapshot : public BaseSnapshot public: QemuSnapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent, + const VMSpecs& specs, QemuVirtualMachine& vm, VirtualMachineDescription& desc); QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc); diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 5ba9e03db89..ffde1407900 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -627,7 +627,7 @@ auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, std::shared_ptr parent) -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise - return std::make_shared(name, comment, specs, std::move(parent), *this, desc); + return std::make_shared(name, comment, std::move(parent), specs, *this, desc); } auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 03aaa67f4de..0e9445129f0 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -97,30 +97,30 @@ std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMa } } // namespace -mp::BaseSnapshot::BaseSnapshot(int index, - const QDir& storage_dir, - const std::string& name, // NOLINT(modernize-pass-by-value) +mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-pass-by-value) const std::string& comment, // NOLINT(modernize-pass-by-value) + std::shared_ptr parent, + int index, + const QDir& storage_dir, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, - QJsonObject metadata, - std::shared_ptr parent) - : index{index}, - storage_dir{storage_dir}, - name{name}, + QJsonObject metadata) + : name{name}, comment{comment}, + parent{std::move(parent)}, + index{index}, + storage_dir{storage_dir}, creation_timestamp{creation_timestamp}, num_cores{num_cores}, mem_size{mem_size}, disk_space{disk_space}, state{state}, mounts{std::move(mounts)}, - metadata{std::move(metadata)}, - parent{std::move(parent)} + metadata{std::move(metadata)} { assert(index > 0 && "snapshot indices need to start at 1"); @@ -136,21 +136,21 @@ mp::BaseSnapshot::BaseSnapshot(int index, mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent, + const VMSpecs& specs, VirtualMachine& vm) - : BaseSnapshot{vm.get_snapshot_count() + 1, - vm.instance_directory(), - name, + : BaseSnapshot{name, comment, + std::move(parent), + vm.get_snapshot_count() + 1, + vm.instance_directory(), QDateTime::currentDateTimeUtc(), specs.num_cores, specs.mem_size, specs.disk_space, specs.state, specs.mounts, - specs.metadata, - std::move(parent)} + specs.metadata} { } @@ -161,18 +161,18 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm) : BaseSnapshot{ - 0, // TODO@ricab derive index - vm.instance_directory(), json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment + find_parent(json, vm), // parent + 0, // index TODO@ricab + vm.instance_directory(), // storage_dir QDateTime::fromString(json["creation_timestamp"].toString(), Qt::ISODateWithMs), // creation_timestamp json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space static_cast(json["state"].toInt()), // state load_mounts(json["mounts"].toArray()), // mounts - json["metadata"].toObject(), // metadata - find_parent(json, vm)} // parent + json["metadata"].toObject()} // metadata { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 0b878b1d7d5..026a1a5c570 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -36,8 +36,8 @@ class BaseSnapshot : public Snapshot public: BaseSnapshot(const std::string& name, const std::string& comment, - const VMSpecs& specs, std::shared_ptr parent, + const VMSpecs& specs, VirtualMachine& vm); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); @@ -80,22 +80,23 @@ class BaseSnapshot : public Snapshot { }; BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); - BaseSnapshot(int index, - const QDir& storage_dir, - const std::string& name, + BaseSnapshot(const std::string& name, const std::string& comment, + std::shared_ptr parent, + int index, + const QDir& storage_dir, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, - QJsonObject metadata, - std::shared_ptr parent); + QJsonObject metadata); private: std::string name; std::string comment; + std::shared_ptr parent; // This class is non-copyable and having these const simplifies thread safety const int index; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -108,8 +109,6 @@ class BaseSnapshot : public Snapshot const std::unordered_map mounts; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QJsonObject metadata; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - std::shared_ptr parent; - mutable std::recursive_mutex mutex; }; } // namespace multipass From 72512f0847c2df6c2e6aa342a16b5b37d24c04eb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 3 Oct 2023 19:34:01 +0100 Subject: [PATCH 476/627] [snapshot] Adapt JSON format to updated fields --- src/platform/backends/shared/base_snapshot.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 0e9445129f0..8c5a3898a85 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -164,7 +164,7 @@ mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMac json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment find_parent(json, vm), // parent - 0, // index TODO@ricab + json["index"].toInt(), // index vm.instance_directory(), // storage_dir QDateTime::fromString(json["creation_timestamp"].toString(), Qt::ISODateWithMs), // creation_timestamp json["num_cores"].toInt(), // num_cores @@ -183,13 +183,14 @@ QJsonObject mp::BaseSnapshot::serialize() const snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); + snapshot.insert("parent", QString::fromStdString(get_parents_name())); + snapshot.insert("index", index); snapshot.insert("creation_timestamp", creation_timestamp.toString(Qt::ISODateWithMs)); snapshot.insert("num_cores", num_cores); snapshot.insert("mem_size", QString::number(mem_size.in_bytes())); snapshot.insert("disk_space", QString::number(disk_space.in_bytes())); snapshot.insert("state", static_cast(state)); snapshot.insert("metadata", metadata); - snapshot.insert("parent", QString::fromStdString(get_parents_name())); // Extract mount serialization QJsonArray json_mounts; From 777dc6eea4470109595f140cb6b1f26197d40587 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 4 Oct 2023 11:34:42 +0100 Subject: [PATCH 477/627] [utils] Use QSaveFile to write json First step towards making it transactional. --- src/utils/json_utils.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index 898a02a309a..34c358853c4 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -20,8 +20,8 @@ #include #include -#include #include +#include namespace mp = multipass; @@ -29,9 +29,10 @@ void mp::write_json(const QJsonObject& root, QString file_name) { QJsonDocument doc{root}; auto raw_json = doc.toJson(); - QFile db_file{file_name}; - MP_FILEOPS.open(db_file, QIODevice::ReadWrite | QIODevice::Truncate); + QSaveFile db_file{file_name}; + MP_FILEOPS.open(db_file, QIODevice::WriteOnly); MP_FILEOPS.write(db_file, raw_json); + db_file.commit(); } std::string mp::json_to_string(const QJsonObject& root) From 5816c50be7218194559e37d714c94b307f122b85 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 4 Oct 2023 12:26:07 +0100 Subject: [PATCH 478/627] [utils] Actually handle errors when writing JSON This completes making it transactional. --- include/multipass/json_utils.h | 2 +- src/utils/json_utils.cpp | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/include/multipass/json_utils.h b/include/multipass/json_utils.h index 2b08d86b587..330ebb493ff 100644 --- a/include/multipass/json_utils.h +++ b/include/multipass/json_utils.h @@ -27,7 +27,7 @@ namespace multipass { -void write_json(const QJsonObject& root, QString file_name); +void write_json(const QJsonObject& root, QString file_name); // transactional; requires the parent directory to exist std::string json_to_string(const QJsonObject& root); } #endif // MULTIPASS_JSON_UTILS_H diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index 34c358853c4..c6a016cff2d 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -17,22 +17,32 @@ * */ +#include "multipass/format.h" #include #include #include #include +#include + namespace mp = multipass; void mp::write_json(const QJsonObject& root, QString file_name) { QJsonDocument doc{root}; auto raw_json = doc.toJson(); + QSaveFile db_file{file_name}; - MP_FILEOPS.open(db_file, QIODevice::WriteOnly); - MP_FILEOPS.write(db_file, raw_json); - db_file.commit(); + if (!MP_FILEOPS.open(db_file, QIODevice::WriteOnly)) + throw std::runtime_error{fmt::format("Could not open transactional file for writing; filename: {}", file_name)}; + + if (MP_FILEOPS.write(db_file, raw_json) == -1) + throw std::runtime_error{fmt::format("Could not write json to transactional file; filename: {}; error: {}", + file_name, db_file.errorString())}; + + if (!db_file.commit()) + throw std::runtime_error{fmt::format("Could not commit transactional file; filename: {}", file_name)}; } std::string mp::json_to_string(const QJsonObject& root) From 7189054ba62125bbf88645a3eaa86d2fb403d552 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 4 Oct 2023 18:30:49 +0100 Subject: [PATCH 479/627] [aliases] Rely on transactional json writing Rely on newly transactional `mp::write_json` not to mess with existing aliases files unless they can be properly overwritten. Remove obsolete file backup logic. Breaks tests. --- src/client/common/alias_dict.cpp | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/client/common/alias_dict.cpp b/src/client/common/alias_dict.cpp index 48fce686fc6..3f3d95cf5b5 100644 --- a/src/client/common/alias_dict.cpp +++ b/src/client/common/alias_dict.cpp @@ -398,33 +398,13 @@ void mp::AliasDict::save_dict() QJsonObject dict_json = to_json(); auto config_file_name = QString::fromStdString(aliases_file); - - QTemporaryFile temp_file{mpu::create_temp_file_with_path(config_file_name)}; - - if (MP_FILEOPS.open(temp_file, QIODevice::ReadWrite)) + auto temp_folder = QFileInfo(config_file_name).absoluteDir(); + if (!MP_FILEOPS.mkpath(temp_folder, ".")) { - temp_file.setAutoRemove(false); - - mp::write_json(dict_json, temp_file.fileName()); - - temp_file.close(); - - if (MP_FILEOPS.exists(QFile{config_file_name})) - { - auto backup_file_name = config_file_name + ".bak"; - QFile backup_file(backup_file_name); - - if (MP_FILEOPS.exists(backup_file) && !MP_FILEOPS.remove(backup_file)) - throw std::runtime_error(fmt::format("cannot remove old aliases backup file {}", backup_file_name)); - - QFile config_file(config_file_name); - if (!MP_FILEOPS.rename(config_file, backup_file_name)) - throw std::runtime_error(fmt::format("cannot rename aliases config to {}", backup_file_name)); - } - - if (!MP_FILEOPS.rename(temp_file, config_file_name)) - throw std::runtime_error(fmt::format("cannot create aliases config file {}", config_file_name)); + throw std::runtime_error(fmt::format("Could not create path '{}'", temp_folder.absolutePath())); } + + mp::write_json(dict_json, config_file_name); } // This function removes the contexts which do not contain aliases, except the active context. From 27629e581175ba41e8842447e11f2457d91f2219 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 4 Oct 2023 18:31:30 +0100 Subject: [PATCH 480/627] [tests] Remove obsolete alias test Other tests still broken. --- tests/test_alias_dict.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index d8ea96471f7..031f60ff748 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -421,17 +421,6 @@ TEST_F(AliasDictionary, get_unexisting_alias_returns_nullopt) ASSERT_EQ(dict.get_alias("unexisting"), std::nullopt); } -TEST_F(AliasDictionary, creates_backup_db) -{ - populate_db_file(AliasesVector{{"some_alias", {"some_instance", "some_command", "map"}}}); - - QString bak_filename = QString::fromStdString(db_filename() + ".bak"); - ASSERT_FALSE(QFile::exists(bak_filename)); - - populate_db_file(AliasesVector{{"another_alias", {"an_instance", "a_command", "map"}}}); - ASSERT_TRUE(QFile::exists(bak_filename)); -} - TEST_F(AliasDictionary, throws_when_open_alias_file_fails) { auto [mock_file_ops, guard] = mpt::MockFileOps::inject(); From 904a8153ea739226a76e8d1007eb164899638c71 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 4 Oct 2023 18:33:52 +0100 Subject: [PATCH 481/627] [tests] Replace obsolete alias tests with new one Drop tests that verify obsolete alias file backup logic, replacing them with a test to verify new behavior when unable to create parent directory. --- tests/test_cli_client.cpp | 54 +++------------------------------------ 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 83859b7b53f..adb68c1d217 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -3906,64 +3906,18 @@ TEST_F(ClientAlias, unaliasDashDashAllClashesWithOtherArguments) "another_alias,another_instance,another_command,default,default*\n"); } -TEST_F(ClientAlias, fails_when_remove_backup_alias_file_fails) +TEST_F(ClientAlias, fails_if_unable_to_create_directory) { auto [mock_file_ops, guard] = mpt::MockFileOps::inject(); - EXPECT_CALL(*mock_file_ops, exists(A())) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)); // mpu::create_temp_file_with_path() - EXPECT_CALL(*mock_file_ops, open(_, _)).Times(2).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_file_ops, write(_, _)).WillOnce(Return(true)); - EXPECT_CALL(*mock_file_ops, remove(_)).WillOnce(Return(false)); - - EXPECT_CALL(mock_daemon, info(_, _)).Times(AtMost(1)).WillRepeatedly(make_info_function()); - - std::stringstream cerr_stream; - send_command({"alias", "primary:command", "alias_name"}, trash_stream, cerr_stream); - - ASSERT_THAT(cerr_stream.str(), HasSubstr("cannot remove old aliases backup file ")); -} - -TEST_F(ClientAlias, fails_renaming_alias_file_fails) -{ - auto [mock_file_ops, guard] = mpt::MockFileOps::inject(); - - EXPECT_CALL(*mock_file_ops, exists(A())) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(false)); - EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)); // mpu::create_temp_file_with_path() - EXPECT_CALL(*mock_file_ops, open(_, _)).Times(2).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_file_ops, write(_, _)).WillOnce(Return(true)); - EXPECT_CALL(*mock_file_ops, rename(_, _)).WillOnce(Return(false)); - - EXPECT_CALL(mock_daemon, info(_, _)).Times(AtMost(1)).WillRepeatedly(make_info_function()); - - std::stringstream cerr_stream; - send_command({"alias", "primary:command", "alias_name"}, trash_stream, cerr_stream); - - ASSERT_THAT(cerr_stream.str(), HasSubstr("cannot rename aliases config to ")); -} - -TEST_F(ClientAlias, fails_creating_alias_file_fails) -{ - auto [mock_file_ops, guard] = mpt::MockFileOps::inject(); - - EXPECT_CALL(*mock_file_ops, exists(A())).WillOnce(Return(false)).WillOnce(Return(false)); - EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)); // mpu::create_temp_file_with_path() - EXPECT_CALL(*mock_file_ops, open(_, _)).Times(2).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_file_ops, write(_, _)).WillOnce(Return(true)); - EXPECT_CALL(*mock_file_ops, rename(_, _)).WillOnce(Return(false)); - + EXPECT_CALL(*mock_file_ops, exists(A())).WillOnce(Return(false)); + EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(false)); EXPECT_CALL(mock_daemon, info(_, _)).Times(AtMost(1)).WillRepeatedly(make_info_function()); std::stringstream cerr_stream; send_command({"alias", "primary:command", "alias_name"}, trash_stream, cerr_stream); - ASSERT_THAT(cerr_stream.str(), HasSubstr("cannot create aliases config file ")); + ASSERT_THAT(cerr_stream.str(), HasSubstr("Could not create path")); } TEST_F(ClientAlias, creating_first_alias_displays_message) From 835c96510a8ea7bd8c3a86e63ae6e32550644602 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 4 Oct 2023 21:08:19 +0100 Subject: [PATCH 482/627] [tests] Fix a couple of hanging daemon tests --- src/utils/json_utils.cpp | 2 +- tests/test_daemon.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index c6a016cff2d..d4b1cbfe374 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -41,7 +41,7 @@ void mp::write_json(const QJsonObject& root, QString file_name) throw std::runtime_error{fmt::format("Could not write json to transactional file; filename: {}; error: {}", file_name, db_file.errorString())}; - if (!db_file.commit()) + if (!MP_FILEOPS.commit(db_file)) throw std::runtime_error{fmt::format("Could not commit transactional file; filename: {}", file_name)}; } diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 3ec0bfe0b80..c6efd6eaf6d 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -843,6 +843,9 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotMountUnwrittableWo .WillOnce(Return(temp_dir.path())); auto [mock_file_ops, guard] = mpt::MockFileOps::inject(); + EXPECT_CALL(*mock_file_ops, open(_, _)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_file_ops, write(_, _)).WillRepeatedly(Return(1234)); + EXPECT_CALL(*mock_file_ops, commit(_)).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(false)); config_builder.blueprint_provider = std::move(mock_blueprint_provider); @@ -887,6 +890,9 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundButCannotMount) .WillOnce(Return(temp_dir.path())); auto [mock_file_ops, guard] = mpt::MockFileOps::inject(); + EXPECT_CALL(*mock_file_ops, open(_, _)).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_file_ops, write(_, _)).WillRepeatedly(Return(1234)); + EXPECT_CALL(*mock_file_ops, commit(_)).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)); config_builder.blueprint_provider = std::move(mock_blueprint_provider); From e2d866f2840d229262729ec3213e0660cb0a75b7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 5 Oct 2023 00:21:13 +0100 Subject: [PATCH 483/627] [utils] Move json utils to mockable singleton To simplify fixing of remaining failing tests. --- include/multipass/json_utils.h | 16 +++++++++++++--- src/client/cli/formatter/json_formatter.cpp | 14 +++++++------- src/client/common/alias_dict.cpp | 2 +- src/daemon/daemon.cpp | 2 +- src/daemon/default_vm_image_vault.cpp | 2 +- src/platform/backends/shared/base_snapshot.cpp | 2 +- .../backends/shared/base_virtual_machine.cpp | 6 +++--- src/utils/json_utils.cpp | 8 ++++++-- 8 files changed, 33 insertions(+), 19 deletions(-) diff --git a/include/multipass/json_utils.h b/include/multipass/json_utils.h index 330ebb493ff..675c5e886b8 100644 --- a/include/multipass/json_utils.h +++ b/include/multipass/json_utils.h @@ -20,14 +20,24 @@ #ifndef MULTIPASS_JSON_UTILS_H #define MULTIPASS_JSON_UTILS_H +#include "singleton.h" + #include #include #include +#define MP_JSONUTILS multipass::JsonUtils::instance() + namespace multipass { -void write_json(const QJsonObject& root, QString file_name); // transactional; requires the parent directory to exist -std::string json_to_string(const QJsonObject& root); -} +class JsonUtils : public Singleton +{ +public: + explicit JsonUtils(const Singleton::PrivatePass&) noexcept; + + virtual void write_json(const QJsonObject& root, QString file_name) const; // transactional; requires dir to exist + virtual std::string json_to_string(const QJsonObject& root) const; +}; +} // namespace multipass #endif // MULTIPASS_JSON_UTILS_H diff --git a/src/client/cli/formatter/json_formatter.cpp b/src/client/cli/formatter/json_formatter.cpp index a95de278c63..3377eea39ca 100644 --- a/src/client/cli/formatter/json_formatter.cpp +++ b/src/client/cli/formatter/json_formatter.cpp @@ -201,7 +201,7 @@ std::string generate_instances_list(const mp::InstancesList& instance_list) list_json.insert("list", instances); - return mp::json_to_string(list_json); + return MP_JSONUTILS.json_to_string(list_json); } std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) @@ -234,7 +234,7 @@ std::string generate_snapshots_list(const mp::SnapshotsList& snapshot_list) info_json.insert("info", info_obj); - return mp::json_to_string(info_json); + return MP_JSONUTILS.json_to_string(info_json); } } // namespace @@ -302,7 +302,7 @@ std::string mp::JsonFormatter::format(const InfoReply& reply) const } info_json.insert("info", info_obj); - return mp::json_to_string(info_json); + return MP_JSONUTILS.json_to_string(info_json); } std::string mp::JsonFormatter::format(const ListReply& reply) const @@ -339,7 +339,7 @@ std::string mp::JsonFormatter::format(const NetworksReply& reply) const list_json.insert("list", interfaces); - return mp::json_to_string(list_json); + return MP_JSONUTILS.json_to_string(list_json); } std::string mp::JsonFormatter::format(const FindReply& reply) const @@ -350,7 +350,7 @@ std::string mp::JsonFormatter::format(const FindReply& reply) const find_json.insert("blueprints", format_images(reply.blueprints_info())); find_json.insert("images", format_images(reply.images_info())); - return mp::json_to_string(find_json); + return MP_JSONUTILS.json_to_string(find_json); } std::string mp::JsonFormatter::format(const VersionReply& reply, const std::string& client_version) const @@ -373,10 +373,10 @@ std::string mp::JsonFormatter::format(const VersionReply& reply, const std::stri version_json.insert("update", update); } } - return mp::json_to_string(version_json); + return MP_JSONUTILS.json_to_string(version_json); } std::string mp::JsonFormatter::format(const mp::AliasDict& aliases) const { - return mp::json_to_string(aliases.to_json()); + return MP_JSONUTILS.json_to_string(aliases.to_json()); } diff --git a/src/client/common/alias_dict.cpp b/src/client/common/alias_dict.cpp index 3f3d95cf5b5..39cde9f239f 100644 --- a/src/client/common/alias_dict.cpp +++ b/src/client/common/alias_dict.cpp @@ -404,7 +404,7 @@ void mp::AliasDict::save_dict() throw std::runtime_error(fmt::format("Could not create path '{}'", temp_folder.absolutePath())); } - mp::write_json(dict_json, config_file_name); + MP_JSONUTILS.write_json(dict_json, config_file_name); } // This function removes the contexts which do not contain aliases, except the active context. diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 355db4a54c2..f68012461df 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2673,7 +2673,7 @@ void mp::Daemon::persist_instances() } QDir data_dir{ mp::utils::backend_directory_path(config->data_directory, config->factory->get_backend_directory_name())}; - mp::write_json(instance_records_json, data_dir.filePath(instance_db_name)); + MP_JSONUTILS.write_json(instance_records_json, data_dir.filePath(instance_db_name)); } void mp::Daemon::release_resources(const std::string& instance) diff --git a/src/daemon/default_vm_image_vault.cpp b/src/daemon/default_vm_image_vault.cpp index bb0cb6b7263..cdf711c6fa5 100644 --- a/src/daemon/default_vm_image_vault.cpp +++ b/src/daemon/default_vm_image_vault.cpp @@ -228,7 +228,7 @@ void persist_records(const T& records, const QString& path) auto key = QString::fromStdString(record.first); json_records.insert(key, record_to_json(record.second)); } - mp::write_json(json_records, path); + MP_JSONUTILS.write_json(json_records, path); } } // namespace diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 8c5a3898a85..a0d6c7c1c0c 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -243,7 +243,7 @@ void mp::BaseSnapshot::persist() const // TODO@no-merge rollback const auto snapshot_filename = derive_snapshot_filename(derive_index_string(index), QString::fromStdString(name)); auto snapshot_filepath = storage_dir.filePath(snapshot_filename); - mp::write_json(serialize(), snapshot_filepath); + MP_JSONUTILS.write_json(serialize(), snapshot_filepath); } QString mp::BaseSnapshot::derive_id() const diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 208ee64105c..eb1f1b7abad 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -92,7 +92,7 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted { snapshot->set_parent(deleted_parent); if (!snapshot_filepath.isEmpty()) - mp::write_json(snapshot->serialize(), snapshot_filepath); + MP_JSONUTILS.write_json(snapshot->serialize(), snapshot_filepath); } } } // namespace @@ -333,7 +333,7 @@ void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_paren updated_snapshot_paths[other.get()]; const auto other_filepath = find_snapshot_file(instance_dir, other->get_name()).filePath(); - write_json(other->serialize(), other_filepath); + MP_JSONUTILS.write_json(other->serialize(), other_filepath); updated_snapshot_paths[other.get()] = other_filepath; } } @@ -525,7 +525,7 @@ void BaseVirtualMachine::persist_head_snapshot() const QFile{snapshot_filepath}.remove(); // best effort, ignore return }); - mp::write_json(head_snapshot->serialize(), snapshot_filepath); + MP_JSONUTILS.write_json(head_snapshot->serialize(), snapshot_filepath); QFile head_file{head_path}; diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index d4b1cbfe374..8f321deb06a 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -28,7 +28,11 @@ namespace mp = multipass; -void mp::write_json(const QJsonObject& root, QString file_name) +mp::JsonUtils::JsonUtils(const Singleton::PrivatePass& pass) noexcept : Singleton{pass} +{ +} + +void mp::JsonUtils::write_json(const QJsonObject& root, QString file_name) const { QJsonDocument doc{root}; auto raw_json = doc.toJson(); @@ -45,7 +49,7 @@ void mp::write_json(const QJsonObject& root, QString file_name) throw std::runtime_error{fmt::format("Could not commit transactional file; filename: {}", file_name)}; } -std::string mp::json_to_string(const QJsonObject& root) +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. return QJsonDocument(root).toJson().toStdString(); From 81383e7dbff5cfd88d73ca9162c4f20cc14a7cca Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 5 Oct 2023 00:21:35 +0100 Subject: [PATCH 484/627] [tests] Add a MockJsonUtils --- tests/mock_json_utils.h | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/mock_json_utils.h diff --git a/tests/mock_json_utils.h b/tests/mock_json_utils.h new file mode 100644 index 00000000000..7dd2a838693 --- /dev/null +++ b/tests/mock_json_utils.h @@ -0,0 +1,40 @@ +/* + * 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_MOCK_JSON_UTILS_H +#define MULTIPASS_MOCK_JSON_UTILS_H + +#include "common.h" +#include "mock_singleton_helpers.h" + +#include + +namespace multipass::test +{ +class MockJsonUtils : public JsonUtils +{ +public: + using JsonUtils::JsonUtils; + + MOCK_METHOD(void, write_json, (const QJsonObject&, QString), (const, override)); + MOCK_METHOD(std::string, json_to_string, (const QJsonObject& root), (const, override)); + + MP_MOCK_SINGLETON_BOILERPLATE(MockJsonUtils, JsonUtils); +}; +} // namespace multipass::test + +#endif // MULTIPASS_MOCK_JSON_UTILS_H From ef8a74f1ca619b4956e9434e77935a491e9eb337 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 5 Oct 2023 00:25:01 +0100 Subject: [PATCH 485/627] [tests] Mock JsonUtils in ImageVault tests Fixes multiple, but not yet all, ImageVault tests. --- tests/test_image_vault.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index 23ea05aa987..cf96115b263 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -19,6 +19,7 @@ #include "disabling_macros.h" #include "file_operations.h" #include "mock_image_host.h" +#include "mock_json_utils.h" #include "mock_logger.h" #include "mock_process_factory.h" #include "path.h" @@ -163,6 +164,7 @@ struct ImageVault : public testing::Test mpt::TrackingURLDownloader url_downloader; std::vector hosts; NiceMock host; + mpt::MockJsonUtils::GuardedMock mock_json_utils_injection = mpt::MockJsonUtils::inject(); mp::ProgressMonitor stub_monitor{[](int, int) { return true; }}; mp::VMImageVault::PrepareAction stub_prepare{ [](const mp::VMImage& source_image) -> mp::VMImage { return source_image; }}; From 4e6ba330146ddfa5f59a4b9b754f554145d98f8e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 5 Oct 2023 02:00:27 +0100 Subject: [PATCH 486/627] [utils] Create parent dirs to write json --- include/multipass/json_utils.h | 2 +- src/client/common/alias_dict.cpp | 12 +----------- src/utils/json_utils.cpp | 7 ++++--- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/include/multipass/json_utils.h b/include/multipass/json_utils.h index 675c5e886b8..ce7a50bdb21 100644 --- a/include/multipass/json_utils.h +++ b/include/multipass/json_utils.h @@ -36,7 +36,7 @@ class JsonUtils : public Singleton public: explicit JsonUtils(const Singleton::PrivatePass&) noexcept; - virtual void write_json(const QJsonObject& root, QString file_name) const; // transactional; requires dir to exist + virtual void write_json(const QJsonObject& root, QString file_name) const; // transactional; creates parent dirs virtual std::string json_to_string(const QJsonObject& root) const; }; } // namespace multipass diff --git a/src/client/common/alias_dict.cpp b/src/client/common/alias_dict.cpp index 39cde9f239f..38a21af4ab6 100644 --- a/src/client/common/alias_dict.cpp +++ b/src/client/common/alias_dict.cpp @@ -394,17 +394,7 @@ void mp::AliasDict::load_dict() void mp::AliasDict::save_dict() { sanitize_contexts(); - - QJsonObject dict_json = to_json(); - - auto config_file_name = QString::fromStdString(aliases_file); - auto temp_folder = QFileInfo(config_file_name).absoluteDir(); - if (!MP_FILEOPS.mkpath(temp_folder, ".")) - { - throw std::runtime_error(fmt::format("Could not create path '{}'", temp_folder.absolutePath())); - } - - MP_JSONUTILS.write_json(dict_json, config_file_name); + MP_JSONUTILS.write_json(to_json(), QString::fromStdString(aliases_file)); } // This function removes the contexts which do not contain aliases, except the active context. diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index 8f321deb06a..0baf5c88f02 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -34,14 +34,15 @@ mp::JsonUtils::JsonUtils(const Singleton::PrivatePass& pass) noexcept void mp::JsonUtils::write_json(const QJsonObject& root, QString file_name) const { - QJsonDocument doc{root}; - auto raw_json = doc.toJson(); + auto dir = QFileInfo(file_name).absoluteDir(); + if (!MP_FILEOPS.mkpath(dir, ".")) + throw std::runtime_error(fmt::format("Could not create path '{}'", dir.absolutePath())); QSaveFile db_file{file_name}; if (!MP_FILEOPS.open(db_file, QIODevice::WriteOnly)) throw std::runtime_error{fmt::format("Could not open transactional file for writing; filename: {}", file_name)}; - if (MP_FILEOPS.write(db_file, raw_json) == -1) + if (MP_FILEOPS.write(db_file, QJsonDocument{root}.toJson()) == -1) throw std::runtime_error{fmt::format("Could not write json to transactional file; filename: {}; error: {}", file_name, db_file.errorString())}; From 47ed2bd48d2b38fa9ff1fe641bf8efd8cfa10bee Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 5 Oct 2023 02:19:20 +0100 Subject: [PATCH 487/627] [tests] Fix two remaining ImageVault test problems --- tests/test_image_vault.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index cf96115b263..4d2af0f1323 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -18,6 +18,7 @@ #include "common.h" #include "disabling_macros.h" #include "file_operations.h" +#include "mock_file_ops.h" #include "mock_image_host.h" #include "mock_json_utils.h" #include "mock_logger.h" @@ -165,6 +166,7 @@ struct ImageVault : public testing::Test std::vector hosts; NiceMock host; mpt::MockJsonUtils::GuardedMock mock_json_utils_injection = mpt::MockJsonUtils::inject(); + mpt::MockJsonUtils& mock_json_utils = *mock_json_utils_injection.first; mp::ProgressMonitor stub_monitor{[](int, int) { return true; }}; mp::VMImageVault::PrepareAction stub_prepare{ [](const mp::VMImage& source_image) -> mp::VMImage { return source_image; }}; @@ -296,6 +298,10 @@ TEST_F(ImageVault, remembers_instance_images) return source_image; }; + EXPECT_CALL(mock_json_utils, write_json).WillRepeatedly([this](auto&&... args) { + return mock_json_utils.JsonUtils::write_json(std::forward(args)...); // call the real thing + }); + mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, @@ -327,6 +333,10 @@ TEST_F(ImageVault, remembers_prepared_images) return source_image; }; + EXPECT_CALL(mock_json_utils, write_json).WillRepeatedly([this](auto&&... args) { + return mock_json_utils.JsonUtils::write_json(std::forward(args)...); // call the real thing + }); + mp::DefaultVMImageVault first_vault{hosts, &url_downloader, cache_dir.path(), data_dir.path(), mp::days{0}}; auto vm_image1 = first_vault.fetch_image(mp::FetchType::ImageOnly, default_query, From 0cf0f030fdd5f28e59d43c4756469cf335a7e261 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 5 Oct 2023 02:13:47 +0100 Subject: [PATCH 488/627] [tests] Fix new issue in a couple of daemon tests --- tests/test_daemon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index c6efd6eaf6d..609c98c0aca 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -846,7 +846,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundDoesNotMountUnwrittableWo EXPECT_CALL(*mock_file_ops, open(_, _)).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_file_ops, write(_, _)).WillRepeatedly(Return(1234)); EXPECT_CALL(*mock_file_ops, commit(_)).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(false)); + EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)).WillOnce(Return(true)).WillOnce(Return(false)); config_builder.blueprint_provider = std::move(mock_blueprint_provider); config_builder.vault = std::move(mock_image_vault); @@ -893,7 +893,7 @@ TEST_F(DaemonCreateLaunchAliasTestSuite, blueprintFoundButCannotMount) EXPECT_CALL(*mock_file_ops, open(_, _)).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_file_ops, write(_, _)).WillRepeatedly(Return(1234)); EXPECT_CALL(*mock_file_ops, commit(_)).WillRepeatedly(Return(true)); - EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)); + EXPECT_CALL(*mock_file_ops, mkpath(_, _)).WillOnce(Return(true)).WillOnce(Return(true)).WillOnce(Return(true)); config_builder.blueprint_provider = std::move(mock_blueprint_provider); config_builder.vault = std::move(mock_image_vault); From 8f710c61fe33765accbaed2c976f464cc754b4b1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 9 Oct 2023 23:44:30 +0100 Subject: [PATCH 489/627] [tests] Fix repeated include guard ids Rename `tests/json_utils.{h,cpp}` and include guards therein, to avoid colliding with `multipass/json_utils.{h,cpp}. Fixes impossibility to include both headers simultaneously. --- tests/CMakeLists.txt | 2 +- tests/{json_utils.cpp => json_test_utils.cpp} | 2 +- tests/{json_utils.h => json_test_utils.h} | 6 +++--- tests/test_alias_dict.cpp | 2 +- tests/test_daemon.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename tests/{json_utils.cpp => json_test_utils.cpp} (99%) rename tests/{json_utils.h => json_test_utils.h} (93%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 52bd407f195..0bc8f6050f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,7 +36,7 @@ add_executable(multipass_tests daemon_test_fixture.cpp file_operations.cpp image_host_remote_count.cpp - json_utils.cpp + json_test_utils.cpp main.cpp mischievous_url_downloader.cpp mock_logger.cpp diff --git a/tests/json_utils.cpp b/tests/json_test_utils.cpp similarity index 99% rename from tests/json_utils.cpp rename to tests/json_test_utils.cpp index 0e2bd264c08..1796b245dbe 100644 --- a/tests/json_utils.cpp +++ b/tests/json_test_utils.cpp @@ -19,7 +19,7 @@ #include "common.h" #include "file_operations.h" -#include "json_utils.h" +#include "json_test_utils.h" #include diff --git a/tests/json_utils.h b/tests/json_test_utils.h similarity index 93% rename from tests/json_utils.h rename to tests/json_test_utils.h index 1385977fea2..1c8df6cf4e5 100644 --- a/tests/json_utils.h +++ b/tests/json_test_utils.h @@ -15,8 +15,8 @@ * */ -#ifndef MULTIPASS_JSON_UTILS_H -#define MULTIPASS_JSON_UTILS_H +#ifndef MULTIPASS_JSON_TEST_UTILS_H +#define MULTIPASS_JSON_TEST_UTILS_H #include "temp_dir.h" @@ -43,4 +43,4 @@ void check_interfaces_in_json(const QString& file, const std::string& mac, void check_mounts_in_json(const QString& file, std::unordered_map& mounts); -#endif // MULTIPASS_JSON_UTILS_H +#endif // MULTIPASS_JSON_TEST_UTILS_H diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index 031f60ff748..189717cb588 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -26,7 +26,7 @@ #include "daemon_test_fixture.h" #include "fake_alias_config.h" #include "file_operations.h" -#include "json_utils.h" +#include "json_test_utils.h" #include "mock_file_ops.h" #include "mock_platform.h" #include "mock_settings.h" diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 609c98c0aca..90c5e2510ea 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -20,7 +20,7 @@ #include "daemon_test_fixture.h" #include "dummy_ssh_key_provider.h" #include "fake_alias_config.h" -#include "json_utils.h" +#include "json_test_utils.h" #include "mock_daemon.h" #include "mock_environment_helpers.h" #include "mock_file_ops.h" From 586024bde0a91c94c98cbf6dbb2e93d9415d5bde Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 9 Oct 2023 23:46:10 +0100 Subject: [PATCH 490/627] [utils/tests] Fix small include and doc issues. --- include/multipass/json_utils.h | 6 ++---- tests/test_image_vault.cpp | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/include/multipass/json_utils.h b/include/multipass/json_utils.h index ce7a50bdb21..daa6875d6d1 100644 --- a/include/multipass/json_utils.h +++ b/include/multipass/json_utils.h @@ -13,8 +13,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . * - * Authored by: Alberto Aguirre - * */ #ifndef MULTIPASS_JSON_UTILS_H @@ -22,11 +20,11 @@ #include "singleton.h" -#include - #include #include +#include + #define MP_JSONUTILS multipass::JsonUtils::instance() namespace multipass diff --git a/tests/test_image_vault.cpp b/tests/test_image_vault.cpp index 4d2af0f1323..73ecc923cd8 100644 --- a/tests/test_image_vault.cpp +++ b/tests/test_image_vault.cpp @@ -18,7 +18,6 @@ #include "common.h" #include "disabling_macros.h" #include "file_operations.h" -#include "mock_file_ops.h" #include "mock_image_host.h" #include "mock_json_utils.h" #include "mock_logger.h" From 0435abe00a0407569a021904307196923aeec433 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 9 Oct 2023 23:59:48 +0100 Subject: [PATCH 491/627] [tests] Fix last failing test after `write_json` All tests finally passing. --- tests/test_daemon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_daemon.cpp b/tests/test_daemon.cpp index 90c5e2510ea..0c3857f581b 100644 --- a/tests/test_daemon.cpp +++ b/tests/test_daemon.cpp @@ -25,6 +25,7 @@ #include "mock_environment_helpers.h" #include "mock_file_ops.h" #include "mock_image_host.h" +#include "mock_json_utils.h" #include "mock_logger.h" #include "mock_platform.h" #include "mock_server_reader_writer.h" @@ -1316,6 +1317,9 @@ TEST_P(LaunchStorageCheckSuite, launch_fails_with_invalid_data_directory) config_builder.data_directory = QString("invalid_data_directory"); mp::Daemon daemon{config_builder.build()}; + auto [mock_json_utils, guard] = mpt::MockJsonUtils::inject(); + EXPECT_CALL(*mock_json_utils, write_json).Times(1); // avoid creating directory + std::stringstream stream; EXPECT_CALL(*mock_factory, create_virtual_machine(_, _)).Times(0); send_command({GetParam()}, trash_stream, stream); From 7ccf273b888b336a4254991043af1a9644ee51e4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 00:01:53 +0100 Subject: [PATCH 492/627] [snapshot] Remove outdated TODO Rollback now automatic for any callers of `write_json`. --- src/platform/backends/shared/base_snapshot.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index a0d6c7c1c0c..d842711a1fe 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -240,7 +240,6 @@ void mp::BaseSnapshot::persist() const { const std::unique_lock lock{mutex}; - // TODO@no-merge rollback const auto snapshot_filename = derive_snapshot_filename(derive_index_string(index), QString::fromStdString(name)); auto snapshot_filepath = storage_dir.filePath(snapshot_filename); MP_JSONUTILS.write_json(serialize(), snapshot_filepath); From a53120adad71f523c5680020202938257674fae7 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 13:05:43 +0100 Subject: [PATCH 493/627] [vm] Adapt rollbacks to self-persisting snapshots Since the snapshot can rollback on persistence failure, we can leave it for last and rollback only the head and count files in the VM. --- .../backends/shared/base_virtual_machine.cpp | 35 +++++++++++++------ .../backends/shared/base_virtual_machine.h | 4 +++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index eb1f1b7abad..555052ad1ac 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -510,6 +510,26 @@ void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, }); } +auto BaseVirtualMachine::make_count_file_rollback(const Path& count_path, QFile& count_file) const +{ + return sg::make_scope_guard([this, &count_path, &count_file, old_contents = std::to_string(snapshot_count), + existed = count_file.exists()]() noexcept { + count_file_rollback_helper(count_path, count_file, old_contents, existed); + }); +} + +void BaseVirtualMachine::count_file_rollback_helper(const Path& count_path, QFile& count_file, + const std::string& old_contents, bool existed) const +{ + // best effort, ignore returns + if (!existed) + count_file.remove(); + else + top_catch_all(vm_name, [&count_path, &old_contents] { + MP_UTILS.make_file_with_content(count_path.toStdString(), old_contents, yes_overwrite); + }); +} + void BaseVirtualMachine::persist_head_snapshot() const { assert(head_snapshot); @@ -517,24 +537,19 @@ void BaseVirtualMachine::persist_head_snapshot() const const auto snapshot_filename = derive_snapshot_filename(derive_index_string(snapshot_count), QString::fromStdString(head_snapshot->get_name())); - auto snapshot_filepath = instance_dir.filePath(snapshot_filename); auto head_path = derive_head_path(instance_dir); auto count_path = instance_dir.filePath(count_filename); - auto rollback_snapshot_file = sg::make_scope_guard([&snapshot_filepath]() noexcept { - QFile{snapshot_filepath}.remove(); // best effort, ignore return - }); - - MP_JSONUTILS.write_json(head_snapshot->serialize(), snapshot_filepath); - QFile head_file{head_path}; - auto head_file_rollback = make_head_file_rollback(head_path, head_file); - persist_head_snapshot_name(head_path); + + QFile count_file{count_path}; + auto count_file_rollback = make_count_file_rollback(count_path, count_file); MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), yes_overwrite); - rollback_snapshot_file.dismiss(); + head_snapshot->persist(); + count_file_rollback.dismiss(); head_file_rollback.dismiss(); } diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index ec832b234f6..850925f0979 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -101,6 +101,10 @@ class BaseVirtualMachine : public VirtualMachine bool existed) const; void persist_head_snapshot() const; + auto make_count_file_rollback(const Path& count_path, QFile& count_file) const; + void count_file_rollback_helper(const Path& count_path, QFile& count_file, const std::string& old_contents, + bool existed) const; + void persist_head_snapshot_name(const Path& head_path) const; std::string generate_snapshot_name() const; From 571de629e4a476068d6375559e93e911652f1310 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 13:33:14 +0100 Subject: [PATCH 494/627] [vm] Refactor common file rolling back --- .../backends/shared/base_virtual_machine.cpp | 51 ++++++------------- .../backends/shared/base_virtual_machine.h | 16 +++--- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 555052ad1ac..85e955d8e4c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -485,48 +485,27 @@ void BaseVirtualMachine::load_snapshot(const QJsonObject& json) } } -auto BaseVirtualMachine::make_head_file_rollback(const Path& head_path, QFile& head_file) const -{ - return sg::make_scope_guard([this, - &head_path, - &head_file, - old_head = head_snapshot->get_parents_name(), - existed = head_file.exists()]() noexcept { - head_file_rollback_helper(head_path, head_file, old_head, existed); - }); -} - -void BaseVirtualMachine::head_file_rollback_helper(const Path& head_path, - QFile& head_file, - const std::string& old_head, - bool existed) const +auto BaseVirtualMachine::make_common_file_rollback(const Path& file_path, + QFile& file, + const std::string& old_contents) const { - // best effort, ignore returns - if (!existed) - head_file.remove(); - else - top_catch_all(vm_name, [&head_path, &old_head] { - MP_UTILS.make_file_with_content(head_path.toStdString(), old_head, yes_overwrite); + return sg::make_scope_guard( + [this, &file_path, &file, old_contents = old_contents, existed = file.exists()]() noexcept { + common_file_rollback_helper(file_path, file, old_contents, existed); }); } -auto BaseVirtualMachine::make_count_file_rollback(const Path& count_path, QFile& count_file) const -{ - return sg::make_scope_guard([this, &count_path, &count_file, old_contents = std::to_string(snapshot_count), - existed = count_file.exists()]() noexcept { - count_file_rollback_helper(count_path, count_file, old_contents, existed); - }); -} - -void BaseVirtualMachine::count_file_rollback_helper(const Path& count_path, QFile& count_file, - const std::string& old_contents, bool existed) const +void BaseVirtualMachine::common_file_rollback_helper(const Path& file_path, + QFile& file, + const std::string& old_contents, + bool existed) const { // best effort, ignore returns if (!existed) - count_file.remove(); + file.remove(); else - top_catch_all(vm_name, [&count_path, &old_contents] { - MP_UTILS.make_file_with_content(count_path.toStdString(), old_contents, yes_overwrite); + top_catch_all(vm_name, [&file_path, &old_contents] { + MP_UTILS.make_file_with_content(file_path.toStdString(), old_contents, yes_overwrite); }); } @@ -541,11 +520,11 @@ void BaseVirtualMachine::persist_head_snapshot() const auto count_path = instance_dir.filePath(count_filename); QFile head_file{head_path}; - auto head_file_rollback = make_head_file_rollback(head_path, head_file); + auto head_file_rollback = make_common_file_rollback(head_path, head_file, head_snapshot->get_parents_name()); persist_head_snapshot_name(head_path); QFile count_file{count_path}; - auto count_file_rollback = make_count_file_rollback(count_path, count_file); + auto count_file_rollback = make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count)); MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), yes_overwrite); head_snapshot->persist(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 850925f0979..f205fab3be4 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -94,17 +94,13 @@ class BaseVirtualMachine : public VirtualMachine auto make_take_snapshot_rollback(SnapshotMap::iterator it); void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count); - auto make_head_file_rollback(const Path& head_path, QFile& head_file) const; - void head_file_rollback_helper(const Path& head_path, - QFile& head_file, - const std::string& old_head, - bool existed) const; - void persist_head_snapshot() const; - - auto make_count_file_rollback(const Path& count_path, QFile& count_file) const; - void count_file_rollback_helper(const Path& count_path, QFile& count_file, const std::string& old_contents, - bool existed) const; + auto make_common_file_rollback(const Path& file_path, QFile& file, const std::string& old_contents) const; + void common_file_rollback_helper(const Path& file_path, + QFile& file, + const std::string& old_contents, + bool existed) const; + void persist_head_snapshot() const; void persist_head_snapshot_name(const Path& head_path) const; std::string generate_snapshot_name() const; From 29805f64ce1af43afdfdd20a811f18ce1f8fb7a8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 16:15:54 +0100 Subject: [PATCH 495/627] [vm] Improve comment --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 85e955d8e4c..0b2132fc098 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -183,7 +183,7 @@ void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, if (old_head != head_snapshot) { assert(it->second && "snapshot not created despite modified head"); - if (snapshot_count > old_count) // snapshot was created + if (snapshot_count > old_count) // snapshot was captured { assert(snapshot_count - old_count == 1); --snapshot_count; From 006c71ce0658ce30239f29a45a84143d63bf3367 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 16:55:03 +0100 Subject: [PATCH 496/627] [snapshot] Persist snapshot deletion in snapshots --- .../backends/shared/base_snapshot.cpp | 30 ++++++++++++++++++- src/platform/backends/shared/base_snapshot.h | 6 ++-- .../backends/shared/base_virtual_machine.cpp | 19 ------------ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index d842711a1fe..df4821d8e15 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -17,14 +17,18 @@ #include "base_snapshot.h" #include "daemon/vm_specs.h" // TODO@snapshots move this -#include "multipass/json_utils.h" #include // TODO@snapshots may be able to drop after extracting JSON utilities +#include #include +#include + #include // TODO@snapshots may be able to drop after extracting JSON utilities #include +#include +#include #include namespace mp = multipass; @@ -249,3 +253,27 @@ QString mp::BaseSnapshot::derive_id() const { return snapshot_template.arg(get_name().c_str()); } + +void mp::BaseSnapshot::erase_helper() +{ + // Remove snapshot file + QTemporaryDir tmp_dir{}; + if (!tmp_dir.isValid()) + throw std::runtime_error{"Could not create temporary directory"}; + + const auto snapshot_filename = derive_snapshot_filename( + derive_index_string(index), QString::fromStdString(name)); // TODO@ricab turn into method + auto snapshot_filepath = storage_dir.filePath(snapshot_filename); + auto deleting_filepath = tmp_dir.filePath(snapshot_filename); + + if (!QFile{snapshot_filepath}.rename(deleting_filepath)) // TODO@ricab use fileops + throw std::runtime_error{ + fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; + + auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { + QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return + }); + + erase_impl(); + rollback_snapshot_file.dismiss(); +} diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 026a1a5c570..7a7609d724c 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -93,6 +93,8 @@ class BaseSnapshot : public Snapshot std::unordered_map mounts, QJsonObject metadata); + void erase_helper(); + private: std::string name; std::string comment; @@ -214,9 +216,7 @@ inline void multipass::BaseSnapshot::capture() inline void multipass::BaseSnapshot::erase() { const std::unique_lock lock{mutex}; - erase_impl(); - // TODO@no-merge persist! - // TODO@no-merge stop persisting elsewhere + erase_helper(); } inline void multipass::BaseSnapshot::apply() diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 0b2132fc098..15bb3bcc3f3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -33,7 +33,6 @@ #include #include #include -#include namespace mp = multipass; namespace mpl = multipass::logging; @@ -284,23 +283,6 @@ auto BaseVirtualMachine::make_parent_update_rollback(const std::shared_ptr& snapshot) { - // Remove snapshot file - QTemporaryDir tmp_dir{}; - if (!tmp_dir.isValid()) - throw std::runtime_error{"Could not create temporary directory"}; - - auto snapshot_fileinfo = find_snapshot_file(instance_dir, snapshot->get_name()); - auto snapshot_filepath = snapshot_fileinfo.filePath(); - auto deleting_filepath = tmp_dir.filePath(snapshot_fileinfo.fileName()); - - if (!QFile{snapshot_filepath}.rename(deleting_filepath)) - throw std::runtime_error{ - fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; - - auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { - QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return - }); - // Update head if deleted auto wrote_head = false; auto head_path = derive_head_path(instance_dir); @@ -318,7 +300,6 @@ void BaseVirtualMachine::delete_snapshot_helper(std::shared_ptr& snaps snapshot->erase(); rollback_parent_updates.dismiss(); rollback_head.dismiss(); - rollback_snapshot_file.dismiss(); } void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_parent, From 42447e04f02a989647c48daa560bf1260454a70b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 17:01:50 +0100 Subject: [PATCH 497/627] [snapshot] Refactor derivation of filename Simplify code to derive snapshot file names by using a private function that has direct access to the necessary data. --- .../backends/shared/base_snapshot.cpp | 20 +++++++++---------- src/platform/backends/shared/base_snapshot.h | 2 ++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index df4821d8e15..fe1b56a2899 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -46,11 +46,6 @@ QString derive_index_string(int index) return QString{"%1"}.arg(index, index_digits, 10, QLatin1Char('0')); } -QString derive_snapshot_filename(const QString& index, const QString& name) -{ - return QString{"%1-%2.%3"}.arg(index, name, snapshot_extension); -} - std::unordered_map load_mounts(const QJsonArray& json) { std::unordered_map mounts; @@ -244,8 +239,7 @@ void mp::BaseSnapshot::persist() const { const std::unique_lock lock{mutex}; - const auto snapshot_filename = derive_snapshot_filename(derive_index_string(index), QString::fromStdString(name)); - auto snapshot_filepath = storage_dir.filePath(snapshot_filename); + auto snapshot_filepath = storage_dir.filePath(derive_snapshot_filename()); MP_JSONUTILS.write_json(serialize(), snapshot_filepath); } @@ -261,10 +255,9 @@ void mp::BaseSnapshot::erase_helper() if (!tmp_dir.isValid()) throw std::runtime_error{"Could not create temporary directory"}; - const auto snapshot_filename = derive_snapshot_filename( - derive_index_string(index), QString::fromStdString(name)); // TODO@ricab turn into method - auto snapshot_filepath = storage_dir.filePath(snapshot_filename); - auto deleting_filepath = tmp_dir.filePath(snapshot_filename); + const auto snapshot_filename = derive_snapshot_filename(); + const auto snapshot_filepath = storage_dir.filePath(snapshot_filename); + const auto deleting_filepath = tmp_dir.filePath(snapshot_filename); if (!QFile{snapshot_filepath}.rename(deleting_filepath)) // TODO@ricab use fileops throw std::runtime_error{ @@ -277,3 +270,8 @@ void mp::BaseSnapshot::erase_helper() erase_impl(); rollback_snapshot_file.dismiss(); } + +QString mp::BaseSnapshot::derive_snapshot_filename() const +{ + return QString{"%1-%2.%3"}.arg(derive_index_string(index), QString::fromStdString(name), snapshot_extension); +} diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 7a7609d724c..0b30d0bfc24 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -24,6 +24,7 @@ #include #include +#include #include @@ -94,6 +95,7 @@ class BaseSnapshot : public Snapshot QJsonObject metadata); void erase_helper(); + QString derive_snapshot_filename() const; private: std::string name; From 6ee24cce5cc8d485272902d035499ca5bbbc301d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 17:10:11 +0100 Subject: [PATCH 498/627] [snapshot] Use FileOps to rename files --- src/platform/backends/shared/base_snapshot.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index fe1b56a2899..4157fd9cfb8 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -18,6 +18,7 @@ #include "base_snapshot.h" #include "daemon/vm_specs.h" // TODO@snapshots move this +#include #include // TODO@snapshots may be able to drop after extracting JSON utilities #include #include @@ -259,12 +260,14 @@ void mp::BaseSnapshot::erase_helper() const auto snapshot_filepath = storage_dir.filePath(snapshot_filename); const auto deleting_filepath = tmp_dir.filePath(snapshot_filename); - if (!QFile{snapshot_filepath}.rename(deleting_filepath)) // TODO@ricab use fileops + QFile snapshot_file{snapshot_filepath}; + if (!MP_FILEOPS.rename(snapshot_file, deleting_filepath)) throw std::runtime_error{ fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { - QFile{deleting_filepath}.rename(snapshot_filepath); // best effort, ignore return + QFile temp_file{deleting_filepath}; + MP_FILEOPS.rename(temp_file, snapshot_filepath); // best effort, ignore return }); erase_impl(); From 08f555f71d3c3095410ba48d51d7be7fad168075 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 17:42:17 +0100 Subject: [PATCH 499/627] [snapshot] Persist snapshot when modifying parent --- src/platform/backends/shared/base_snapshot.h | 2 +- .../backends/shared/base_virtual_machine.cpp | 44 +++++-------------- .../backends/shared/base_virtual_machine.h | 5 +-- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 0b30d0bfc24..dad8e420ffc 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -203,7 +203,7 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) { const std::unique_lock lock{mutex}; parent = std::move(p); - // TODO@no-merge persist! + persist(); // TODO@no-merge stop persisting elsewhere } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 15bb3bcc3f3..109c68503a3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -70,29 +70,11 @@ QString derive_snapshot_filename(const QString& index, const QString& name) return QString{"%1-%2.%3"}.arg(index, name, snapshot_extension); } -QFileInfo find_snapshot_file(const QDir& snapshot_dir, const std::string& snapshot_name) -{ - auto index_wildcard = "????"; - auto pattern = derive_snapshot_filename(index_wildcard, QString::fromStdString(snapshot_name)); - auto files = MP_FILEOPS.entryInfoList(snapshot_dir, {pattern}, QDir::Filter::Files | QDir::Filter::Readable); - - if (auto num_found = files.count(); !num_found) - throw std::runtime_error{fmt::format("Could not find snapshot file for pattern '{}'", pattern)}; - else if (num_found > 1) - throw std::runtime_error{fmt::format("Multiple snapshot files found for pattern '{}'", pattern)}; - - return files.first(); -} - void update_parents_rollback_helper(const std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) + std::vector& updated_parents) { - for (auto [snapshot, snapshot_filepath] : updated_snapshot_paths) - { + for (auto snapshot : updated_parents) snapshot->set_parent(deleted_parent); - if (!snapshot_filepath.isEmpty()) - MP_JSONUTILS.write_json(snapshot->serialize(), snapshot_filepath); - } } } // namespace @@ -274,10 +256,10 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, } auto BaseVirtualMachine::make_parent_update_rollback(const std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) const + std::vector& updated_parents) const { - return sg::make_scope_guard([this, &updated_snapshot_paths, deleted_parent]() noexcept { - top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_snapshot_paths); + return sg::make_scope_guard([this, &updated_parents, deleted_parent]() noexcept { + top_catch_all(vm_name, &update_parents_rollback_helper, deleted_parent, updated_parents); }); } @@ -290,11 +272,11 @@ void BaseVirtualMachine::delete_snapshot_helper(std::shared_ptr& snaps wrote_head = updated_deleted_head(snapshot, head_path); // Update children of deleted snapshot - std::unordered_map updated_snapshot_paths; - updated_snapshot_paths.reserve(snapshots.size()); + std::vector updated_parents{}; + updated_parents.reserve(snapshots.size()); - auto rollback_parent_updates = make_parent_update_rollback(snapshot, updated_snapshot_paths); - update_parents(snapshot, updated_snapshot_paths); + auto rollback_parent_updates = make_parent_update_rollback(snapshot, updated_parents); + update_parents(snapshot, updated_parents); // Erase the snapshot with the backend and dismiss rollbacks on success snapshot->erase(); @@ -303,7 +285,7 @@ void BaseVirtualMachine::delete_snapshot_helper(std::shared_ptr& snaps } void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) + std::vector& updated_parents) { auto new_parent = deleted_parent->get_parent(); for (auto& [ignore, other] : snapshots) @@ -311,11 +293,7 @@ void BaseVirtualMachine::update_parents(std::shared_ptr& deleted_paren if (other->get_parent() == deleted_parent) { other->set_parent(new_parent); - updated_snapshot_paths[other.get()]; - - const auto other_filepath = find_snapshot_file(instance_dir, other->get_name()).filePath(); - MP_JSONUTILS.write_json(other->serialize(), other_filepath); - updated_snapshot_paths[other.get()] = other_filepath; + updated_parents.push_back(other.get()); } } } diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index f205fab3be4..3594fe3e560 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -119,10 +119,9 @@ class BaseVirtualMachine : public VirtualMachine const bool& wrote_head, std::shared_ptr& old_head); - void update_parents(std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths); + void update_parents(std::shared_ptr& deleted_parent, std::vector& updated_parents); auto make_parent_update_rollback(const std::shared_ptr& deleted_parent, - std::unordered_map& updated_snapshot_paths) const; + std::vector& updated_snapshot_paths) const; void delete_snapshot_helper(std::shared_ptr& snapshot); From 1d346dd275a67fbb074a43288e129d66567da785 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 17:53:37 +0100 Subject: [PATCH 500/627] [vm] Prune obsolete code --- .../backends/shared/base_virtual_machine.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 109c68503a3..52bac867da2 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -60,16 +59,6 @@ mp::Path derive_head_path(const QDir& snapshot_dir) return snapshot_dir.filePath(head_filename); } -QString derive_index_string(int index) -{ - return QString{"%1"}.arg(index, index_digits, 10, QLatin1Char('0')); -} - -QString derive_snapshot_filename(const QString& index, const QString& name) -{ - return QString{"%1-%2.%3"}.arg(index, name, snapshot_extension); -} - void update_parents_rollback_helper(const std::shared_ptr& deleted_parent, std::vector& updated_parents) { @@ -472,9 +461,6 @@ void BaseVirtualMachine::persist_head_snapshot() const { assert(head_snapshot); - const auto snapshot_filename = derive_snapshot_filename(derive_index_string(snapshot_count), - QString::fromStdString(head_snapshot->get_name())); - auto head_path = derive_head_path(instance_dir); auto count_path = instance_dir.filePath(count_filename); From e47108edd9adc5b4feaa2837ec84929bad85300e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 18:56:38 +0100 Subject: [PATCH 501/627] [snapshot] Persist renaming in snapshots --- .../backends/shared/base_snapshot.cpp | 26 +++++++++++++++---- src/platform/backends/shared/base_snapshot.h | 16 +----------- .../backends/shared/base_virtual_machine.cpp | 4 +-- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 4157fd9cfb8..4691f59d34d 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -249,27 +249,43 @@ QString mp::BaseSnapshot::derive_id() const return snapshot_template.arg(get_name().c_str()); } -void mp::BaseSnapshot::erase_helper() +auto mp::BaseSnapshot::erase_helper() { // Remove snapshot file - QTemporaryDir tmp_dir{}; - if (!tmp_dir.isValid()) + auto tmp_dir = std::make_unique(); // work around no move ctor + if (!tmp_dir->isValid()) throw std::runtime_error{"Could not create temporary directory"}; const auto snapshot_filename = derive_snapshot_filename(); const auto snapshot_filepath = storage_dir.filePath(snapshot_filename); - const auto deleting_filepath = tmp_dir.filePath(snapshot_filename); + const auto deleting_filepath = tmp_dir->filePath(snapshot_filename); QFile snapshot_file{snapshot_filepath}; if (!MP_FILEOPS.rename(snapshot_file, deleting_filepath)) throw std::runtime_error{ fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; - auto rollback_snapshot_file = sg::make_scope_guard([&deleting_filepath, &snapshot_filepath]() noexcept { + return sg::make_scope_guard([tmp_dir = std::move(tmp_dir), &deleting_filepath, &snapshot_filepath]() noexcept { QFile temp_file{deleting_filepath}; MP_FILEOPS.rename(temp_file, snapshot_filepath); // best effort, ignore return }); +} + +void mp::BaseSnapshot::set_name(const std::string& n) +{ + const std::unique_lock lock{mutex}; + auto rollback_old_file = erase_helper(); + + name = n; + persist(); + rollback_old_file.dismiss(); +} + +void multipass::BaseSnapshot::erase() +{ + const std::unique_lock lock{mutex}; + auto rollback_snapshot_file = erase_helper(); erase_impl(); rollback_snapshot_file.dismiss(); } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index dad8e420ffc..8fa1ff4c0c5 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -94,7 +94,7 @@ class BaseSnapshot : public Snapshot std::unordered_map mounts, QJsonObject metadata); - void erase_helper(); + auto erase_helper(); QString derive_snapshot_filename() const; private: @@ -184,14 +184,6 @@ inline const QJsonObject& multipass::BaseSnapshot::get_metadata() const noexcept return metadata; } -inline void multipass::BaseSnapshot::set_name(const std::string& n) -{ - const std::unique_lock lock{mutex}; - name = n; - // TODO@no-merge delete current file - // TODO@no-merge persist! -} - inline void multipass::BaseSnapshot::set_comment(const std::string& c) { const std::unique_lock lock{mutex}; @@ -215,12 +207,6 @@ inline void multipass::BaseSnapshot::capture() // TODO@no-merge stop persisting elsewhere } -inline void multipass::BaseSnapshot::erase() -{ - const std::unique_lock lock{mutex}; - erase_helper(); -} - inline void multipass::BaseSnapshot::apply() { const std::unique_lock lock{mutex}; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 52bac867da2..55fb3d943c4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -317,7 +317,7 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: throw SnapshotNameTaken{vm_name, new_name}; auto snapshot_node = snapshots.extract(old_it); - auto reinsert_guard = make_reinsert_guard(snapshot_node); // we want this to execute both on failure and success + const auto reinsert_guard = make_reinsert_guard(snapshot_node); // we want this to execute both on failure & success snapshot_node.key() = new_name; snapshot_node.mapped()->set_name(new_name); @@ -338,7 +338,7 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } -void BaseVirtualMachine::load_snapshots() +void BaseVirtualMachine::load_snapshots() // TODO@no-merge let snapshots load themselves { std::unique_lock lock{snapshot_mutex}; From c44e6da080ede7ca27d3eaac369003ceef47b0c1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 19:17:42 +0100 Subject: [PATCH 502/627] [snapshot] Change format of ID to use in backend Some backends (notably QEMU, at least with qemu-img) cannot rename snapshots. Therefore, avoid including the name in the ID that is used to name/tag the snapshot in the backend. This allows identifying backend snapshot that correspond to multipass snapshots whose name changed. --- src/platform/backends/shared/base_snapshot.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 4691f59d34d..f3e67304797 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -39,8 +39,9 @@ namespace constexpr auto snapshot_extension = "snapshot.json"; constexpr auto index_digits = 4; // these two go together constexpr auto max_snapshots = 1000; -const auto snapshot_template = QStringLiteral("@%1"); /* avoid colliding with suspension snapshots; prefix with a char - that can't be part of the name, to avoid confusion */ +const auto snapshot_template = QStringLiteral("@s%1"); /* avoid confusion with snapshot names by prepending a character + that can't be part of the name (users can call a snapshot + "s1", but they cannot call it "@s1") */ QString derive_index_string(int index) { @@ -246,7 +247,7 @@ void mp::BaseSnapshot::persist() const QString mp::BaseSnapshot::derive_id() const { - return snapshot_template.arg(get_name().c_str()); + return snapshot_template.arg(index); } auto mp::BaseSnapshot::erase_helper() From 8abe01a356cbfa82295f419f995b7e94191bb72d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 00:00:26 +0100 Subject: [PATCH 503/627] [snapshot] Add index and parent-index getters --- include/multipass/snapshot.h | 10 ++++++---- src/platform/backends/shared/base_snapshot.h | 21 ++++++++++++++++---- tests/stub_snapshot.h | 10 ++++++++++ 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index c34a4f28f51..9131bc0eb92 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -38,13 +38,10 @@ class Snapshot : private DisabledCopyMove public: // TODO@snapshots drop any accessors that we end up not needing virtual ~Snapshot() = default; + virtual int get_index() const = 0; virtual std::string get_name() const = 0; virtual std::string get_comment() const = 0; - virtual std::string get_parents_name() const = 0; virtual QDateTime get_creation_timestamp() const = 0; - virtual std::shared_ptr get_parent() const = 0; - virtual std::shared_ptr get_parent() = 0; - virtual int get_num_cores() const noexcept = 0; virtual MemorySize get_mem_size() const noexcept = 0; virtual MemorySize get_disk_space() const noexcept = 0; @@ -54,6 +51,11 @@ class Snapshot : private DisabledCopyMove virtual const std::unordered_map& get_mounts() const noexcept = 0; virtual const QJsonObject& get_metadata() const noexcept = 0; + virtual std::shared_ptr get_parent() const = 0; + virtual std::shared_ptr get_parent() = 0; + virtual std::string get_parents_name() const = 0; + virtual int get_parents_index() const = 0; // TODO@no-merge use this to reference parents in json and head_file + virtual QJsonObject serialize() const = 0; // TODO@no-merge remove virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 8fa1ff4c0c5..6f42c7b7193 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -42,13 +42,10 @@ class BaseSnapshot : public Snapshot VirtualMachine& vm); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); + int get_index() const override; std::string get_name() const override; std::string get_comment() const override; QDateTime get_creation_timestamp() const override; - std::string get_parents_name() const override; - std::shared_ptr get_parent() const override; - std::shared_ptr get_parent() override; - int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; MemorySize get_disk_space() const noexcept override; @@ -58,6 +55,11 @@ class BaseSnapshot : public Snapshot const std::unordered_map& get_mounts() const noexcept override; const QJsonObject& get_metadata() const noexcept override; + std::shared_ptr get_parent() const override; + std::shared_ptr get_parent() override; + std::string get_parents_name() const override; + int get_parents_index() const override; + QJsonObject serialize() const override; void persist() const override; @@ -129,6 +131,11 @@ inline std::string multipass::BaseSnapshot::get_comment() const return comment; } +inline int multipass::BaseSnapshot::get_index() const +{ + return index; +} + inline QDateTime multipass::BaseSnapshot::get_creation_timestamp() const { return creation_timestamp; @@ -143,6 +150,12 @@ inline std::string multipass::BaseSnapshot::get_parents_name() const return par ? par->get_name() : ""; } +inline int multipass::BaseSnapshot::get_parents_index() const +{ + std::unique_lock lock{mutex}; + return parent ? parent->get_index() : 0; // this doesn't lock +} + inline auto multipass::BaseSnapshot::get_parent() const -> std::shared_ptr { const std::unique_lock lock{mutex}; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 00537c52bf3..098a297c89b 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -57,6 +57,16 @@ struct StubSnapshot : public Snapshot return nullptr; } + int get_index() const override + { + return 0; + } + + int get_parents_index() const override + { + return 0; + } + int get_num_cores() const noexcept override { return 0; From 356558713f0d10783803e4506f750c73304bb3f1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 23:05:06 +0100 Subject: [PATCH 504/627] [snapshot] Delete obsolete TODO --- include/multipass/snapshot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 9131bc0eb92..fb71a84aa43 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -59,7 +59,7 @@ class Snapshot : private DisabledCopyMove virtual QJsonObject serialize() const = 0; // TODO@no-merge remove virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? - virtual void set_name(const std::string&) = 0; // TODO@snapshots don't forget to rename json file + virtual void set_name(const std::string&) = 0; virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; From f442778ec1f43fb7849029db3d5ea9984212ffc2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 23:12:34 +0100 Subject: [PATCH 505/627] [vm] Move inline method implementation --- src/platform/backends/shared/base_virtual_machine.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 3594fe3e560..c2031b776be 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -55,10 +55,7 @@ class BaseVirtualMachine : public VirtualMachine }; SnapshotVista view_snapshots() const noexcept override; - inline int get_num_snapshots() const noexcept override - { - return static_cast(snapshots.size()); - } + int get_num_snapshots() const noexcept override; std::shared_ptr get_snapshot(const std::string& name) const override; std::shared_ptr get_snapshot(const std::string& name) override; @@ -134,6 +131,11 @@ class BaseVirtualMachine : public VirtualMachine } // namespace multipass +inline int multipass::BaseVirtualMachine::get_num_snapshots() const noexcept +{ + return static_cast(snapshots.size()); +} + inline int multipass::BaseVirtualMachine::get_snapshot_count() const { const std::unique_lock lock{snapshot_mutex}; From 2445d60d51b4be2801622f422bfa6db68d50e42e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 23:18:00 +0100 Subject: [PATCH 506/627] [vm] Allow fetching snapshots by index --- include/multipass/virtual_machine.h | 4 ++++ .../backends/shared/base_virtual_machine.cpp | 17 +++++++++++++++++ .../backends/shared/base_virtual_machine.h | 3 +++ tests/mock_virtual_machine.h | 2 ++ tests/stub_virtual_machine.h | 10 ++++++++++ 5 files changed, 36 insertions(+) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 7cfa3a03622..b70aa2056c1 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -90,8 +90,12 @@ class VirtualMachine : private DisabledCopyMove using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views virtual SnapshotVista view_snapshots() const noexcept = 0; virtual int get_num_snapshots() const noexcept = 0; + virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; + virtual std::shared_ptr get_snapshot(int index) const = 0; virtual std::shared_ptr get_snapshot(const std::string& name) = 0; + virtual std::shared_ptr get_snapshot(int index) = 0; + virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 55fb3d943c4..98cbd66505e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -141,11 +141,28 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri } } +std::shared_ptr BaseVirtualMachine::get_snapshot(int index) const +{ + const std::unique_lock lock{snapshot_mutex}; + + auto index_matcher = [index](const auto& elem) { return elem.second->get_index() == index; }; + if (auto it = std::find_if(snapshots.begin(), snapshots.end(), index_matcher); it != snapshots.end()) + return it->second; + + throw std::runtime_error{ + fmt::format("No snapshot with given index in instance; instance name: {}; snapshot index: {}", vm_name, index)}; +} + std::shared_ptr BaseVirtualMachine::get_snapshot(const std::string& name) { return std::const_pointer_cast(std::as_const(*this).get_snapshot(name)); } +std::shared_ptr BaseVirtualMachine::get_snapshot(int index) +{ + return std::const_pointer_cast(std::as_const(*this).get_snapshot(index)); +} + void BaseVirtualMachine::take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count) diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index c2031b776be..02aa0b0eb55 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -56,8 +56,11 @@ class BaseVirtualMachine : public VirtualMachine SnapshotVista view_snapshots() const noexcept override; int get_num_snapshots() const noexcept override; + std::shared_ptr get_snapshot(const std::string& name) const override; + std::shared_ptr get_snapshot(int index) const override; std::shared_ptr get_snapshot(const std::string& name) override; + std::shared_ptr get_snapshot(int index) override; // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index b70b12becc4..813f306526c 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -78,7 +78,9 @@ struct MockVirtualMachineT : public T MOCK_METHOD(VirtualMachine::SnapshotVista, view_snapshots, (), (const, override, noexcept)); MOCK_METHOD(int, get_num_snapshots, (), (const, override, noexcept)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (const, override)); + MOCK_METHOD(std::shared_ptr, get_snapshot, (int index), (const, override)); MOCK_METHOD(std::shared_ptr, get_snapshot, (const std::string&), (override)); + MOCK_METHOD(std::shared_ptr, get_snapshot, (int index), (override)); MOCK_METHOD(std::shared_ptr, take_snapshot, (const VMSpecs&, const std::string&, const std::string&), diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 0dba61f8591..10916df3e16 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -146,6 +146,16 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } + std::shared_ptr get_snapshot(int index) const override + { + return nullptr; + } + + std::shared_ptr get_snapshot(int index) override + { + return nullptr; + } + std::shared_ptr take_snapshot(const VMSpecs&, const std::string&, const std::string&) override { return {}; From fa6abb5cfb57b6af900e0bea5fb8d33e54900343 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 10 Oct 2023 23:26:24 +0100 Subject: [PATCH 507/627] [snapshot] Persist and load parents by index --- src/platform/backends/shared/base_snapshot.cpp | 10 +++++----- src/platform/backends/shared/base_snapshot.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f3e67304797..1f887924505 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -84,16 +84,16 @@ std::unordered_map load_mounts(const QJsonArray& json) std::shared_ptr find_parent(const QJsonObject& json, mp::VirtualMachine& vm) { - auto parent_name = json["parent"].toString().toStdString(); + auto parent_idx = json["parent"].toInt(); try { - return parent_name.empty() ? nullptr : vm.get_snapshot(parent_name); + return parent_idx ? vm.get_snapshot(parent_idx) : nullptr; } catch (std::out_of_range&) { - throw std::runtime_error{fmt::format("Missing snapshot parent. Snapshot name: {}; parent name: {}", + throw std::runtime_error{fmt::format("Missing snapshot parent. Snapshot name: {}; parent index: {}", json["name"].toString(), - parent_name)}; + parent_idx)}; } } } // namespace @@ -184,7 +184,7 @@ QJsonObject mp::BaseSnapshot::serialize() const snapshot.insert("name", QString::fromStdString(name)); snapshot.insert("comment", QString::fromStdString(comment)); - snapshot.insert("parent", QString::fromStdString(get_parents_name())); + snapshot.insert("parent", get_parents_index()); snapshot.insert("index", index); snapshot.insert("creation_timestamp", creation_timestamp.toString(Qt::ISODateWithMs)); snapshot.insert("num_cores", num_cores); diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 6f42c7b7193..500258173d7 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -42,6 +42,7 @@ class BaseSnapshot : public Snapshot VirtualMachine& vm); BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); + // TODO@snapshots tag as noexcept those that can be int get_index() const override; std::string get_name() const override; std::string get_comment() const override; From 337bd416966abf894334fee38ffda87dc48da65d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 11:04:01 +0100 Subject: [PATCH 508/627] [vm] Tag loaded JSON as const --- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 98cbd66505e..abf8390d086 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -427,7 +427,7 @@ void BaseVirtualMachine::load_snapshot_from_file(const QString& filename) QJsonParseError parse_error{}; const auto& data = MP_FILEOPS.read_all(file); - if (auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) + if (const auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", file.fileName(), parse_error.errorString())}; From 83880b4da24407c3fae573b03ef42db6e3e05344 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 12:47:55 +0100 Subject: [PATCH 509/627] [vm] Save head snapshot index rather than name Frees renaming from having to update saved head. --- include/multipass/snapshot.h | 2 +- .../backends/shared/base_virtual_machine.cpp | 24 ++++++++++--------- .../backends/shared/base_virtual_machine.h | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index fb71a84aa43..bef4f7c805c 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -54,7 +54,7 @@ class Snapshot : private DisabledCopyMove virtual std::shared_ptr get_parent() const = 0; virtual std::shared_ptr get_parent() = 0; virtual std::string get_parents_name() const = 0; - virtual int get_parents_index() const = 0; // TODO@no-merge use this to reference parents in json and head_file + virtual int get_parents_index() const = 0; virtual QJsonObject serialize() const = 0; // TODO@no-merge remove virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index abf8390d086..43bcf819aeb 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -233,7 +233,7 @@ bool BaseVirtualMachine::updated_deleted_head(std::shared_ptr& snapsho if (head_snapshot == snapshot) { head_snapshot = snapshot->get_parent(); - persist_head_snapshot_name(head_path); + persist_head_snapshot_index(head_path); return true; } @@ -256,7 +256,8 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, head_snapshot = std::move(old_head); if (wrote_head) top_catch_all(vm_name, [this, &head_path] { - MP_UTILS.make_file_with_content(head_path.toStdString(), head_snapshot->get_name(), yes_overwrite); + MP_UTILS.make_file_with_content(head_path.toStdString(), std::to_string(head_snapshot->get_index()), + yes_overwrite); }); } } @@ -386,8 +387,8 @@ void BaseVirtualMachine::load_generic_snapshot_info() { snapshot_count = std::stoi(mpu::contents_of(instance_dir.filePath(count_filename))); - auto head_name = mpu::contents_of(instance_dir.filePath(head_filename)); - head_snapshot = head_name.empty() ? nullptr : get_snapshot(head_name); + auto head_index = std::stoi(mpu::contents_of(instance_dir.filePath(head_filename))); + head_snapshot = head_index ? get_snapshot(head_index) : nullptr; } catch (FileOpenFailedException&) { @@ -482,8 +483,9 @@ void BaseVirtualMachine::persist_head_snapshot() const auto count_path = instance_dir.filePath(count_filename); QFile head_file{head_path}; - auto head_file_rollback = make_common_file_rollback(head_path, head_file, head_snapshot->get_parents_name()); - persist_head_snapshot_name(head_path); + auto head_file_rollback = + make_common_file_rollback(head_path, head_file, std::to_string(head_snapshot->get_parents_index())); + persist_head_snapshot_index(head_path); QFile count_file{count_path}; auto count_file_rollback = make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count)); @@ -494,10 +496,10 @@ void BaseVirtualMachine::persist_head_snapshot() const head_file_rollback.dismiss(); } -void BaseVirtualMachine::persist_head_snapshot_name(const Path& head_path) const +void BaseVirtualMachine::persist_head_snapshot_index(const Path& head_path) const { - auto head_name = head_snapshot ? head_snapshot->get_name() : ""; - MP_UTILS.make_file_with_content(head_path.toStdString(), head_name, yes_overwrite); + auto head_index = head_snapshot ? head_snapshot->get_index() : 0; + MP_UTILS.make_file_with_content(head_path.toStdString(), std::to_string(head_index), yes_overwrite); } std::string BaseVirtualMachine::generate_snapshot_name() const @@ -529,7 +531,7 @@ void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, if (old_head != head_snapshot) { head_snapshot = old_head; - persist_head_snapshot_name(head_path); + persist_head_snapshot_index(head_path); } } @@ -558,7 +560,7 @@ void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& spec if (head_snapshot != snapshot) { head_snapshot = snapshot; - persist_head_snapshot_name(head_path); + persist_head_snapshot_index(head_path); } rollback.dismiss(); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 02aa0b0eb55..7af1249dd8f 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -101,7 +101,7 @@ class BaseVirtualMachine : public VirtualMachine bool existed) const; void persist_head_snapshot() const; - void persist_head_snapshot_name(const Path& head_path) const; + void persist_head_snapshot_index(const Path& head_path) const; std::string generate_snapshot_name() const; template From fcb2ed66d464d02176872e9623441720ac65e56e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 12:06:28 +0100 Subject: [PATCH 510/627] [vm] Allow boundary space in snapshot files --- .../backends/shared/base_virtual_machine.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 43bcf819aeb..54a073a6835 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -33,6 +33,8 @@ #include #include +#include + namespace mp = multipass; namespace mpl = multipass::logging; namespace mpu = multipass::utils; @@ -65,6 +67,16 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted for (auto snapshot : updated_parents) snapshot->set_parent(deleted_parent); } + +std::string trim(const std::string& s) +{ + return std::regex_replace(s, std::regex{R"(^\s+|\s+$)"}, ""); +} + +std::string trimmed_contents_of(const QString& file_path) +{ + return trim(mpu::contents_of(file_path)); +} } // namespace namespace multipass @@ -385,9 +397,9 @@ void BaseVirtualMachine::load_generic_snapshot_info() { try { - snapshot_count = std::stoi(mpu::contents_of(instance_dir.filePath(count_filename))); + snapshot_count = std::stoi(trimmed_contents_of(instance_dir.filePath(count_filename))); - auto head_index = std::stoi(mpu::contents_of(instance_dir.filePath(head_filename))); + auto head_index = std::stoi(trimmed_contents_of(instance_dir.filePath(head_filename))); head_snapshot = head_index ? get_snapshot(head_index) : nullptr; } catch (FileOpenFailedException&) From 79503cf28b5cbc9413645557521ab68ce7343fec Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 12:09:47 +0100 Subject: [PATCH 511/627] [vm] Add a newline at the end of snapshot files To make them friendlier to humans (to `cat`, for instance). --- .../backends/shared/base_virtual_machine.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 54a073a6835..e1a53249760 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -268,8 +268,8 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, head_snapshot = std::move(old_head); if (wrote_head) top_catch_all(vm_name, [this, &head_path] { - MP_UTILS.make_file_with_content(head_path.toStdString(), std::to_string(head_snapshot->get_index()), - yes_overwrite); + MP_UTILS.make_file_with_content(head_path.toStdString(), + std::to_string(head_snapshot->get_index()) + "\n", yes_overwrite); }); } } @@ -496,12 +496,12 @@ void BaseVirtualMachine::persist_head_snapshot() const QFile head_file{head_path}; auto head_file_rollback = - make_common_file_rollback(head_path, head_file, std::to_string(head_snapshot->get_parents_index())); + make_common_file_rollback(head_path, head_file, std::to_string(head_snapshot->get_parents_index()) + "\n"); persist_head_snapshot_index(head_path); QFile count_file{count_path}; - auto count_file_rollback = make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count)); - MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count), yes_overwrite); + auto count_file_rollback = make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count) + "\n"); + MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count) + "\n", yes_overwrite); head_snapshot->persist(); count_file_rollback.dismiss(); @@ -511,7 +511,7 @@ void BaseVirtualMachine::persist_head_snapshot() const void BaseVirtualMachine::persist_head_snapshot_index(const Path& head_path) const { auto head_index = head_snapshot ? head_snapshot->get_index() : 0; - MP_UTILS.make_file_with_content(head_path.toStdString(), std::to_string(head_index), yes_overwrite); + MP_UTILS.make_file_with_content(head_path.toStdString(), std::to_string(head_index) + "\n", yes_overwrite); } std::string BaseVirtualMachine::generate_snapshot_name() const From fc7a8d417a58973596474beef8af1ac9f597cc26 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 12:56:50 +0100 Subject: [PATCH 512/627] [snapshot] Reduce visibility of `serialize` method --- include/multipass/snapshot.h | 4 +--- src/platform/backends/shared/base_snapshot.h | 5 ++--- tests/stub_snapshot.h | 5 ----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index bef4f7c805c..ce126b1cc11 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -56,9 +56,6 @@ class Snapshot : private DisabledCopyMove virtual std::string get_parents_name() const = 0; virtual int get_parents_index() const = 0; - virtual QJsonObject serialize() const = 0; // TODO@no-merge remove - virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? - virtual void set_name(const std::string&) = 0; virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; @@ -66,6 +63,7 @@ class Snapshot : private DisabledCopyMove virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits virtual void apply() = 0; + virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? }; } // namespace multipass diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 500258173d7..a353ef2c674 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -61,9 +61,6 @@ class BaseSnapshot : public Snapshot std::string get_parents_name() const override; int get_parents_index() const override; - QJsonObject serialize() const override; - void persist() const override; - void set_name(const std::string& n) override; void set_comment(const std::string& c) override; void set_parent(std::shared_ptr p) override; @@ -71,6 +68,7 @@ class BaseSnapshot : public Snapshot void capture() final; void erase() final; void apply() final; + void persist() const override; protected: virtual void capture_impl() = 0; @@ -99,6 +97,7 @@ class BaseSnapshot : public Snapshot auto erase_helper(); QString derive_snapshot_filename() const; + QJsonObject serialize() const; private: std::string name; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 098a297c89b..86fe1e61908 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -97,11 +97,6 @@ struct StubSnapshot : public Snapshot return metadata; } - QJsonObject serialize() const override - { - return {}; - } - void persist() const override { } From ea29a86887694962a8e287036f5b81a1ced7cedf Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 13 Oct 2023 12:17:37 +0100 Subject: [PATCH 513/627] [snapshot] Construct snapshots from filename --- .../libvirt/libvirt_virtual_machine.cpp | 2 +- .../libvirt/libvirt_virtual_machine.h | 2 +- .../backends/lxd/lxd_virtual_machine.cpp | 2 +- .../backends/lxd/lxd_virtual_machine.h | 3 +-- src/platform/backends/qemu/qemu_snapshot.cpp | 4 +-- src/platform/backends/qemu/qemu_snapshot.h | 2 +- .../backends/qemu/qemu_virtual_machine.cpp | 4 +-- .../backends/qemu/qemu_virtual_machine.h | 2 +- .../backends/shared/base_snapshot.cpp | 26 +++++++++++++++--- src/platform/backends/shared/base_snapshot.h | 7 ++--- .../backends/shared/base_virtual_machine.cpp | 27 +++---------------- .../backends/shared/base_virtual_machine.h | 5 ++-- tests/test_base_virtual_machine.cpp | 2 +- 13 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 565445e7586..5effb308e39 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -564,7 +564,7 @@ auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } -auto mp::LibVirtVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr +auto mp::LibVirtVirtualMachine::make_specific_snapshot(const QString& /*filename*/) -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index db4c475bc9d..b351c2913b0 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -62,7 +62,7 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine static ConnectionUPtr open_libvirt_connection(const LibvirtWrapper::UPtr& libvirt_wrapper); protected: - std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; + std::shared_ptr make_specific_snapshot(const QString& filename) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 1230677214c..494155f6319 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -495,7 +495,7 @@ auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& name, throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } -std::shared_ptr mp::LXDVirtualMachine::make_specific_snapshot(const QJsonObject& json) +std::shared_ptr mp::LXDVirtualMachine::make_specific_snapshot(const QString& /*filename*/) { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index ffcd559d4d9..fd5c882478e 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -18,7 +18,6 @@ #ifndef MULTIPASS_LXD_VIRTUAL_MACHINE_H #define MULTIPASS_LXD_VIRTUAL_MACHINE_H -#include #include #include @@ -62,7 +61,7 @@ class LXDVirtualMachine : public BaseVirtualMachine const std::string& target, const VMMount& mount) override; protected: - std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; + std::shared_ptr make_specific_snapshot(const QString& filename) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 77994c3db06..b3f15063258 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -64,8 +64,8 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, { } -mp::QemuSnapshot::QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc) - : BaseSnapshot(json, vm), desc{desc}, image_path{desc.image.image_path} +mp::QemuSnapshot::QemuSnapshot(const QString& filename, QemuVirtualMachine& vm, VirtualMachineDescription& desc) + : BaseSnapshot(filename, vm), desc{desc}, image_path{desc.image.image_path} { } diff --git a/src/platform/backends/qemu/qemu_snapshot.h b/src/platform/backends/qemu/qemu_snapshot.h index 09cdcd5d014..caff224631f 100644 --- a/src/platform/backends/qemu/qemu_snapshot.h +++ b/src/platform/backends/qemu/qemu_snapshot.h @@ -38,7 +38,7 @@ class QemuSnapshot : public BaseSnapshot const VMSpecs& specs, QemuVirtualMachine& vm, VirtualMachineDescription& desc); - QemuSnapshot(const QJsonObject& json, QemuVirtualMachine& vm, VirtualMachineDescription& desc); + QemuSnapshot(const QString& filename, QemuVirtualMachine& vm, VirtualMachineDescription& desc); protected: void capture_impl() override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index ffde1407900..36c3798dde2 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -630,7 +630,7 @@ auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, return std::make_shared(name, comment, std::move(parent), specs, *this, desc); } -auto mp::QemuVirtualMachine::make_specific_snapshot(const QJsonObject& json) -> std::shared_ptr +auto mp::QemuVirtualMachine::make_specific_snapshot(const QString& filename) -> std::shared_ptr { - return std::make_shared(json, *this, desc); + return std::make_shared(filename, *this, desc); } diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 6d23b237c14..0ed3f636765 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -77,7 +77,7 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine { } - std::shared_ptr make_specific_snapshot(const QJsonObject& json) override; + std::shared_ptr make_specific_snapshot(const QString& filename) override; std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 1f887924505..b04a9c47e14 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -48,6 +49,25 @@ QString derive_index_string(int index) return QString{"%1"}.arg(index, index_digits, 10, QLatin1Char('0')); } +QJsonObject read_snapshot_json(const QString& filename) +{ + QFile file{filename}; + if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) + throw std::runtime_error{fmt::v9::format("Could not open snapshot file for for reading: {}", file.fileName())}; + + QJsonParseError parse_error{}; + const auto& data = MP_FILEOPS.read_all(file); + + if (const auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) + throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", + file.fileName(), + parse_error.errorString())}; + else if (json.isEmpty()) + throw std::runtime_error{fmt::v9::format("Empty snapshot JSON: {}", file.fileName())}; + else + return json["snapshot"].toObject(); +} + std::unordered_map load_mounts(const QJsonArray& json) { std::unordered_map mounts; @@ -155,12 +175,12 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, { } -mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) - : BaseSnapshot(InnerJsonTag{}, json["snapshot"].toObject(), vm) +mp::BaseSnapshot::BaseSnapshot(const QString& filename, VirtualMachine& vm) + : BaseSnapshot{read_snapshot_json(filename), vm} { } -mp::BaseSnapshot::BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm) +mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) : BaseSnapshot{ json["name"].toString().toStdString(), // name json["comment"].toString().toStdString(), // comment diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index a353ef2c674..1a3a696280c 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -40,7 +40,7 @@ class BaseSnapshot : public Snapshot std::shared_ptr parent, const VMSpecs& specs, VirtualMachine& vm); - BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); + BaseSnapshot(const QString& filename, VirtualMachine& vm); // TODO@snapshots tag as noexcept those that can be int get_index() const override; @@ -78,10 +78,7 @@ class BaseSnapshot : public Snapshot QString derive_id() const; private: - struct InnerJsonTag - { - }; - BaseSnapshot(InnerJsonTag, const QJsonObject& json, VirtualMachine& vm); + BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index e1a53249760..0b775420dfa 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -30,8 +30,6 @@ #include #include -#include -#include #include @@ -377,7 +375,7 @@ void BaseVirtualMachine::load_snapshots() // TODO@no-merge let snapshots load th QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& finfo : snapshot_files) - load_snapshot_from_file(finfo.filePath()); + load_snapshot(finfo.filePath()); load_generic_snapshot_info(); } @@ -431,28 +429,9 @@ void BaseVirtualMachine::log_latest_snapshot(LockT lock) const } } -void BaseVirtualMachine::load_snapshot_from_file(const QString& filename) +void BaseVirtualMachine::load_snapshot(const QString& filename) { - QFile file{filename}; - if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) - throw std::runtime_error{fmt::v9::format("Could not open snapshot file for for reading: {}", file.fileName())}; - - QJsonParseError parse_error{}; - const auto& data = MP_FILEOPS.read_all(file); - - if (const auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) - throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", - file.fileName(), - parse_error.errorString())}; - else if (json.isEmpty()) - throw std::runtime_error{fmt::v9::format("Empty snapshot JSON: {}", file.fileName())}; - else - load_snapshot(json); -} - -void BaseVirtualMachine::load_snapshot(const QJsonObject& json) -{ - auto snapshot = make_specific_snapshot(json); + auto snapshot = make_specific_snapshot(filename); const auto& name = snapshot->get_name(); auto [it, success] = snapshots.try_emplace(name, snapshot); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 7af1249dd8f..46ddab10c8b 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -75,7 +75,7 @@ class BaseVirtualMachine : public VirtualMachine int get_snapshot_count() const override; protected: - virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) = 0; + virtual std::shared_ptr make_specific_snapshot(const QString& filename) = 0; virtual std::shared_ptr make_specific_snapshot(const std::string& name, const std::string& comment, const VMSpecs& specs, @@ -88,8 +88,7 @@ class BaseVirtualMachine : public VirtualMachine void log_latest_snapshot(LockT lock) const; void load_generic_snapshot_info(); - void load_snapshot_from_file(const QString& filename); - void load_snapshot(const QJsonObject& json); + void load_snapshot(const QString& filename); auto make_take_snapshot_rollback(SnapshotMap::iterator it); void take_snapshot_rollback_helper(SnapshotMap::iterator it, std::shared_ptr& old_head, int old_count); diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 27b7e6c2646..c02ff5abb21 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -128,7 +128,7 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine return nullptr; } - virtual std::shared_ptr make_specific_snapshot(const QJsonObject& json) override + virtual std::shared_ptr make_specific_snapshot(const QString& json) override { return nullptr; } From 6173a21c37462f82fb9e9437f44e360f46da4000 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 17:05:06 +0100 Subject: [PATCH 514/627] [snapshot] Remove snapshot name from file name --- src/platform/backends/shared/base_snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index b04a9c47e14..5a7daaa08bc 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -313,5 +313,5 @@ void multipass::BaseSnapshot::erase() QString mp::BaseSnapshot::derive_snapshot_filename() const { - return QString{"%1-%2.%3"}.arg(derive_index_string(index), QString::fromStdString(name), snapshot_extension); + return QString{"%1.%2"}.arg(derive_index_string(index), snapshot_extension); } From 13689d3c95abba8a284a7451ddd3dcf1fa318c2d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 17:12:15 +0100 Subject: [PATCH 515/627] [snapshot] Remove obsolete renamed-file rollback Stop rolling back renamed snapshot files, since a snapshot's file name no longer depends on its name, meaning that renaming the snapshot no longer renames the snapshot file. --- src/platform/backends/shared/base_snapshot.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 5a7daaa08bc..796171aa178 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -295,11 +295,8 @@ auto mp::BaseSnapshot::erase_helper() void mp::BaseSnapshot::set_name(const std::string& n) { const std::unique_lock lock{mutex}; - auto rollback_old_file = erase_helper(); - name = n; persist(); - rollback_old_file.dismiss(); } void multipass::BaseSnapshot::erase() From 3ebc7f67f7127376b2286125fd6ca61cefa4e138 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 17:19:40 +0100 Subject: [PATCH 516/627] [snapshot] Re-inline simplified `set_name` method --- src/platform/backends/shared/base_snapshot.cpp | 7 ------- src/platform/backends/shared/base_snapshot.h | 8 +++++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 796171aa178..ac233a17a50 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -292,13 +292,6 @@ auto mp::BaseSnapshot::erase_helper() }); } -void mp::BaseSnapshot::set_name(const std::string& n) -{ - const std::unique_lock lock{mutex}; - name = n; - persist(); -} - void multipass::BaseSnapshot::erase() { const std::unique_lock lock{mutex}; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 1a3a696280c..676654ecea7 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -194,6 +194,13 @@ inline const QJsonObject& multipass::BaseSnapshot::get_metadata() const noexcept return metadata; } +inline void multipass::BaseSnapshot::set_name(const std::string& n) +{ + const std::unique_lock lock{mutex}; + name = n; + persist(); +} + inline void multipass::BaseSnapshot::set_comment(const std::string& c) { const std::unique_lock lock{mutex}; @@ -206,7 +213,6 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) const std::unique_lock lock{mutex}; parent = std::move(p); persist(); - // TODO@no-merge stop persisting elsewhere } inline void multipass::BaseSnapshot::capture() From 52e80fb7a69421b081e3901a4d1b0c9e100c9413 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 19:20:16 +0100 Subject: [PATCH 517/627] [snapshot] Persist the snapshot when capturing Persist snapshots when they are captured, freeing the VM from having to request persistence. Note that the rollback for new snapshots in the VM erases the snapshot if needed, which already removes the persisted json. --- src/platform/backends/shared/base_snapshot.h | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 5 ++--- src/platform/backends/shared/base_virtual_machine.h | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 676654ecea7..3fa94ae320a 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -219,7 +219,7 @@ inline void multipass::BaseSnapshot::capture() { const std::unique_lock lock{mutex}; capture_impl(); - // TODO@no-merge persist! + persist(); // TODO@no-merge stop persisting elsewhere } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 0b775420dfa..c475522474c 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -229,7 +229,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& ret->capture(); ++snapshot_count; - persist_head_snapshot(); + persist_generic_snapshot_info(); rollback_on_failure.dismiss(); log_latest_snapshot(std::move(lock)); @@ -466,7 +466,7 @@ void BaseVirtualMachine::common_file_rollback_helper(const Path& file_path, }); } -void BaseVirtualMachine::persist_head_snapshot() const +void BaseVirtualMachine::persist_generic_snapshot_info() const { assert(head_snapshot); @@ -482,7 +482,6 @@ void BaseVirtualMachine::persist_head_snapshot() const auto count_file_rollback = make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count) + "\n"); MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count) + "\n", yes_overwrite); - head_snapshot->persist(); count_file_rollback.dismiss(); head_file_rollback.dismiss(); } diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 46ddab10c8b..9551b7ec222 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -99,7 +99,7 @@ class BaseVirtualMachine : public VirtualMachine const std::string& old_contents, bool existed) const; - void persist_head_snapshot() const; + void persist_generic_snapshot_info() const; void persist_head_snapshot_index(const Path& head_path) const; std::string generate_snapshot_name() const; From b6c031e327c5d6e5f9a0f1b5c738ac0168cb83ea Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 22:10:41 +0100 Subject: [PATCH 518/627] [snapshot] Remove persist method from interface Snapshots should persist themselves whenever appropriate. --- include/multipass/snapshot.h | 1 - src/platform/backends/shared/base_snapshot.h | 2 +- tests/stub_snapshot.h | 4 ---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index ce126b1cc11..1d19cd3bcbf 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -63,7 +63,6 @@ class Snapshot : private DisabledCopyMove virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits virtual void apply() = 0; - virtual void persist() const = 0; // TODO@no-merge can we avoid exposing this at all (just persist when pertinent)? }; } // namespace multipass diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 3fa94ae320a..e3153ab9066 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -68,7 +68,6 @@ class BaseSnapshot : public Snapshot void capture() final; void erase() final; void apply() final; - void persist() const override; protected: virtual void capture_impl() = 0; @@ -95,6 +94,7 @@ class BaseSnapshot : public Snapshot auto erase_helper(); QString derive_snapshot_filename() const; QJsonObject serialize() const; + void persist() const; private: std::string name; diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index 86fe1e61908..ced617d8cff 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -97,10 +97,6 @@ struct StubSnapshot : public Snapshot return metadata; } - void persist() const override - { - } - void set_name(const std::string&) override { } From 76317114d0f0ac757cec8d3e1ded1ebe47696932 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 22:12:13 +0100 Subject: [PATCH 519/627] [snapshot] Persist a snapshot when setting comment --- src/platform/backends/shared/base_snapshot.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index e3153ab9066..088f683c7b5 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -205,7 +205,7 @@ inline void multipass::BaseSnapshot::set_comment(const std::string& c) { const std::unique_lock lock{mutex}; comment = c; - // TODO@no-merge persist! + persist(); } inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) @@ -215,12 +215,11 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) persist(); } -inline void multipass::BaseSnapshot::capture() +inline void multipass::BaseSnapshot::capture() // TODO@ricab should only be called once { const std::unique_lock lock{mutex}; capture_impl(); persist(); - // TODO@no-merge stop persisting elsewhere } inline void multipass::BaseSnapshot::apply() From 22fc602f7c16d2748ef21ba19cda636fc333f314 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 22:13:27 +0100 Subject: [PATCH 520/627] [snapshot] Slight reorder of snapshot fields Leave storage_dir for last among const fields, since it is not persisted to disk, unlike the rest. --- src/platform/backends/shared/base_snapshot.cpp | 16 ++++++++-------- src/platform/backends/shared/base_snapshot.h | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index ac233a17a50..1c18d49f913 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -122,26 +122,26 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p const std::string& comment, // NOLINT(modernize-pass-by-value) std::shared_ptr parent, int index, - const QDir& storage_dir, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, - QJsonObject metadata) + QJsonObject metadata, + const QDir& storage_dir) : name{name}, comment{comment}, parent{std::move(parent)}, index{index}, - storage_dir{storage_dir}, creation_timestamp{creation_timestamp}, num_cores{num_cores}, mem_size{mem_size}, disk_space{disk_space}, state{state}, mounts{std::move(mounts)}, - metadata{std::move(metadata)} + metadata{std::move(metadata)}, + storage_dir{storage_dir} { assert(index > 0 && "snapshot indices need to start at 1"); @@ -164,14 +164,14 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, comment, std::move(parent), vm.get_snapshot_count() + 1, - vm.instance_directory(), QDateTime::currentDateTimeUtc(), specs.num_cores, specs.mem_size, specs.disk_space, specs.state, specs.mounts, - specs.metadata} + specs.metadata, + vm.instance_directory()} { } @@ -186,14 +186,14 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) json["comment"].toString().toStdString(), // comment find_parent(json, vm), // parent json["index"].toInt(), // index - vm.instance_directory(), // storage_dir QDateTime::fromString(json["creation_timestamp"].toString(), Qt::ISODateWithMs), // creation_timestamp json["num_cores"].toInt(), // num_cores MemorySize{json["mem_size"].toString().toStdString()}, // mem_size MemorySize{json["disk_space"].toString().toStdString()}, // disk_space static_cast(json["state"].toInt()), // state load_mounts(json["mounts"].toArray()), // mounts - json["metadata"].toObject()} // metadata + json["metadata"].toObject(), // metadata + vm.instance_directory()} // storage_dir { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 088f683c7b5..ffbd99c2ebc 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -82,14 +82,14 @@ class BaseSnapshot : public Snapshot const std::string& comment, std::shared_ptr parent, int index, - const QDir& storage_dir, const QDateTime& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, VirtualMachine::State state, std::unordered_map mounts, - QJsonObject metadata); + QJsonObject metadata, + const QDir& storage_dir); auto erase_helper(); QString derive_snapshot_filename() const; @@ -103,7 +103,6 @@ class BaseSnapshot : public Snapshot // This class is non-copyable and having these const simplifies thread safety const int index; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) - const QDir storage_dir; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QDateTime creation_timestamp; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const MemorySize mem_size; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -111,6 +110,7 @@ class BaseSnapshot : public Snapshot const VirtualMachine::State state; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const std::unordered_map mounts; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QJsonObject metadata; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const QDir storage_dir; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) mutable std::recursive_mutex mutex; }; From bc0be8301d7d48fcb329c9cc11ea6ea34cbcf5ad Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 11 Oct 2023 22:22:19 +0100 Subject: [PATCH 521/627] [snapshot] Remove obsolete TODOs Replace with comment where appropriate. --- src/daemon/snapshot_settings_handler.cpp | 1 - src/platform/backends/shared/base_snapshot.h | 4 ++-- src/platform/backends/shared/base_virtual_machine.cpp | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index e092a8edce3..d7e49446ac1 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -137,7 +137,6 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) auto snapshot = modify_snapshot(instance_name, snapshot_name_stdstr); snapshot->set_comment(val_stdstr); } - // TODO@no-merge persist (ideally would happen automatically in setters) } auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name, diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index ffbd99c2ebc..f5a06dfd1dd 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -226,8 +226,8 @@ inline void multipass::BaseSnapshot::apply() { const std::unique_lock lock{mutex}; apply_impl(); - // TODO@no-merge persist! - // TODO@no-merge stop persisting elsewhere + // no need to persist here for the time being: only private fields of the base class are persisted for now, and + // those cannot be affected by apply_impl (except by setters, which already persist) } #endif // MULTIPASS_BASE_SNAPSHOT_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c475522474c..ff9adb5a85e 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -37,8 +37,6 @@ namespace mp = multipass; namespace mpl = multipass::logging; namespace mpu = multipass::utils; -// TODO@no-merge prune everything related to individual snapshot persistence - namespace { using St = mp::VirtualMachine::State; @@ -366,7 +364,7 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) mpl::log(mpl::Level::debug, vm_name, fmt::format("Snapshot deleted: {}", name)); } -void BaseVirtualMachine::load_snapshots() // TODO@no-merge let snapshots load themselves +void BaseVirtualMachine::load_snapshots() { std::unique_lock lock{snapshot_mutex}; From d495273c422ea6bb929400d56b3be73c5ebc62be Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 12 Oct 2023 14:29:08 +0100 Subject: [PATCH 522/627] [snapshot] Require a single capture per snapshot --- include/multipass/snapshot.h | 1 + src/platform/backends/shared/base_snapshot.cpp | 12 ++++++++---- src/platform/backends/shared/base_snapshot.h | 17 +++++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 1d19cd3bcbf..5d05f3eacb5 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -60,6 +60,7 @@ class Snapshot : private DisabledCopyMove virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; + // precondition: capture only once virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits virtual void apply() = 0; diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 1c18d49f913..5fbfdde08be 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -129,7 +129,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata, - const QDir& storage_dir) + const QDir& storage_dir, + bool captured) : name{name}, comment{comment}, parent{std::move(parent)}, @@ -141,7 +142,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p state{state}, mounts{std::move(mounts)}, metadata{std::move(metadata)}, - storage_dir{storage_dir} + storage_dir{storage_dir}, + captured{captured} { assert(index > 0 && "snapshot indices need to start at 1"); @@ -171,7 +173,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, specs.state, specs.mounts, specs.metadata, - vm.instance_directory()} + vm.instance_directory(), + /*captured=*/false} { } @@ -193,7 +196,8 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) static_cast(json["state"].toInt()), // state load_mounts(json["mounts"].toArray()), // mounts json["metadata"].toObject(), // metadata - vm.instance_directory()} // storage_dir + vm.instance_directory(), // storage_dir + true} // captured { } diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index f5a06dfd1dd..a3b8cbfa5e5 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -89,7 +89,8 @@ class BaseSnapshot : public Snapshot VirtualMachine::State state, std::unordered_map mounts, QJsonObject metadata, - const QDir& storage_dir); + const QDir& storage_dir, + bool captured); auto erase_helper(); QString derive_snapshot_filename() const; @@ -112,6 +113,7 @@ class BaseSnapshot : public Snapshot const QJsonObject metadata; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QDir storage_dir; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + bool captured; mutable std::recursive_mutex mutex; }; } // namespace multipass @@ -215,11 +217,18 @@ inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) persist(); } -inline void multipass::BaseSnapshot::capture() // TODO@ricab should only be called once +inline void multipass::BaseSnapshot::capture() { const std::unique_lock lock{mutex}; - capture_impl(); - persist(); + assert(!captured && + "pre-condition: capture should only be called once, and only for snapshots that were not loaded from disk"); + + if (!captured) + { + captured = true; + capture_impl(); + persist(); + } } inline void multipass::BaseSnapshot::apply() From 02beae7ed33605f0ae2a799f82fc2bd8271965f1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 12 Oct 2023 14:37:37 +0100 Subject: [PATCH 523/627] [snapshot] Add further preconditions Add further preconditions relating to whether or not snapshots were captured. --- include/multipass/snapshot.h | 3 +++ src/platform/backends/shared/base_snapshot.cpp | 2 ++ src/platform/backends/shared/base_snapshot.h | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 5d05f3eacb5..769bbdfa87f 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -56,13 +56,16 @@ class Snapshot : private DisabledCopyMove virtual std::string get_parents_name() const = 0; virtual int get_parents_index() const = 0; + // precondition for setters: call only on captured snapshots virtual void set_name(const std::string&) = 0; virtual void set_comment(const std::string&) = 0; virtual void set_parent(std::shared_ptr) = 0; // precondition: capture only once virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too + // precondition: call only on captured snapshots virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits + // precondition: call only on captured snapshots virtual void apply() = 0; }; } // namespace multipass diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 5fbfdde08be..f91c0a47958 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -203,6 +203,7 @@ mp::BaseSnapshot::BaseSnapshot(const QJsonObject& json, VirtualMachine& vm) QJsonObject mp::BaseSnapshot::serialize() const { + assert(captured && "precondition: only captured snapshots can be serialized"); QJsonObject ret, snapshot{}; const std::unique_lock lock{mutex}; @@ -299,6 +300,7 @@ auto mp::BaseSnapshot::erase_helper() void multipass::BaseSnapshot::erase() { const std::unique_lock lock{mutex}; + assert(captured && "precondition: only captured snapshots can be erased"); auto rollback_snapshot_file = erase_helper(); erase_impl(); diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index a3b8cbfa5e5..2fdef4c3162 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -199,6 +199,8 @@ inline const QJsonObject& multipass::BaseSnapshot::get_metadata() const noexcept inline void multipass::BaseSnapshot::set_name(const std::string& n) { const std::unique_lock lock{mutex}; + assert(captured && "precondition: only captured snapshots can be edited"); + name = n; persist(); } @@ -206,6 +208,8 @@ inline void multipass::BaseSnapshot::set_name(const std::string& n) inline void multipass::BaseSnapshot::set_comment(const std::string& c) { const std::unique_lock lock{mutex}; + assert(captured && "precondition: only captured snapshots can be edited"); + comment = c; persist(); } @@ -213,6 +217,8 @@ inline void multipass::BaseSnapshot::set_comment(const std::string& c) inline void multipass::BaseSnapshot::set_parent(std::shared_ptr p) { const std::unique_lock lock{mutex}; + assert(captured && "precondition: only captured snapshots can be edited"); + parent = std::move(p); persist(); } From 58b4caa5b473cf1ce0182aafa95c608c753ad53e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 12 Oct 2023 12:24:09 +0100 Subject: [PATCH 524/627] [snapshot] Move check for max index into Snapshot --- src/platform/backends/shared/base_snapshot.cpp | 2 ++ src/platform/backends/shared/base_virtual_machine.cpp | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f91c0a47958..e322ecabe1b 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -146,6 +146,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p captured{captured} { assert(index > 0 && "snapshot indices need to start at 1"); + if (index > max_snapshots) + throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", max_snapshots)}; if (name.empty()) throw std::runtime_error{"Snapshot names cannot be empty"}; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index ff9adb5a85e..f8a50389dc3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -43,8 +43,6 @@ using St = mp::VirtualMachine::State; constexpr auto snapshot_extension = "snapshot.json"; constexpr auto head_filename = "snapshot-head"; constexpr auto count_filename = "snapshot-count"; -constexpr auto index_digits = 4; // these two go together -constexpr auto max_snapshots = 1000; constexpr auto yes_overwrite = true; void assert_vm_stopped(St state) @@ -210,8 +208,6 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition - if (snapshot_count > max_snapshots) - throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", max_snapshots)}; snapshot_name = name.empty() ? generate_snapshot_name() : name; const auto [it, success] = snapshots.try_emplace(snapshot_name, nullptr); From 4edc8feb3c00466addc41664e0bf3e77b83576d3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 12 Oct 2023 12:25:18 +0100 Subject: [PATCH 525/627] [snapshot] Fix max index with four digits --- src/platform/backends/shared/base_snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index e322ecabe1b..181a7dcc5a8 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -39,7 +39,7 @@ namespace { constexpr auto snapshot_extension = "snapshot.json"; constexpr auto index_digits = 4; // these two go together -constexpr auto max_snapshots = 1000; +constexpr auto max_snapshots = 9999; const auto snapshot_template = QStringLiteral("@s%1"); /* avoid confusion with snapshot names by prepending a character that can't be part of the name (users can call a snapshot "s1", but they cannot call it "@s1") */ From dc7dec20d378efadb6075fd1cfa5ff1db66433d1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 13 Oct 2023 12:06:58 +0100 Subject: [PATCH 526/627] [snapshot] Receive const VM in one of constructors --- src/platform/backends/shared/base_snapshot.cpp | 2 +- src/platform/backends/shared/base_snapshot.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 181a7dcc5a8..6a1e1c8bb6d 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -163,7 +163,7 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, - VirtualMachine& vm) + const VirtualMachine& vm) : BaseSnapshot{name, comment, std::move(parent), diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index 2fdef4c3162..d91c2abd81c 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -39,7 +39,7 @@ class BaseSnapshot : public Snapshot const std::string& comment, std::shared_ptr parent, const VMSpecs& specs, - VirtualMachine& vm); + const VirtualMachine& vm); BaseSnapshot(const QString& filename, VirtualMachine& vm); // TODO@snapshots tag as noexcept those that can be From 07b821c29494d5b73336f6e673c73dd8ca811fec Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 13 Oct 2023 15:09:14 +0100 Subject: [PATCH 527/627] [snapshot] Fix success on invalid snapshot Detect invalid (absent) snapshots when setting a snapshot name to its present value (e.g. `multipass set local.vm.invalid.name=invalid`). --- src/daemon/snapshot_settings_handler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index d7e49446ac1..0e922cdd45a 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -124,7 +124,10 @@ void mp::SnapshotSettingsHandler::set(const QString& key, const QString& val) if (property == name_suffix) { if (snapshot_name == val) + { + find_snapshot(instance_name, snapshot_name_stdstr); // fail if it ain't there return; + } if (val_stdstr.empty() || !mp::utils::valid_hostname(val_stdstr)) throw mp::InvalidSettingException{key, val, "Invalid snapshot name."}; From 6943415718a1588d64d8d37067ac7d066a13dbf5 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 13 Oct 2023 23:30:15 +0100 Subject: [PATCH 528/627] [lint] Fix various lint issues --- src/daemon/snapshot_settings_handler.h | 2 +- .../backends/libvirt/libvirt_virtual_machine.cpp | 9 +++++---- .../backends/libvirt/libvirt_virtual_machine.h | 2 +- src/platform/backends/lxd/lxd_mount_handler.cpp | 2 ++ src/platform/backends/lxd/lxd_mount_handler.h | 4 ++-- src/platform/backends/lxd/lxd_virtual_machine.cpp | 2 +- src/platform/backends/lxd/lxd_virtual_machine.h | 6 +++--- src/platform/backends/qemu/qemu_mount_handler.cpp | 1 + src/platform/backends/qemu/qemu_snapshot.cpp | 1 - src/platform/backends/qemu/qemu_virtual_machine.h | 2 +- src/platform/backends/shared/base_snapshot.cpp | 10 +++++----- src/platform/backends/shared/base_snapshot.h | 4 ++-- src/platform/backends/shared/base_virtual_machine.cpp | 8 ++++---- src/platform/backends/shared/base_virtual_machine.h | 10 +++------- tests/mock_virtual_machine.h | 2 -- tests/stub_virtual_machine.h | 6 +++--- 16 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 4c6c67f94cd..5eecad76633 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -60,7 +60,7 @@ class SnapshotSettingsException : public SettingsException { public: SnapshotSettingsException(const std::string& missing_instance, const std::string& detail); - SnapshotSettingsException(const std::string& detail); + explicit SnapshotSettingsException(const std::string& detail); }; } // namespace multipass diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 5effb308e39..f179d4c68f7 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -556,10 +556,11 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) desc.disk_space = new_size; } -auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& name, - const std::string& comment, - const VMSpecs& specs, - std::shared_ptr parent) -> std::shared_ptr +auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& /*name*/, + const std::string& /*comment*/, + const VMSpecs& /*specs*/, + std::shared_ptr /*parent*/) + -> std::shared_ptr { throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots } diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index b351c2913b0..388c4f0413f 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -39,7 +39,7 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine const std::string& bridge_name, VMStatusMonitor& monitor, const LibvirtWrapper::UPtr& libvirt_wrapper, - const mp::Path& instance_dir); + const Path& instance_dir); ~LibVirtVirtualMachine(); void start() override; diff --git a/src/platform/backends/lxd/lxd_mount_handler.cpp b/src/platform/backends/lxd/lxd_mount_handler.cpp index 31e532c4ec5..cc54779d0a4 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.cpp +++ b/src/platform/backends/lxd/lxd_mount_handler.cpp @@ -18,6 +18,8 @@ #include "lxd_mount_handler.h" #include "lxd_request.h" +namespace mp = multipass; + namespace { constexpr std::string_view category = "lxd-mount-handler"; diff --git a/src/platform/backends/lxd/lxd_mount_handler.h b/src/platform/backends/lxd/lxd_mount_handler.h index db6a6d76374..fede0467956 100644 --- a/src/platform/backends/lxd/lxd_mount_handler.h +++ b/src/platform/backends/lxd/lxd_mount_handler.h @@ -26,7 +26,7 @@ namespace multipass class LXDMountHandler : public MountHandler { public: - LXDMountHandler(mp::NetworkAccessManager* network_manager, + LXDMountHandler(NetworkAccessManager* network_manager, LXDVirtualMachine* lxd_virtual_machine, const SSHKeyProvider* ssh_key_provider, const std::string& target_path, @@ -46,7 +46,7 @@ class LXDMountHandler : public MountHandler void lxd_device_remove(); // data member - mp::NetworkAccessManager* network_manager{nullptr}; + NetworkAccessManager* network_manager{nullptr}; const QUrl lxd_instance_endpoint{}; const std::string device_name{}; }; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 494155f6319..c12e4c5432b 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -487,7 +487,7 @@ mp::LXDVirtualMachine::make_native_mount_handler(const SSHKeyProvider* ssh_key_p return std::make_unique(manager, this, ssh_key_provider, target, mount); } -auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& name, +auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) -> std::shared_ptr diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index fd5c882478e..5c46cd8b9e1 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -38,7 +38,7 @@ class LXDVirtualMachine : public BaseVirtualMachine const QUrl& base_url, const QString& bridge_name, const QString& storage_pool, - const mp::Path& instance_dir); + const Path& instance_dir); ~LXDVirtualMachine() override; void stop() override; void start() override; @@ -61,8 +61,8 @@ class LXDVirtualMachine : public BaseVirtualMachine const std::string& target, const VMMount& mount) override; protected: - std::shared_ptr make_specific_snapshot(const QString& filename) override; - std::shared_ptr make_specific_snapshot(const std::string& name, + std::shared_ptr make_specific_snapshot(const QString& filename) override; + std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) override; diff --git a/src/platform/backends/qemu/qemu_mount_handler.cpp b/src/platform/backends/qemu/qemu_mount_handler.cpp index 3c042a3f3a8..f40ee0fc4c3 100644 --- a/src/platform/backends/qemu/qemu_mount_handler.cpp +++ b/src/platform/backends/qemu/qemu_mount_handler.cpp @@ -21,6 +21,7 @@ #include +namespace mp = multipass; namespace mpl = multipass::logging; namespace mpu = multipass::utils; diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index b3f15063258..b3047140f62 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -19,7 +19,6 @@ #include "qemu_virtual_machine.h" #include "shared/qemu_img_utils/qemu_img_utils.h" -#include #include #include #include diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 0ed3f636765..267af8a6632 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -73,7 +73,7 @@ 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 mp::Path& instance_dir) : BaseVirtualMachine{name, instance_dir} + QemuVirtualMachine(const std::string& name, const Path& instance_dir) : BaseVirtualMachine{name, instance_dir} { } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 6a1e1c8bb6d..e6022893fe2 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -79,13 +79,13 @@ std::unordered_map load_mounts(const QJsonArray& json) auto target_path = entry.toObject()["target_path"].toString().toStdString(); auto source_path = entry.toObject()["source_path"].toString().toStdString(); - for (QJsonValueRef uid_entry : entry.toObject()["uid_mappings"].toArray()) + for (const QJsonValueRef uid_entry : entry.toObject()["uid_mappings"].toArray()) { uid_mappings.push_back( {uid_entry.toObject()["host_uid"].toInt(), uid_entry.toObject()["instance_uid"].toInt()}); } - for (QJsonValueRef gid_entry : entry.toObject()["gid_mappings"].toArray()) + for (const QJsonValueRef gid_entry : entry.toObject()["gid_mappings"].toArray()) { gid_mappings.push_back( {gid_entry.toObject()["host_gid"].toInt(), gid_entry.toObject()["instance_gid"].toInt()}); @@ -96,7 +96,7 @@ std::unordered_map load_mounts(const QJsonArray& json) auto mount_type = mp::VMMount::MountType(entry.toObject()["mount_type"].toInt()); mp::VMMount mount{source_path, gid_mappings, uid_mappings, mount_type}; - mounts[target_path] = mount; + mounts[target_path] = std::move(mount); } return mounts; @@ -122,7 +122,7 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p const std::string& comment, // NOLINT(modernize-pass-by-value) std::shared_ptr parent, int index, - const QDateTime& creation_timestamp, + QDateTime&& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, @@ -135,7 +135,7 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p comment{comment}, parent{std::move(parent)}, index{index}, - creation_timestamp{creation_timestamp}, + creation_timestamp{std::move(creation_timestamp)}, num_cores{num_cores}, mem_size{mem_size}, disk_space{disk_space}, diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index d91c2abd81c..f67c9250682 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -82,7 +82,7 @@ class BaseSnapshot : public Snapshot const std::string& comment, std::shared_ptr parent, int index, - const QDateTime& creation_timestamp, + QDateTime&& creation_timestamp, int num_cores, MemorySize mem_size, MemorySize disk_space, @@ -151,7 +151,7 @@ inline std::string multipass::BaseSnapshot::get_parents_name() const inline int multipass::BaseSnapshot::get_parents_index() const { - std::unique_lock lock{mutex}; + const std::unique_lock lock{mutex}; return parent ? parent->get_index() : 0; // this doesn't lock } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f8a50389dc3..b0f8aa896a2 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -329,7 +329,7 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: if (old_name == new_name) return; - std::unique_lock lock{snapshot_mutex}; + const std::unique_lock lock{snapshot_mutex}; auto old_it = snapshots.find(old_name); if (old_it == snapshots.end()) @@ -347,7 +347,7 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: void BaseVirtualMachine::delete_snapshot(const std::string& name) { - std::unique_lock lock{snapshot_mutex}; + const std::unique_lock lock{snapshot_mutex}; auto it = snapshots.find(name); if (it == snapshots.end()) @@ -362,7 +362,7 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) void BaseVirtualMachine::load_snapshots() { - std::unique_lock lock{snapshot_mutex}; + const std::unique_lock lock{snapshot_mutex}; auto snapshot_files = MP_FILEOPS.entryInfoList(instance_dir, {QString{"*.%1"}.arg(snapshot_extension)}, @@ -521,7 +521,7 @@ void BaseVirtualMachine::restore_rollback_helper(const Path& head_path, void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& specs) { - std::unique_lock lock{snapshot_mutex}; + const std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition auto snapshot = get_snapshot(name); diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 9551b7ec222..a0447ffab75 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -34,22 +34,18 @@ #include #include -namespace mp = multipass; -namespace mpl = multipass::logging; -namespace mpu = multipass::utils; - namespace multipass { class BaseVirtualMachine : public VirtualMachine { public: - BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, const mp::Path& instance_dir); - BaseVirtualMachine(const std::string& vm_name, const mp::Path& instance_dir); + BaseVirtualMachine(VirtualMachine::State state, const std::string& vm_name, const Path& instance_dir); + BaseVirtualMachine(const std::string& vm_name, const Path& instance_dir); std::vector get_all_ipv4(const SSHKeyProvider& key_provider) override; std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, const std::string& target, - const multipass::VMMount& mount) override + const VMMount& mount) override { throw NotImplementedOnThisBackendException("native mounts"); }; diff --git a/tests/mock_virtual_machine.h b/tests/mock_virtual_machine.h index 813f306526c..76e63ad328f 100644 --- a/tests/mock_virtual_machine.h +++ b/tests/mock_virtual_machine.h @@ -91,8 +91,6 @@ 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)); - - std::unique_ptr tmp_dir; }; using MockVirtualMachine = MockVirtualMachineT<>; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 10916df3e16..67dd1adb0ea 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -141,17 +141,17 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return {}; } - std::shared_ptr get_snapshot(const std::string& name) override + std::shared_ptr get_snapshot(const std::string&) override { return {}; } - std::shared_ptr get_snapshot(int index) const override + std::shared_ptr get_snapshot(int) const override { return nullptr; } - std::shared_ptr get_snapshot(int index) override + std::shared_ptr get_snapshot(int) override { return nullptr; } From 93538bb76d4691c66bffd069d1e8e1726be5f484 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 16 Oct 2023 15:24:34 +0100 Subject: [PATCH 529/627] [format] Adapt to latest clang-format config --- src/daemon/daemon.cpp | 22 ++++++++++++------- src/daemon/instance_settings_handler.cpp | 3 ++- .../backends/shared/base_virtual_machine.cpp | 3 ++- src/utils/json_utils.cpp | 3 ++- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index f68012461df..fa11d27298d 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1228,14 +1228,17 @@ mp::SettingsHandler* register_instance_mod(std::unordered_map& preparing_instances, std::function instance_persister) { - return MP_SETTINGS.register_handler(std::make_unique( - vm_instance_specs, operative_instances, deleted_instances, preparing_instances, std::move(instance_persister))); + return MP_SETTINGS.register_handler(std::make_unique(vm_instance_specs, + operative_instances, + deleted_instances, + preparing_instances, + std::move(instance_persister))); } -mp::SettingsHandler* -register_snapshot_mod(std::unordered_map& operative_instances, - const std::unordered_map& deleted_instances, - const std::unordered_set& preparing_instances) +mp::SettingsHandler* register_snapshot_mod( + std::unordered_map& operative_instances, + const std::unordered_map& deleted_instances, + const std::unordered_set& preparing_instances) { return MP_SETTINGS.register_handler( std::make_unique(operative_instances, deleted_instances, preparing_instances)); @@ -1350,8 +1353,11 @@ mp::Daemon::Daemon(std::unique_ptr the_config) 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()))}, daemon_rpc{config->server_address, *config->cert_provider, config->client_cert_store.get()}, - instance_mod_handler{register_instance_mod(vm_instance_specs, operative_instances, deleted_instances, - preparing_instances, [this] { persist_instances(); })}, + instance_mod_handler{register_instance_mod(vm_instance_specs, + operative_instances, + deleted_instances, + preparing_instances, + [this] { persist_instances(); })}, snapshot_mod_handler{register_snapshot_mod(operative_instances, deleted_instances, preparing_instances)} { connect_rpc(daemon_rpc, *this); diff --git a/src/daemon/instance_settings_handler.cpp b/src/daemon/instance_settings_handler.cpp index a95b4ecaf0e..3cc2f37c076 100644 --- a/src/daemon/instance_settings_handler.cpp +++ b/src/daemon/instance_settings_handler.cpp @@ -165,7 +165,8 @@ mp::InstanceSettingsHandler::InstanceSettingsHandler( std::unordered_map& vm_instance_specs, std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, - const std::unordered_set& preparing_instances, std::function instance_persister) + const std::unordered_set& preparing_instances, + std::function instance_persister) : vm_instance_specs{vm_instance_specs}, operative_instances{operative_instances}, deleted_instances{deleted_instances}, diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index b0f8aa896a2..92ffc5784de 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -261,7 +261,8 @@ void BaseVirtualMachine::deleted_head_rollback_helper(const Path& head_path, if (wrote_head) top_catch_all(vm_name, [this, &head_path] { MP_UTILS.make_file_with_content(head_path.toStdString(), - std::to_string(head_snapshot->get_index()) + "\n", yes_overwrite); + std::to_string(head_snapshot->get_index()) + "\n", + yes_overwrite); }); } } diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index 0baf5c88f02..e2bbe2fa565 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -44,7 +44,8 @@ void mp::JsonUtils::write_json(const QJsonObject& root, QString file_name) const if (MP_FILEOPS.write(db_file, QJsonDocument{root}.toJson()) == -1) throw std::runtime_error{fmt::format("Could not write json to transactional file; filename: {}; error: {}", - file_name, db_file.errorString())}; + file_name, + db_file.errorString())}; if (!MP_FILEOPS.commit(db_file)) throw std::runtime_error{fmt::format("Could not commit transactional file; filename: {}", file_name)}; From ed2cee15bbc305e11e63cd5117946080ba23d4ea Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 18:27:38 +0100 Subject: [PATCH 530/627] [snapshot] Fix rollbacks Fix dangling references in a snapshot rollback; fix count-rollback contents. Fix obsolete capture assignment. --- src/platform/backends/shared/base_snapshot.cpp | 4 +++- src/platform/backends/shared/base_virtual_machine.cpp | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index e6022893fe2..3d21e85ff54 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -293,7 +293,9 @@ auto mp::BaseSnapshot::erase_helper() throw std::runtime_error{ fmt::format("Failed to move snapshot file to temporary destination: {}", deleting_filepath)}; - return sg::make_scope_guard([tmp_dir = std::move(tmp_dir), &deleting_filepath, &snapshot_filepath]() noexcept { + return sg::make_scope_guard([tmp_dir = std::move(tmp_dir), + deleting_filepath = std::move(deleting_filepath), + snapshot_filepath = std::move(snapshot_filepath)]() noexcept { QFile temp_file{deleting_filepath}; MP_FILEOPS.rename(temp_file, snapshot_filepath); // best effort, ignore return }); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 92ffc5784de..0a08c66ea87 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -441,10 +441,9 @@ auto BaseVirtualMachine::make_common_file_rollback(const Path& file_path, QFile& file, const std::string& old_contents) const { - return sg::make_scope_guard( - [this, &file_path, &file, old_contents = old_contents, existed = file.exists()]() noexcept { - common_file_rollback_helper(file_path, file, old_contents, existed); - }); + return sg::make_scope_guard([this, &file_path, &file, old_contents, existed = file.exists()]() noexcept { + common_file_rollback_helper(file_path, file, old_contents, existed); + }); } void BaseVirtualMachine::common_file_rollback_helper(const Path& file_path, @@ -474,7 +473,8 @@ void BaseVirtualMachine::persist_generic_snapshot_info() const persist_head_snapshot_index(head_path); QFile count_file{count_path}; - auto count_file_rollback = make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count) + "\n"); + auto count_file_rollback = + make_common_file_rollback(count_path, count_file, std::to_string(snapshot_count - 1) + "\n"); MP_UTILS.make_file_with_content(count_path.toStdString(), std::to_string(snapshot_count) + "\n", yes_overwrite); count_file_rollback.dismiss(); From 736bac12a184def53653982d5f08c2be23c54963 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 12:41:04 +0100 Subject: [PATCH 531/627] [vm] Normalize snapshot param name across VMs --- src/platform/backends/libvirt/libvirt_virtual_machine.cpp | 2 +- src/platform/backends/libvirt/libvirt_virtual_machine.h | 2 +- src/platform/backends/qemu/qemu_virtual_machine.cpp | 4 ++-- src/platform/backends/qemu/qemu_virtual_machine.h | 2 +- src/platform/backends/shared/base_virtual_machine.h | 2 +- tests/test_base_virtual_machine.cpp | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index f179d4c68f7..6a2f00ee91d 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -556,7 +556,7 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) desc.disk_space = new_size; } -auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& /*name*/, +auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& /*snapshot_name*/, const std::string& /*comment*/, const VMSpecs& /*specs*/, std::shared_ptr /*parent*/) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index 388c4f0413f..bac0737fa75 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -63,7 +63,7 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine protected: std::shared_ptr make_specific_snapshot(const QString& filename) override; - std::shared_ptr make_specific_snapshot(const std::string& name, + std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) override; diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 36c3798dde2..b34e94918d6 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -621,13 +621,13 @@ mp::QemuVirtualMachine::MountArgs& mp::QemuVirtualMachine::modifiable_mount_args return mount_args; } -auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& name, +auto mp::QemuVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) -> std::shared_ptr { assert(state == VirtualMachine::State::off || state != VirtualMachine::State::stopped); // would need QMP otherwise - return std::make_shared(name, comment, std::move(parent), specs, *this, desc); + return std::make_shared(snapshot_name, comment, std::move(parent), specs, *this, desc); } auto mp::QemuVirtualMachine::make_specific_snapshot(const QString& filename) -> std::shared_ptr diff --git a/src/platform/backends/qemu/qemu_virtual_machine.h b/src/platform/backends/qemu/qemu_virtual_machine.h index 267af8a6632..ce0c73f01a8 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.h +++ b/src/platform/backends/qemu/qemu_virtual_machine.h @@ -78,7 +78,7 @@ class QemuVirtualMachine : public QObject, public BaseVirtualMachine } std::shared_ptr make_specific_snapshot(const QString& filename) override; - std::shared_ptr make_specific_snapshot(const std::string& name, + std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) override; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index a0447ffab75..f90b38df5ea 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -72,7 +72,7 @@ class BaseVirtualMachine : public VirtualMachine protected: virtual std::shared_ptr make_specific_snapshot(const QString& filename) = 0; - virtual std::shared_ptr make_specific_snapshot(const std::string& name, + virtual std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, std::shared_ptr parent) = 0; diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index c02ff5abb21..956b92aeb2a 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -120,7 +120,7 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine } protected: - std::shared_ptr make_specific_snapshot(const std::string& name, + std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const mp::VMSpecs& specs, std::shared_ptr parent) override From 7cd1027277fe52a13fcbf1d9332e14a3dfa778ee Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 12:44:28 +0100 Subject: [PATCH 532/627] [tests] Comment out unused param names in stub --- tests/test_base_virtual_machine.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 956b92aeb2a..9da6421839a 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -75,7 +75,7 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine return 42; } - std::string ssh_hostname(std::chrono::milliseconds timeout) override + std::string ssh_hostname(std::chrono::milliseconds /*timeout*/) override { return "localhost"; } @@ -95,7 +95,7 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine return ""; } - void wait_until_ssh_up(std::chrono::milliseconds timeout, const mp::SSHKeyProvider& key_provider) override + void wait_until_ssh_up(std::chrono::milliseconds /*timeout*/, const mp::SSHKeyProvider& /*key_provider*/) override { } @@ -120,15 +120,15 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine } protected: - std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, - const std::string& comment, - const mp::VMSpecs& specs, - std::shared_ptr parent) override + std::shared_ptr make_specific_snapshot(const std::string& /*snapshot_name*/, + const std::string& /*comment*/, + const mp::VMSpecs& /*specs*/, + std::shared_ptr /*parent*/) override { return nullptr; } - virtual std::shared_ptr make_specific_snapshot(const QString& json) override + virtual std::shared_ptr make_specific_snapshot(const QString& /*json*/) override { return nullptr; } From 081a1c60435a840b97996061e365e891b727f7dc Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 12:45:00 +0100 Subject: [PATCH 533/627] [tests] Fix state setting in stub vm constructor --- tests/test_base_virtual_machine.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_base_virtual_machine.cpp b/tests/test_base_virtual_machine.cpp index 9da6421839a..06e5907911d 100644 --- a/tests/test_base_virtual_machine.cpp +++ b/tests/test_base_virtual_machine.cpp @@ -37,11 +37,10 @@ struct StubBaseVirtualMachine : public mp::BaseVirtualMachine StubBaseVirtualMachine(mp::VirtualMachine::State s = mp::VirtualMachine::State::off) : StubBaseVirtualMachine{s, std::make_unique()} { - state = s; } StubBaseVirtualMachine(mp::VirtualMachine::State s, std::unique_ptr&& tmp_dir) - : mp::BaseVirtualMachine{"stub", tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} + : mp::BaseVirtualMachine{s, "stub", tmp_dir->path()}, tmp_dir{std::move(tmp_dir)} { } From f32a1158d4a93c07f8b82e97a20670730a3f83a4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 12:48:39 +0100 Subject: [PATCH 534/627] [vm] Add a TODO to remove rename_snapshot from VM Renaming a snapshot should be done on the snapshot object. The VM should therefore not index snapshots by name. --- include/multipass/virtual_machine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index b70aa2056c1..5f9a2939973 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -99,7 +99,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& name, const std::string& comment) = 0; - virtual void rename_snapshot(const std::string& old_name, const std::string& new_name) = 0; + virtual void rename_snapshot(const std::string& old_name, const std::string& new_name) = 0; // TODO@snapshots remove virtual void delete_snapshot(const std::string& name) = 0; virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots() = 0; From b6f8229712126e81393217a29618bbccdbcf430c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 12:50:16 +0100 Subject: [PATCH 535/627] [snapshot] Fix specific version in fmt calls --- src/platform/backends/shared/base_snapshot.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 3d21e85ff54..f898b27920e 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -53,17 +53,17 @@ QJsonObject read_snapshot_json(const QString& filename) { QFile file{filename}; if (!MP_FILEOPS.open(file, QIODevice::ReadOnly)) - throw std::runtime_error{fmt::v9::format("Could not open snapshot file for for reading: {}", file.fileName())}; + throw std::runtime_error{fmt::format("Could not open snapshot file for for reading: {}", file.fileName())}; QJsonParseError parse_error{}; const auto& data = MP_FILEOPS.read_all(file); if (const auto json = QJsonDocument::fromJson(data, &parse_error).object(); parse_error.error) - throw std::runtime_error{fmt::v9::format("Could not parse snapshot JSON; error: {}; file: {}", - file.fileName(), - parse_error.errorString())}; + throw std::runtime_error{fmt::format("Could not parse snapshot JSON; error: {}; file: {}", + file.fileName(), + parse_error.errorString())}; else if (json.isEmpty()) - throw std::runtime_error{fmt::v9::format("Empty snapshot JSON: {}", file.fileName())}; + throw std::runtime_error{fmt::format("Empty snapshot JSON: {}", file.fileName())}; else return json["snapshot"].toObject(); } From 3d2292ebbf624dc6322574ebb51643c162c131e3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 19:10:30 +0100 Subject: [PATCH 536/627] [vm] Improve parameter naming --- include/multipass/virtual_machine.h | 2 +- .../backends/shared/base_virtual_machine.cpp | 14 +++++++------- .../backends/shared/base_virtual_machine.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 5f9a2939973..7d2cffb3bf1 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -97,7 +97,7 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr get_snapshot(int index) = 0; virtual std::shared_ptr take_snapshot(const VMSpecs& specs, - const std::string& name, + const std::string& snapshot_name, const std::string& comment) = 0; virtual void rename_snapshot(const std::string& old_name, const std::string& new_name) = 0; // TODO@snapshots remove virtual void delete_snapshot(const std::string& name) = 0; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 0a08c66ea87..f52d665cbc2 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -199,27 +199,27 @@ auto BaseVirtualMachine::make_take_snapshot_rollback(SnapshotMap::iterator it) } std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& specs, - const std::string& name, + const std::string& snapshot_name, const std::string& comment) { - std::string snapshot_name; + std::string sname; { std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition - snapshot_name = name.empty() ? generate_snapshot_name() : name; + sname = snapshot_name.empty() ? generate_snapshot_name() : snapshot_name; - const auto [it, success] = snapshots.try_emplace(snapshot_name, nullptr); + const auto [it, success] = snapshots.try_emplace(sname, nullptr); if (!success) { - mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", snapshot_name)); - throw SnapshotNameTaken{vm_name, snapshot_name}; + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", sname)); + throw SnapshotNameTaken{vm_name, sname}; } auto rollback_on_failure = make_take_snapshot_rollback(it); - auto ret = head_snapshot = it->second = make_specific_snapshot(snapshot_name, comment, specs, head_snapshot); + auto ret = head_snapshot = it->second = make_specific_snapshot(sname, comment, specs, head_snapshot); ret->capture(); ++snapshot_count; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index f90b38df5ea..20c6dc8c166 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -61,7 +61,7 @@ class BaseVirtualMachine : public VirtualMachine // TODO: the VM should know its directory, but that is true of everything in its VMDescription; pulling that from // derived classes is a big refactor std::shared_ptr take_snapshot(const VMSpecs& specs, - const std::string& name, + const std::string& snapshot_name, const std::string& comment) override; void rename_snapshot(const std::string& old_name, const std::string& new_name) override; void delete_snapshot(const std::string& name) override; From a935fc566fc0d7566f6edc27e0004a932a250072 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 15:19:45 +0100 Subject: [PATCH 537/627] [cli] Add a `--snapshots` flag to `info` --- src/client/cli/cmd/info.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 7d618b9ad3b..8691c769c87 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -68,13 +68,17 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) "no-runtime-information", "Retrieve from the daemon only the information obtained without running commands on the instance"); noRuntimeInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); - QCommandLineOption formatOption( + QCommandLineOption snapshots_option{"snapshots", + "Display detailed information about the snapshots of specified instances. This " + "option has no effect on snapshot arguments. Omit instance/snapshot arguments " + "to obtain detailed information on all the snapshots of all instances."}; + QCommandLineOption format_option( format_option_name, "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", format_option_name, "table"); - parser->addOptions({all_option, noRuntimeInfoOption, formatOption}); + parser->addOptions({all_option, noRuntimeInfoOption, snapshots_option, formatOption}); auto status = parser->commandParse(this); if (status != ParseCode::Ok) @@ -103,7 +107,8 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); - if (instance_found && snapshot_found && parser->value(format_option_name) == "csv") + if (instance_found && snapshot_found && parser->value(format_option_name) == "csv" && + !parser->isSet(snapshots_option)) { cerr << "Mixed snapshot and instance arguments are not supported with CSV format\n"; return ParseCode::CommandLineError; From 031f1d8cd707548e88d818573df05cb016e8e957 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 12:38:30 +0100 Subject: [PATCH 538/627] [cli] Use consistent form in `info` options Add a dot at the end of options descriptions, where missing, to make them consistent with the new `--snapshots` option and follow the advise in Qt docs. Use camel case for `format_option`. --- src/client/cli/cmd/info.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 8691c769c87..b06e09655fb 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -62,11 +62,11 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) "Names of instances or snapshots to display information about", "[.snapshot] [[.snapshot] ...]"); - QCommandLineOption all_option(all_option_name, "Display info for all instances"); + QCommandLineOption all_option(all_option_name, "Display info for all instances."); all_option.setFlags(QCommandLineOption::HiddenFromHelp); QCommandLineOption noRuntimeInfoOption( "no-runtime-information", - "Retrieve from the daemon only the information obtained without running commands on the instance"); + "Retrieve from the daemon only the information obtained without running commands on the instance."); noRuntimeInfoOption.setFlags(QCommandLineOption::HiddenFromHelp); QCommandLineOption snapshots_option{"snapshots", "Display detailed information about the snapshots of specified instances. This " @@ -74,11 +74,11 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) "to obtain detailed information on all the snapshots of all instances."}; QCommandLineOption format_option( format_option_name, - "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml", + "Output info in the requested format.\nValid formats are: table (default), json, csv and yaml.", format_option_name, "table"); - parser->addOptions({all_option, noRuntimeInfoOption, snapshots_option, formatOption}); + parser->addOptions({all_option, noRuntimeInfoOption, snapshots_option, format_option}); auto status = parser->commandParse(this); if (status != ParseCode::Ok) From 5368010d7a7fcb720368b12de6682420e83f5259 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 15:21:00 +0100 Subject: [PATCH 539/627] [rpc] Add and set a snapshots flag in InfoRequest --- src/client/cli/cmd/info.cpp | 6 ++++-- src/rpc/multipass.proto | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index b06e09655fb..22ab54133ba 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -107,8 +107,10 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); - if (instance_found && snapshot_found && parser->value(format_option_name) == "csv" && - !parser->isSet(snapshots_option)) + const auto& snapshots_only = parser->isSet(snapshots_option); + request.set_snapshots(snapshots_only); + + if (instance_found && snapshot_found && parser->value(format_option_name) == "csv" && !snapshots_only) { cerr << "Mixed snapshot and instance arguments are not supported with CSV format\n"; return ParseCode::CommandLineError; diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index f21328314b5..7c1aa503dc7 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -169,6 +169,7 @@ message InfoRequest { repeated InstanceSnapshotPair instances_snapshots = 1; int32 verbosity_level = 3; bool no_runtime_information = 4; + bool snapshots = 5; } message IdMap { From ab6b659569c693a2c87f93c749e721d277478bd1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 15:16:31 +0100 Subject: [PATCH 540/627] [daemon] Fill details on `info --snapshots` --- src/daemon/daemon.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a46f4f64a0c..d478116ecae 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1706,6 +1706,7 @@ try // clang-format on InstanceSnapshotsMap instance_snapshots_map; bool have_mounts = false; bool deleted = false; + bool snapshots_only = request->snapshots(); auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; @@ -1716,15 +1717,28 @@ try // clang-format on try { + // TODO@ricab streamline this code if (all_or_none) - populate_instance_info(vm, - response.add_details(), - request->no_runtime_information(), - deleted, - have_mounts); + { + if (snapshots_only) + for (const auto& snapshot : vm.view_snapshots()) + populate_snapshot_info(vm, + snapshot, + response.add_details(), + have_mounts); // TODO@snapshots have_mounts doesn't make much sense here + else + populate_instance_info(vm, + response.add_details(), + request->no_runtime_information(), + deleted, + have_mounts); + } for (const auto& snapshot : pick) - populate_snapshot_info(vm, vm.get_snapshot(snapshot), response.add_details(), have_mounts); + populate_snapshot_info(vm, + vm.get_snapshot(snapshot), + response.add_details(), + have_mounts); // TODO@snapshots have_mounts doesn't make much sense here } catch (const NoSuchSnapshot& e) { From 183ad6167f1d09730b953dfd869475fd43a57286 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 16:52:19 +0100 Subject: [PATCH 541/627] [daemon] Streamline populating info response --- src/daemon/daemon.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d478116ecae..a4f019a3e89 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1708,6 +1708,14 @@ try // clang-format on bool deleted = false; bool snapshots_only = request->snapshots(); + auto populate_info = [&](VirtualMachine& vm, const std::shared_ptr& snapshot) { + auto* details = response.add_details(); + if (snapshot) + populate_snapshot_info(vm, snapshot, details, have_mounts); // TODO@snapshots remove have_mounts + else + populate_instance_info(vm, details, request->no_runtime_information(), deleted, have_mounts); + }; + auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; @@ -1717,28 +1725,17 @@ try // clang-format on try { - // TODO@ricab streamline this code if (all_or_none) { if (snapshots_only) for (const auto& snapshot : vm.view_snapshots()) - populate_snapshot_info(vm, - snapshot, - response.add_details(), - have_mounts); // TODO@snapshots have_mounts doesn't make much sense here + populate_info(vm, snapshot); else - populate_instance_info(vm, - response.add_details(), - request->no_runtime_information(), - deleted, - have_mounts); + populate_info(vm, nullptr); } - for (const auto& snapshot : pick) - populate_snapshot_info(vm, - vm.get_snapshot(snapshot), - response.add_details(), - have_mounts); // TODO@snapshots have_mounts doesn't make much sense here + for (const auto& snapshot_name : pick) + populate_info(vm, vm.get_snapshot(snapshot_name)); } catch (const NoSuchSnapshot& e) { From d09c9164891809854d0e77bb25cbaa76650421e2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 15:23:19 +0100 Subject: [PATCH 542/627] [daemon] Fix repeated snapshot info Fix snapshot1 repeated in `multipass info foo foo.snapshot1 --snapshots` --- src/daemon/daemon.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a4f019a3e89..ab1684f67fa 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1728,14 +1728,22 @@ try // clang-format on if (all_or_none) { if (snapshots_only) + { + for (const auto& snapshot_name : pick) + vm.get_snapshot(snapshot_name); // still verify validity of explicit snapshot names for (const auto& snapshot : vm.view_snapshots()) populate_info(vm, snapshot); + } else + { populate_info(vm, nullptr); + for (const auto& snapshot_name : pick) + populate_info(vm, vm.get_snapshot(snapshot_name)); + } } - - for (const auto& snapshot_name : pick) - populate_info(vm, vm.get_snapshot(snapshot_name)); + else + for (const auto& snapshot_name : pick) + populate_info(vm, vm.get_snapshot(snapshot_name)); } catch (const NoSuchSnapshot& e) { From caeec0a0383c729a518b8f84f407f502fcedc4e6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 17 Oct 2023 19:11:13 +0100 Subject: [PATCH 543/627] [bash] Add `info --snapshots` to bash completion --- completions/bash/multipass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 4b0493872f6..3e00201b044 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -200,7 +200,7 @@ _multipass_complete() opts="${opts} --working-directory --no-map-working-directory" ;; "info") - _add_nonrepeating_args "--format" + _add_nonrepeating_args "--format --snapshots" ;; "list"|"ls"|"networks"|"aliases") _add_nonrepeating_args "--format" From be06d01409edd77a04c79261664e553c0e4168b0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 16:43:16 +0100 Subject: [PATCH 544/627] [daemon] Extract processing of snapshot info --- src/daemon/daemon.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index ab1684f67fa..89c207b1f16 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1716,34 +1716,35 @@ try // clang-format on populate_instance_info(vm, details, request->no_runtime_information(), deleted, have_mounts); }; + auto process_snapshot_pick = + [populate_info](VirtualMachine& vm, const SnapshotPick& snapshot_pick, bool snapshots_only) { + for (const auto& snapshot_name : snapshot_pick.pick) + { + const auto snapshot = vm.get_snapshot(snapshot_name); // verify validity even if unused + if (!snapshot_pick.all_or_none || !snapshots_only) + populate_info(vm, snapshot); + } + }; + auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; const auto& it = instance_snapshots_map.find(name); - const auto& [pick, all_or_none] = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + const auto& snapshot_pick = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; + const auto& [pick, all_or_none] = snapshot_pick; try { + process_snapshot_pick(vm, snapshot_pick, snapshots_only); // TODO@ricab capture snapshots only if (all_or_none) { if (snapshots_only) - { - for (const auto& snapshot_name : pick) - vm.get_snapshot(snapshot_name); // still verify validity of explicit snapshot names for (const auto& snapshot : vm.view_snapshots()) populate_info(vm, snapshot); - } else - { populate_info(vm, nullptr); - for (const auto& snapshot_name : pick) - populate_info(vm, vm.get_snapshot(snapshot_name)); - } } - else - for (const auto& snapshot_name : pick) - populate_info(vm, vm.get_snapshot(snapshot_name)); } catch (const NoSuchSnapshot& e) { From 6863937e6addd3155c8994f59afd6271f357e78e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 16:41:10 +0100 Subject: [PATCH 545/627] [daemon] Replace lambda param with capture --- src/daemon/daemon.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 89c207b1f16..3be877c8d28 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1716,15 +1716,15 @@ try // clang-format on populate_instance_info(vm, details, request->no_runtime_information(), deleted, have_mounts); }; - auto process_snapshot_pick = - [populate_info](VirtualMachine& vm, const SnapshotPick& snapshot_pick, bool snapshots_only) { - for (const auto& snapshot_name : snapshot_pick.pick) - { - const auto snapshot = vm.get_snapshot(snapshot_name); // verify validity even if unused - if (!snapshot_pick.all_or_none || !snapshots_only) - populate_info(vm, snapshot); - } - }; + auto process_snapshot_pick = [populate_info, snapshots_only](VirtualMachine& vm, + const SnapshotPick& snapshot_pick) { + for (const auto& snapshot_name : snapshot_pick.pick) + { + const auto snapshot = vm.get_snapshot(snapshot_name); // verify validity even if unused + if (!snapshot_pick.all_or_none || !snapshots_only) + populate_info(vm, snapshot); + } + }; auto fetch_detailed_report = [&](VirtualMachine& vm) { fmt::memory_buffer errors; @@ -1736,7 +1736,7 @@ try // clang-format on try { - process_snapshot_pick(vm, snapshot_pick, snapshots_only); // TODO@ricab capture snapshots only + process_snapshot_pick(vm, snapshot_pick); if (all_or_none) { if (snapshots_only) From effe4e1f80279df0a75df47c96229b4a77e8cf9d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 12:58:44 +0100 Subject: [PATCH 546/627] [rpc] Add `snapshots` flag to `InfoReply` Add a `snapshots` boolean to `InfoReply`, to convey what entity was missing when the `details` list is empty. --- src/rpc/multipass.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 7c1aa503dc7..7d5f254ddfe 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -248,6 +248,7 @@ message DetailedInfoItem { message InfoReply { repeated DetailedInfoItem details = 1; + bool snapshots = 2; // useful to determine what entity (instance/snapshot) was absent when details are empty string log_line = 3; } From c0ad644abd928e3b32ce07ad1be08440099f80f9 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 16:45:56 +0100 Subject: [PATCH 547/627] [daemon] Get rid of superfluous structured binding --- src/daemon/daemon.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 3be877c8d28..62a47062a59 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1732,12 +1732,11 @@ try // clang-format on const auto& it = instance_snapshots_map.find(name); const auto& snapshot_pick = it == instance_snapshots_map.end() ? SnapshotPick{{}, true} : it->second; - const auto& [pick, all_or_none] = snapshot_pick; try { process_snapshot_pick(vm, snapshot_pick); - if (all_or_none) + if (snapshot_pick.all_or_none) { if (snapshots_only) for (const auto& snapshot : vm.view_snapshots()) From f0d8cbfeb45d09b0726beb3c079fc2f4b5e82041 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 12:59:03 +0100 Subject: [PATCH 548/627] [daemon] Set `InfoReply::snapshots` --- src/daemon/daemon.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a4f019a3e89..701b5e323a7 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1707,6 +1707,7 @@ try // clang-format on bool have_mounts = false; bool deleted = false; bool snapshots_only = request->snapshots(); + response.set_snapshots(snapshots_only); auto populate_info = [&](VirtualMachine& vm, const std::shared_ptr& snapshot) { auto* details = response.add_details(); From 8dbc0cab3d5693eba7171a1097da6408a6c2939e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 13:02:27 +0100 Subject: [PATCH 549/627] [cli] Adapt output of `info` when nothing found Explicitly say that no instances or snapshots were found in the `info` command, to better align with `list` (and other commands like `networks` and `find`). --- src/client/cli/formatter/table_formatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 8af28058770..9756d9f5f38 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -361,7 +361,7 @@ std::string mp::TableFormatter::format(const InfoReply& reply) const if (!reply.details().empty()) output.pop_back(); else - output = "\n"; + output = fmt::format("No {} found.\n", reply.snapshots() ? "snapshots" : "instances"); return output; } From 2205dbb41cbd94d1e84e1b73cd3c26ef72705666 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 16:49:21 +0100 Subject: [PATCH 550/627] [tests] Adapt test for empty info output --- tests/test_output_formatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 7c55444c19f..53fca0dfd79 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -850,7 +850,7 @@ const std::vector orderable_list_info_formatter_outputs{ "prosperous-spadefish snapshot10 snapshot2 --\n", "table_list_multiple_snapshots"}, - {&table_formatter, &empty_info_reply, "\n", "table_info_empty"}, + {&table_formatter, &empty_info_reply, "No instances found.\n", "table_info_empty"}, {&table_formatter, &single_instance_info_reply, "Name: foo\n" From 34e96d5988d8acefe93ee63632b64eb5aeeeaa2a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 16:53:49 +0100 Subject: [PATCH 551/627] [tests] Test no-snapshots info reply. --- tests/test_output_formatter.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_output_formatter.cpp b/tests/test_output_formatter.cpp index 53fca0dfd79..b895d2b0717 100644 --- a/tests/test_output_formatter.cpp +++ b/tests/test_output_formatter.cpp @@ -242,6 +242,13 @@ auto construct_multiple_lines_networks_reply() return networks_reply; } +auto construct_empty_info_snapshot_reply() +{ + mp::InfoReply info_reply; + info_reply.set_snapshots(true); + return info_reply; +} + auto construct_single_instance_info_reply() { mp::InfoReply info_reply; @@ -802,6 +809,7 @@ const auto one_long_line_networks_reply = construct_one_long_line_networks_reply const auto multiple_lines_networks_reply = construct_multiple_lines_networks_reply(); const auto empty_info_reply = mp::InfoReply(); +const auto empty_info_snapshot_reply = construct_empty_info_snapshot_reply(); const auto single_instance_info_reply = construct_single_instance_info_reply(); const auto multiple_instances_info_reply = construct_multiple_instances_info_reply(); const auto single_snapshot_info_reply = construct_single_snapshot_info_reply(); @@ -851,6 +859,7 @@ const std::vector orderable_list_info_formatter_outputs{ "table_list_multiple_snapshots"}, {&table_formatter, &empty_info_reply, "No instances found.\n", "table_info_empty"}, + {&table_formatter, &empty_info_snapshot_reply, "No snapshots found.\n", "table_info_snapshot_empty"}, {&table_formatter, &single_instance_info_reply, "Name: foo\n" From 685570daee0ac0ed73a8434fed117ba6cefeff8d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 18:37:14 +0100 Subject: [PATCH 552/627] [qemu] Tolerate missing snapshot on `delete` When deleting a snapshot, tolerate cases where the underlying snapshot is missing in QEMU. --- src/platform/backends/qemu/qemu_snapshot.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index b3047140f62..dd3f8fb4747 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -70,7 +70,7 @@ mp::QemuSnapshot::QemuSnapshot(const QString& filename, QemuVirtualMachine& vm, void mp::QemuSnapshot::capture_impl() { - auto tag = derive_id(); + const auto tag = derive_id(); // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) @@ -85,7 +85,11 @@ void mp::QemuSnapshot::capture_impl() void mp::QemuSnapshot::erase_impl() { - mp::backend::checked_exec_qemu_img(make_delete_spec(derive_id(), image_path)); + const auto tag = derive_id(); + if (!backend::instance_image_has_snapshot(image_path, tag)) + return; // already gone + + mp::backend::checked_exec_qemu_img(make_delete_spec(tag, image_path)); } void mp::QemuSnapshot::apply_impl() From bccb34b4bcef7f7591ba2dd3691776b58db7f28f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 19 Oct 2023 17:06:24 +0100 Subject: [PATCH 553/627] [daemon] Fix interpreting of empty snapshot args Fix interpreting of snapshot arguments with empty name, i.e. ending in dot (like "."). Refuse such arguments, instead of interpreting them as instance arguments (i.e. equivalent to ""). --- src/daemon/daemon.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a46f4f64a0c..0ef731434a0 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1166,13 +1166,12 @@ InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& ins for (const auto& it : instances_snapshots) { const auto& instance = it.instance_name(); - const auto& snapshot = it.snapshot_name(); auto& snapshot_pick = instance_snapshots_map[instance]; - if (snapshot.empty()) + if (!it.has_snapshot_name()) snapshot_pick.all_or_none = true; else - snapshot_pick.pick.insert(snapshot); + snapshot_pick.pick.insert(it.snapshot_name()); } return instance_snapshots_map; From f12e6eacd2789231f32d138ce31797a6855c1be8 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 25 May 2023 20:46:24 -0700 Subject: [PATCH 554/627] [bash] add snapshot and restore bash completions --- completions/bash/multipass | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 6e9aa70e08a..3100f35fc05 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -186,7 +186,7 @@ _multipass_complete() cmd="${COMP_WORDS[1]}" prev_opts=false multipass_cmds="authenticate transfer delete exec find help info launch list mount networks \ - purge recover shell start stop suspend restart umount version get set \ + purge recover restart restore shell snapshot start stop suspend umount version get set \ alias aliases unalias" if [[ "${multipass_cmds}" =~ " ${cmd} " || "${multipass_cmds}" =~ ^${cmd} || "${multipass_cmds}" =~ \ ${cmd}$ ]]; @@ -202,7 +202,10 @@ _multipass_complete() "info") _add_nonrepeating_args "--all --format" ;; - "list"|"ls"|"networks"|"aliases") + "list"|"ls") + _add_nonrepeating_arg "--format --snapshots" + ;; + "networks"|"aliases") _add_nonrepeating_args "--format" ;; "delete") @@ -231,6 +234,12 @@ _multipass_complete() "transfer"|"copy-files") _add_nonrepeating_args "--parents --recursive" ;; + "snapshot") + opts="${opts} --name --comment" + ;; + "restore") + opts="${opts} --destructive" + ;; esac if [[ ${prev} == -* ]]; then @@ -308,6 +317,9 @@ _multipass_complete() "recover") _multipass_instances "Deleted" ;; + "snapshot"|"restore") + _multipass_instances "Stopped" + ;; "mount") local source_set=0 local prev From bfd4a4c3d729b71c66cf3f8494a30b161cd856ad Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 18 Oct 2023 18:49:33 +0100 Subject: [PATCH 555/627] [qemu] Log missing QEMU snapshots on delete --- src/platform/backends/qemu/qemu_snapshot.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index dd3f8fb4747..f12e363b846 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -19,6 +19,7 @@ #include "qemu_virtual_machine.h" #include "shared/qemu_img_utils/qemu_img_utils.h" +#include #include #include #include @@ -86,10 +87,15 @@ void mp::QemuSnapshot::capture_impl() void mp::QemuSnapshot::erase_impl() { const auto tag = derive_id(); - if (!backend::instance_image_has_snapshot(image_path, tag)) - return; // already gone - - mp::backend::checked_exec_qemu_img(make_delete_spec(tag, image_path)); + if (backend::instance_image_has_snapshot(image_path, tag)) + mp::backend::checked_exec_qemu_img(make_delete_spec(tag, image_path)); + else + mpl::log( + mpl::Level::warning, + BaseSnapshot::get_name(), + fmt::format("Could not find the underlying QEMU snapshot. Assuming it is already gone. Image: {}; tag: {}", + image_path, + tag)); } void mp::QemuSnapshot::apply_impl() From ef6e353ce63c54e3c06303ddb1a1f4c15bd17670 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Thu, 6 Jul 2023 00:36:06 -0700 Subject: [PATCH 556/627] [bash] two part bash completion with restorable instances and then their respective snapshots --- completions/bash/multipass | 64 +++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 3100f35fc05..872e372049a 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -34,6 +34,62 @@ _multipass_complete() opts=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 1 ) } + _multipass_restorable_instances_with_period() + { + local instances=$( multipass info --all --no-runtime-information --format=csv \ + | \tail -n +2 \ + | \awk -F'("[^"]*")?,|\r?\n' '$2 == "Stopped" && $16 > 0' \ + | \cut -d',' -f 1 ) + local found + + _get_comp_words_by_ref -n := -w WORDS -i CWORD cur prev + for instance in $instances; do + found=0 + for ((i=2; i/dev/null" + local snapshots=$( \eval $cmd | \grep -Ev '(\+--|Instance)' | \cut -d',' -f 2 ) + + for snapshot in $snapshots; do + opts="${opts} ${instance}.${snapshot}" + done + elif [[ ${instance_found} == 0 ]]; then + _multipass_restorable_instances_with_period + compopt -o nospace + fi + } + _multipass_instances_with_colon() { local opts_bak="${opts}" @@ -237,9 +293,6 @@ _multipass_complete() "snapshot") opts="${opts} --name --comment" ;; - "restore") - opts="${opts} --destructive" - ;; esac if [[ ${prev} == -* ]]; then @@ -317,9 +370,12 @@ _multipass_complete() "recover") _multipass_instances "Deleted" ;; - "snapshot"|"restore") + "snapshot") _multipass_instances "Stopped" ;; + "restore") + _unspecified_restore_vars + ;; "mount") local source_set=0 local prev From 60667cd4d5a2932d9eaf7c4da1915dd98bd6f46a Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 29 Sep 2023 00:04:16 -0500 Subject: [PATCH 557/627] [bash] remove auto-period at end of instances refactor duplicate code into several functions add snapshot options for delete and info --- completions/bash/multipass | 89 ++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 872e372049a..48d41ecffd2 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -26,68 +26,40 @@ _multipass_complete() _add_nonrepeating_args "$instances" } - # Set $opts to the list of available networks. - _multipass_networks() + _multipass_snapshots() { - local cmd="multipass networks --format=csv 2>/dev/null" + local instance="${cur%%.*}" + local cmd="multipass list --snapshots "${instance}" --format=csv 2>/dev/null" + local snapshots=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 2 ) - opts=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 1 ) + _add_nonrepeating_args $snapshots + } + + _multipass_instances_and_snapshots() + { + if [[ "${cur}" =~ '.' ]]; then + _multipass_snapshots + fi + + _multipass_instances } - _multipass_restorable_instances_with_period() + _multipass_restorable_instances() { local instances=$( multipass info --all --no-runtime-information --format=csv \ | \tail -n +2 \ - | \awk -F'("[^"]*")?,|\r?\n' '$2 == "Stopped" && $16 > 0' \ + | \awk -F',|\r?\n' '$2 == "Stopped" && $16 > 0' \ | \cut -d',' -f 1 ) - local found - _get_comp_words_by_ref -n := -w WORDS -i CWORD cur prev - for instance in $instances; do - found=0 - for ((i=2; i/dev/null" - local snapshots=$( \eval $cmd | \grep -Ev '(\+--|Instance)' | \cut -d',' -f 2 ) + local cmd="multipass networks --format=csv 2>/dev/null" - for snapshot in $snapshots; do - opts="${opts} ${instance}.${snapshot}" - done - elif [[ ${instance_found} == 0 ]]; then - _multipass_restorable_instances_with_period - compopt -o nospace - fi + opts=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 1 ) } _multipass_instances_with_colon() @@ -234,6 +206,8 @@ _multipass_complete() local cur cmd opts prev prev2 prev_opts multipass_aliases unused_aliases _get_comp_words_by_ref -n := -w WORDS -i CWORD cur prev COMPREPLY=() + instances=() + snapshots=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ ${COMP_CWORD} -gt 1 ]]; then @@ -293,6 +267,9 @@ _multipass_complete() "snapshot") opts="${opts} --name --comment" ;; + "restore") + opts="${opts} --destructive" + ;; esac if [[ ${prev} == -* ]]; then @@ -364,9 +341,12 @@ _multipass_complete() _multipass_instances "Stopped" _multipass_instances "Suspended" ;; - "delete"|"info"|"umount"|"unmount") + "umount"|"unmount") _multipass_instances ;; + "delete"|"info") + _multipass_instances_and_snapshots + ;; "recover") _multipass_instances "Deleted" ;; @@ -374,7 +354,11 @@ _multipass_complete() _multipass_instances "Stopped" ;; "restore") - _unspecified_restore_vars + if [[ "${cur}" =~ '.' ]]; then + _multipass_snapshots + else + _multipass_restorable_instances + fi ;; "mount") local source_set=0 @@ -419,6 +403,9 @@ _multipass_complete() opts=$multipass_cmds ;; esac + + opts+="${instances}" + opts+="${snapshots}" fi if [[ ${COMP_CWORD} -eq 1 ]]; then From a1af37a132f2a5d31e4022a6920de47964e39fcb Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 3 Oct 2023 22:51:58 -0500 Subject: [PATCH 558/627] [bash] flatten and expand item lists --- completions/bash/multipass | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 48d41ecffd2..8314e0430b3 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -30,9 +30,9 @@ _multipass_complete() { local instance="${cur%%.*}" local cmd="multipass list --snapshots "${instance}" --format=csv 2>/dev/null" - local snapshots=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 2 ) + local snapshots=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 2 | \tr '\r\n' ' ' ) - _add_nonrepeating_args $snapshots + _add_nonrepeating_args "$snapshots" } _multipass_instances_and_snapshots() @@ -49,9 +49,10 @@ _multipass_complete() local instances=$( multipass info --all --no-runtime-information --format=csv \ | \tail -n +2 \ | \awk -F',|\r?\n' '$2 == "Stopped" && $16 > 0' \ - | \cut -d',' -f 1 ) + | \cut -d',' -f 1 \ + | \tr '\r\n' ' ') - _add_nonrepeating_args $instances + _add_nonrepeating_args "$instances" } # Set $opts to the list of available networks. From 885c126724d56dc990a77a5ec6563a64f37ddf92 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 3 Oct 2023 22:52:49 -0500 Subject: [PATCH 559/627] [bash] format fetched snapshots as . --- completions/bash/multipass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 8314e0430b3..97a84659404 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -30,7 +30,7 @@ _multipass_complete() { local instance="${cur%%.*}" local cmd="multipass list --snapshots "${instance}" --format=csv 2>/dev/null" - local snapshots=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 2 | \tr '\r\n' ' ' ) + local snapshots=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 1,2 | \tr ',' '.' | \tr '\r\n' ' ' ) _add_nonrepeating_args "$snapshots" } From 2e31ad2f219880a1f7dae790bfd170a918f1430b Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 16 Oct 2023 09:19:14 -0500 Subject: [PATCH 560/627] [bash] add snapshot flags as nonrepeating args --- completions/bash/multipass | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 97a84659404..35a963302d5 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -207,8 +207,6 @@ _multipass_complete() local cur cmd opts prev prev2 prev_opts multipass_aliases unused_aliases _get_comp_words_by_ref -n := -w WORDS -i CWORD cur prev COMPREPLY=() - instances=() - snapshots=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ ${COMP_CWORD} -gt 1 ]]; then @@ -266,10 +264,10 @@ _multipass_complete() _add_nonrepeating_args "--parents --recursive" ;; "snapshot") - opts="${opts} --name --comment" + _add_nonrepeating_args "--name --comment" ;; "restore") - opts="${opts} --destructive" + _add_nonrepeating_args "--destructive" ;; esac @@ -404,9 +402,6 @@ _multipass_complete() opts=$multipass_cmds ;; esac - - opts+="${instances}" - opts+="${snapshots}" fi if [[ ${COMP_CWORD} -eq 1 ]]; then From 159be738560f40755dd9b8beb069da70a5a28604 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 17 Oct 2023 13:19:42 -0500 Subject: [PATCH 561/627] [bash] fix typo --- completions/bash/multipass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 35a963302d5..3c88887a9d9 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -232,7 +232,7 @@ _multipass_complete() _add_nonrepeating_args "--all --format" ;; "list"|"ls") - _add_nonrepeating_arg "--format --snapshots" + _add_nonrepeating_args "--format --snapshots" ;; "networks"|"aliases") _add_nonrepeating_args "--format" From 04e5857bb85673e0231de50246344c9bbfb0f046 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 17 Oct 2023 13:20:19 -0500 Subject: [PATCH 562/627] [bash] adapt command for multipass list --- completions/bash/multipass | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 3c88887a9d9..1c0cd3e4e1e 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -29,8 +29,8 @@ _multipass_complete() _multipass_snapshots() { local instance="${cur%%.*}" - local cmd="multipass list --snapshots "${instance}" --format=csv 2>/dev/null" - local snapshots=$( \eval $cmd | \tail -n +2 | \cut -d',' -f 1,2 | \tr ',' '.' | \tr '\r\n' ' ' ) + local cmd="multipass list --snapshots --format=csv 2>/dev/null | \tail -n +2 | \awk -F',' '\$1 == \"$1\"'" + local snapshots=$( \eval $cmd | \cut -d',' -f 1,2 | \tr ',' '.' | \tr '\r\n' ' ' ) _add_nonrepeating_args "$snapshots" } From 1198a3b9004f89df401a6e6d7a61b67129e4975f Mon Sep 17 00:00:00 2001 From: sharder996 Date: Tue, 17 Oct 2023 13:21:46 -0500 Subject: [PATCH 563/627] [bash] return all restorable snapshots on multipass restore tab completion --- completions/bash/multipass | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 1c0cd3e4e1e..8d2c2c72a33 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -28,7 +28,6 @@ _multipass_complete() _multipass_snapshots() { - local instance="${cur%%.*}" local cmd="multipass list --snapshots --format=csv 2>/dev/null | \tail -n +2 | \awk -F',' '\$1 == \"$1\"'" local snapshots=$( \eval $cmd | \cut -d',' -f 1,2 | \tr ',' '.' | \tr '\r\n' ' ' ) @@ -38,13 +37,13 @@ _multipass_complete() _multipass_instances_and_snapshots() { if [[ "${cur}" =~ '.' ]]; then - _multipass_snapshots + _multipass_snapshots "${cur%%.*}" fi _multipass_instances } - _multipass_restorable_instances() + _multipass_restorable_snapshots() { local instances=$( multipass info --all --no-runtime-information --format=csv \ | \tail -n +2 \ @@ -52,7 +51,9 @@ _multipass_complete() | \cut -d',' -f 1 \ | \tr '\r\n' ' ') - _add_nonrepeating_args "$instances" + for instance in ${instances}; do + _multipass_snapshots "${instance}" + done } # Set $opts to the list of available networks. @@ -353,11 +354,7 @@ _multipass_complete() _multipass_instances "Stopped" ;; "restore") - if [[ "${cur}" =~ '.' ]]; then - _multipass_snapshots - else - _multipass_restorable_instances - fi + _multipass_restorable_snapshots ;; "mount") local source_set=0 From fd6782a4f6764d259562971820beb5b08e6a13b5 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Wed, 18 Oct 2023 11:14:48 -0500 Subject: [PATCH 564/627] [bash] reorder piping --- completions/bash/multipass | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 8d2c2c72a33..f69c424415f 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -18,8 +18,8 @@ _multipass_complete() { local state=$1 - local cmd="multipass list --format=csv --no-ipv4" - [ -n "$state" ] && cmd="$cmd | \tail -n +2 | \awk -F',' '\$2 == \"$state\"'" + local cmd="multipass list --format=csv --no-ipv4 | \tail -n +2" + [ -n "$state" ] && cmd="$cmd | \awk -F',' '\$2 == \"$state\"'" local instances=$( \eval $cmd | \cut -d',' -f 1 | \tr '\r\n' ' ') From b6802c756d9ad49fea5e115c8b4f5cb72c0fcfe7 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 20 Oct 2023 12:26:33 -0500 Subject: [PATCH 565/627] [bash] remove deprecated `--all` from info call --- completions/bash/multipass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index f69c424415f..9c25fee706f 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -45,7 +45,7 @@ _multipass_complete() _multipass_restorable_snapshots() { - local instances=$( multipass info --all --no-runtime-information --format=csv \ + local instances=$( multipass info --no-runtime-information --format=csv \ | \tail -n +2 \ | \awk -F',|\r?\n' '$2 == "Stopped" && $16 > 0' \ | \cut -d',' -f 1 \ From f0b5a584f690c2a34e4829748488267f9c10e643 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Fri, 20 Oct 2023 12:27:17 -0500 Subject: [PATCH 566/627] [bash] suggest all snapshots and instances for info and delete --- completions/bash/multipass | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/completions/bash/multipass b/completions/bash/multipass index 9c25fee706f..7e9a0979ca4 100644 --- a/completions/bash/multipass +++ b/completions/bash/multipass @@ -28,7 +28,9 @@ _multipass_complete() _multipass_snapshots() { - local cmd="multipass list --snapshots --format=csv 2>/dev/null | \tail -n +2 | \awk -F',' '\$1 == \"$1\"'" + local instance=$1 + local cmd="multipass list --snapshots --format=csv 2>/dev/null | \tail -n +2" + [ -n "$instance" ] && cmd="$cmd | \awk -F',' '\$1 == \"$instance\"'" local snapshots=$( \eval $cmd | \cut -d',' -f 1,2 | \tr ',' '.' | \tr '\r\n' ' ' ) _add_nonrepeating_args "$snapshots" @@ -36,10 +38,7 @@ _multipass_complete() _multipass_instances_and_snapshots() { - if [[ "${cur}" =~ '.' ]]; then - _multipass_snapshots "${cur%%.*}" - fi - + _multipass_snapshots _multipass_instances } From 339e8d2432303dffad1fcbbec71b4299066adc13 Mon Sep 17 00:00:00 2001 From: sharder996 Date: Mon, 23 Oct 2023 15:33:17 -0500 Subject: [PATCH 567/627] [clang] edit formatting --- include/multipass/snapshot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 769bbdfa87f..b0abb949c67 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -64,7 +64,7 @@ class Snapshot : private DisabledCopyMove // precondition: capture only once virtual void capture() = 0; // not using the constructor, we need snapshot objects for existing snapshots too // precondition: call only on captured snapshots - virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits + virtual void erase() = 0; // not using the destructor, we want snapshots to stick around when daemon quits // precondition: call only on captured snapshots virtual void apply() = 0; }; From 062e4b18eed31d86f6139e3290740a007f6e05af Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 12:38:33 +0100 Subject: [PATCH 568/627] [exception] Adhere to conventional naming suffix --- include/multipass/exceptions/snapshot_exceptions.h | 8 ++++---- src/daemon/daemon.cpp | 8 ++++---- src/daemon/snapshot_settings_handler.cpp | 2 +- .../backends/shared/base_virtual_machine.cpp | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/multipass/exceptions/snapshot_exceptions.h b/include/multipass/exceptions/snapshot_exceptions.h index 0e8c8c4001f..3e65a208568 100644 --- a/include/multipass/exceptions/snapshot_exceptions.h +++ b/include/multipass/exceptions/snapshot_exceptions.h @@ -25,19 +25,19 @@ namespace multipass { -class SnapshotNameTaken : public std::runtime_error +class SnapshotNameTakenException : public std::runtime_error { public: - SnapshotNameTaken(const std::string& instance_name, const std::string& snapshot_name) + SnapshotNameTakenException(const std::string& instance_name, const std::string& snapshot_name) : std::runtime_error{fmt::format(R"(Snapshot "{}.{}" already exists)", instance_name, snapshot_name)} { } }; -class NoSuchSnapshot : public std::runtime_error +class NoSuchSnapshotException : public std::runtime_error { public: - NoSuchSnapshot(const std::string& vm_name, const std::string& snapshot_name) + NoSuchSnapshotException(const std::string& vm_name, const std::string& snapshot_name) : std::runtime_error{fmt::format("No such snapshot: {}.{}", vm_name, snapshot_name)} { } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9c1a69ec973..905255618ef 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1765,7 +1765,7 @@ try // clang-format on populate_info(vm, nullptr); } } - catch (const NoSuchSnapshot& e) + catch (const NoSuchSnapshotException& e) { add_fmt_to(errors, e.what()); } @@ -1877,7 +1877,7 @@ try // clang-format on populate_snapshot_fundamentals(snapshot, fundamentals); } } - catch (const NoSuchSnapshot& e) + catch (const NoSuchSnapshotException& e) { add_fmt_to(errors, e.what()); } @@ -2548,7 +2548,7 @@ try status_promise->set_value(status); } -catch (const SnapshotNameTaken& e) +catch (const SnapshotNameTakenException& e) { status_promise->set_value(grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, e.what(), "")); } @@ -2631,7 +2631,7 @@ try status_promise->set_value(status); } -catch (const mp::NoSuchSnapshot& e) +catch (const mp::NoSuchSnapshotException& e) { status_promise->set_value(grpc::Status{grpc::StatusCode::NOT_FOUND, e.what(), ""}); } diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 0e922cdd45a..3451a37fc66 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -150,7 +150,7 @@ auto mp::SnapshotSettingsHandler::find_snapshot(const std::string& instance_name { return find_instance(instance_name)->get_snapshot(snapshot_name); } - catch (const NoSuchSnapshot& e) + catch (const NoSuchSnapshotException& e) { throw SnapshotSettingsException{e.what()}; } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f52d665cbc2..a319542fb97 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -143,7 +143,7 @@ std::shared_ptr BaseVirtualMachine::get_snapshot(const std::stri } catch (const std::out_of_range&) { - throw NoSuchSnapshot{vm_name, name}; + throw NoSuchSnapshotException{vm_name, name}; } } @@ -214,7 +214,7 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& if (!success) { mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", sname)); - throw SnapshotNameTaken{vm_name, sname}; + throw SnapshotNameTakenException{vm_name, sname}; } auto rollback_on_failure = make_take_snapshot_rollback(it); @@ -334,10 +334,10 @@ void BaseVirtualMachine::rename_snapshot(const std::string& old_name, const std: auto old_it = snapshots.find(old_name); if (old_it == snapshots.end()) - throw NoSuchSnapshot{vm_name, old_name}; + throw NoSuchSnapshotException{vm_name, old_name}; if (snapshots.find(new_name) != snapshots.end()) - throw SnapshotNameTaken{vm_name, new_name}; + throw SnapshotNameTakenException{vm_name, new_name}; auto snapshot_node = snapshots.extract(old_it); const auto reinsert_guard = make_reinsert_guard(snapshot_node); // we want this to execute both on failure & success @@ -352,7 +352,7 @@ void BaseVirtualMachine::delete_snapshot(const std::string& name) auto it = snapshots.find(name); if (it == snapshots.end()) - throw NoSuchSnapshot{vm_name, name}; + throw NoSuchSnapshotException{vm_name, name}; auto snapshot = it->second; delete_snapshot_helper(snapshot); @@ -433,7 +433,7 @@ void BaseVirtualMachine::load_snapshot(const QString& filename) if (!success) { mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", name)); - throw SnapshotNameTaken{vm_name, name}; + throw SnapshotNameTakenException{vm_name, name}; } } From b6d10dcb303e80d327b8667fee83b296c5c3d10e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 12:41:43 +0100 Subject: [PATCH 569/627] [exception] Use uniform snapshot exception msgs --- include/multipass/exceptions/snapshot_exceptions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/exceptions/snapshot_exceptions.h b/include/multipass/exceptions/snapshot_exceptions.h index 3e65a208568..33298710fe1 100644 --- a/include/multipass/exceptions/snapshot_exceptions.h +++ b/include/multipass/exceptions/snapshot_exceptions.h @@ -29,7 +29,7 @@ class SnapshotNameTakenException : public std::runtime_error { public: SnapshotNameTakenException(const std::string& instance_name, const std::string& snapshot_name) - : std::runtime_error{fmt::format(R"(Snapshot "{}.{}" already exists)", instance_name, snapshot_name)} + : std::runtime_error{fmt::format("Snapshot already exists: {}.{}", instance_name, snapshot_name)} { } }; From e21408042dcd923a5f296d393d0d7dc0208b3e60 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 12:43:19 +0100 Subject: [PATCH 570/627] [exception] Use same parameter names --- include/multipass/exceptions/snapshot_exceptions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/multipass/exceptions/snapshot_exceptions.h b/include/multipass/exceptions/snapshot_exceptions.h index 33298710fe1..b1974b41411 100644 --- a/include/multipass/exceptions/snapshot_exceptions.h +++ b/include/multipass/exceptions/snapshot_exceptions.h @@ -28,8 +28,8 @@ namespace multipass class SnapshotNameTakenException : public std::runtime_error { public: - SnapshotNameTakenException(const std::string& instance_name, const std::string& snapshot_name) - : std::runtime_error{fmt::format("Snapshot already exists: {}.{}", instance_name, snapshot_name)} + SnapshotNameTakenException(const std::string& vm_name, const std::string& snapshot_name) + : std::runtime_error{fmt::format("Snapshot already exists: {}.{}", vm_name, snapshot_name)} { } }; From ae8799dabe28a031db2e6376a6731091f8dc3354 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 16:30:59 +0100 Subject: [PATCH 571/627] [cli] Use consistent alias namespace declaration --- src/client/cli/cmd/restore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index fabc343b223..15ba986fec6 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -27,7 +27,7 @@ #include namespace mp = multipass; -namespace cmd = mp::cmd; +namespace cmd = multipass::cmd; namespace { From 47fb4e597f981edafc00a120bd6527efe0e30ebd Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 16:31:58 +0100 Subject: [PATCH 572/627] [cli] Add a period at the end of exception message --- src/client/cli/cmd/restore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 15ba986fec6..9bb283aca15 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -74,7 +74,7 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) client_response.set_destructive(confirm_destruction(request.instance())); else throw std::runtime_error("Unable to query client for confirmation. Use '--destructive' to " - "automatically discard current machine state"); + "automatically discard current machine state."); client->Write(client_response); spinner.start(); From 314d4839d84875644c79e6c12a441670599b6a9b Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 16:46:33 +0100 Subject: [PATCH 573/627] [cli] Refactor repeated yes/no regexes --- src/client/cli/cmd/common_cli.h | 4 ++++ src/client/cli/cmd/launch.cpp | 4 ---- src/client/cli/cmd/restore.cpp | 8 -------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/client/cli/cmd/common_cli.h b/src/client/cli/cmd/common_cli.h index 4d8d1a10d22..8e90756c361 100644 --- a/src/client/cli/cmd/common_cli.h +++ b/src/client/cli/cmd/common_cli.h @@ -26,6 +26,8 @@ #include +#include + using RpcMethod = multipass::Rpc::StubInterface; namespace multipass @@ -39,6 +41,8 @@ namespace cmd { const QString all_option_name{"all"}; const QString format_option_name{"format"}; +const std::regex yes{"y|yes", std::regex::icase | std::regex::optimize}; +const std::regex no{"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); diff --git a/src/client/cli/cmd/launch.cpp b/src/client/cli/cmd/launch.cpp index baca36117d4..e2bcfbf703a 100644 --- a/src/client/cli/cmd/launch.cpp +++ b/src/client/cli/cmd/launch.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include namespace mp = multipass; @@ -53,9 +52,6 @@ namespace fs = std::filesystem; namespace { -const std::regex yes{"y|yes", std::regex::icase | std::regex::optimize}; -const std::regex no{"n|no", std::regex::icase | std::regex::optimize}; - constexpr bool on_windows() { // TODO when we have remote client-daemon communication, we need to get the daemon's platform return diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 9bb283aca15..b882025f3fe 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -24,17 +24,9 @@ #include #include -#include - namespace mp = multipass; namespace cmd = multipass::cmd; -namespace -{ -const std::regex yes{"y|yes", std::regex::icase | std::regex::optimize}; -const std::regex no{"n|no", std::regex::icase | std::regex::optimize}; -} // namespace - mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) { if (auto ret = parse_args(parser); ret != ParseCode::Ok) From 5019ce12fc2e4cb06c86bbcf134d1c8ab22e5529 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 16:50:58 +0100 Subject: [PATCH 574/627] [cli] Rename yes/no answer vars Rename yes/no regex vars, now that they are part of a header, to be more specific and less likely to collide with other identifiers. --- src/client/cli/cmd/common_cli.h | 4 ++-- src/client/cli/cmd/launch.cpp | 4 ++-- src/client/cli/cmd/restore.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/cli/cmd/common_cli.h b/src/client/cli/cmd/common_cli.h index 8e90756c361..2376471e1cc 100644 --- a/src/client/cli/cmd/common_cli.h +++ b/src/client/cli/cmd/common_cli.h @@ -41,8 +41,8 @@ namespace cmd { const QString all_option_name{"all"}; const QString format_option_name{"format"}; -const std::regex yes{"y|yes", std::regex::icase | std::regex::optimize}; -const std::regex no{"n|no", std::regex::icase | std::regex::optimize}; +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); diff --git a/src/client/cli/cmd/launch.cpp b/src/client/cli/cmd/launch.cpp index e2bcfbf703a..1b0e9413674 100644 --- a/src/client/cli/cmd/launch.cpp +++ b/src/client/cli/cmd/launch.cpp @@ -637,9 +637,9 @@ bool cmd::Launch::ask_bridge_permission(multipass::LaunchReply& reply) { std::string answer; std::getline(term->cin(), answer); - if (std::regex_match(answer, yes)) + if (std::regex_match(answer, yes_answer)) return true; - else if (std::regex_match(answer, no)) + else if (std::regex_match(answer, no_answer)) return false; else cout << "Please answer yes/no: "; diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index b882025f3fe..4f3b001657d 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -143,8 +143,8 @@ 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) && !std::regex_match(answer, no)) + while (!answer.empty() && !std::regex_match(answer, yes_answer) && !std::regex_match(answer, no_answer)) answer = prompter.prompt(invalid_input); - return std::regex_match(answer, no); + return std::regex_match(answer, no_answer); } From 0a1c8a4aba148fe54a8e2ca7ab05d2e19696ab51 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 17:07:23 +0100 Subject: [PATCH 575/627] [various] Fix namespace usage inconsistencies --- src/platform/backends/shared/base_virtual_machine_factory.h | 2 +- .../backends/shared/qemu_img_utils/qemu_img_utils.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine_factory.h b/src/platform/backends/shared/base_virtual_machine_factory.h index 28377029da1..7dff2907797 100644 --- a/src/platform/backends/shared/base_virtual_machine_factory.h +++ b/src/platform/backends/shared/base_virtual_machine_factory.h @@ -50,7 +50,7 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory Path get_instance_directory(const std::string& name) const override { - return multipass::utils::backend_directory_path(instances_dir, QString::fromStdString(name)); + return utils::backend_directory_path(instances_dir, QString::fromStdString(name)); } void prepare_networking(std::vector& /*extra_interfaces*/) override diff --git a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp index 9c170f2fb24..9bcc1b06dd6 100644 --- a/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp +++ b/src/platform/backends/shared/qemu_img_utils/qemu_img_utils.cpp @@ -30,7 +30,7 @@ #include namespace mp = multipass; -namespace mpp = mp::platform; +namespace mpp = multipass::platform; auto mp::backend::checked_exec_qemu_img(std::unique_ptr spec, const std::string& custom_error_prefix, @@ -87,7 +87,7 @@ mp::Path mp::backend::convert_to_qcow_if_necessary(const mp::Path& image_path) } } -void mp::backend::amend_to_qcow2_v3(const multipass::Path& image_path) +void mp::backend::amend_to_qcow2_v3(const mp::Path& image_path) { checked_exec_qemu_img( std::make_unique(QStringList{"amend", "-o", "compat=1.1", image_path}, image_path)); From 7af570baf1ddb675c35ed633a93325371af38d37 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 17:16:27 +0100 Subject: [PATCH 576/627] [vm] Remove unnecessary inner scope --- .../backends/shared/base_virtual_machine.cpp | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index a319542fb97..09144163429 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -204,32 +204,30 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& { std::string sname; - { - std::unique_lock lock{snapshot_mutex}; - assert_vm_stopped(state); // precondition + std::unique_lock lock{snapshot_mutex}; + assert_vm_stopped(state); // precondition - sname = snapshot_name.empty() ? generate_snapshot_name() : snapshot_name; + sname = snapshot_name.empty() ? generate_snapshot_name() : snapshot_name; - const auto [it, success] = snapshots.try_emplace(sname, nullptr); - if (!success) - { - mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", sname)); - throw SnapshotNameTakenException{vm_name, sname}; - } + const auto [it, success] = snapshots.try_emplace(sname, nullptr); + if (!success) + { + mpl::log(mpl::Level::warning, vm_name, fmt::format("Snapshot name taken: {}", sname)); + throw SnapshotNameTakenException{vm_name, sname}; + } - auto rollback_on_failure = make_take_snapshot_rollback(it); + auto rollback_on_failure = make_take_snapshot_rollback(it); - auto ret = head_snapshot = it->second = make_specific_snapshot(sname, comment, specs, head_snapshot); - ret->capture(); + auto ret = head_snapshot = it->second = make_specific_snapshot(sname, comment, specs, head_snapshot); + ret->capture(); - ++snapshot_count; - persist_generic_snapshot_info(); + ++snapshot_count; + persist_generic_snapshot_info(); - rollback_on_failure.dismiss(); - log_latest_snapshot(std::move(lock)); + rollback_on_failure.dismiss(); + log_latest_snapshot(std::move(lock)); - return ret; - } + return ret; } bool BaseVirtualMachine::updated_deleted_head(std::shared_ptr& snapshot, const Path& head_path) From 5a0f3c71abb086f9db6fb9069e29d2b48e1530df Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 17:17:32 +0100 Subject: [PATCH 577/627] [vm] Remove separate variable declaration --- src/platform/backends/shared/base_virtual_machine.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 09144163429..6717ef045e4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -202,12 +202,10 @@ std::shared_ptr BaseVirtualMachine::take_snapshot(const VMSpecs& const std::string& snapshot_name, const std::string& comment) { - std::string sname; - std::unique_lock lock{snapshot_mutex}; assert_vm_stopped(state); // precondition - sname = snapshot_name.empty() ? generate_snapshot_name() : snapshot_name; + auto sname = snapshot_name.empty() ? generate_snapshot_name() : snapshot_name; const auto [it, success] = snapshots.try_emplace(sname, nullptr); if (!success) From d79aa304d7fbf5e7778ef3132dfd643549391bd8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 17:25:46 +0100 Subject: [PATCH 578/627] [cli] Fix missing newlines in `delete` description --- src/client/cli/cmd/delete.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index e44d4cc70c1..19b09644a18 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -88,8 +88,8 @@ QString cmd::Delete::short_help() const QString cmd::Delete::description() const { return QStringLiteral( - "Delete instances and snapshots. Instances can be purged immediately or later on," - "with the \"purge\" command. Until they are purged, instances can be recovered" + "Delete instances and snapshots. Instances can be purged immediately or later on,\n" + "with the \"purge\" command. Until they are purged, instances can be recovered\n" "with the \"recover\" command. Snapshots cannot be recovered after deletion and must be purged at once."); } From 2baeea570dc36594814a0f165a05009c38edc27a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 18:30:05 +0100 Subject: [PATCH 579/627] [cli] Add tip about quoting snapshot comments --- src/client/cli/cmd/snapshot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index c80794b79a6..c648993223f 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -76,7 +76,8 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) "number of snapshots that were ever taken for .", "name"); QCommandLineOption comment_opt{{"comment", "c", "m"}, - "An optional free comment to associate with the snapshot.", + "An optional free comment to associate with the snapshot. (Tip: quote the text to " + "avoid spaces being parsed by your shell)", "comment"}; parser->addOptions({name_opt, comment_opt}); From 3ec559394738990881382d551932688da7eb3617 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 18:30:18 +0100 Subject: [PATCH 580/627] [daemon] Add newline for readability --- src/daemon/daemon.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 905255618ef..17c8b6e5f40 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1158,6 +1158,7 @@ struct SnapshotPick std::unordered_set pick; bool all_or_none; }; + using InstanceSnapshotPairs = google::protobuf::RepeatedPtrField; using InstanceSnapshotsMap = std::unordered_map; InstanceSnapshotsMap map_snapshots_to_instances(const InstanceSnapshotPairs& instances_snapshots) From 054771e23f49b8aeb83d020753875e10df046b99 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 19:28:11 +0100 Subject: [PATCH 581/627] [daemon] Move initialization of info details Move initialization of details in the `InfoReply` into helper functions to populate those details. --- src/daemon/daemon.cpp | 15 +++++++++------ src/daemon/daemon.h | 7 ++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 17c8b6e5f40..8760c823bd3 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1322,9 +1322,10 @@ void populate_mount_info(const std::unordered_map& mou void populate_snapshot_info(mp::VirtualMachine& vm, std::shared_ptr snapshot, - mp::DetailedInfoItem* info, + mp::InfoReply& response, bool& have_mounts) { + auto* info = response.add_details(); auto snapshot_info = info->mutable_snapshot_info(); auto fundamentals = snapshot_info->mutable_fundamentals(); @@ -1730,11 +1731,10 @@ try // clang-format on response.set_snapshots(snapshots_only); auto populate_info = [&](VirtualMachine& vm, const std::shared_ptr& snapshot) { - auto* details = response.add_details(); if (snapshot) - populate_snapshot_info(vm, snapshot, details, have_mounts); // TODO@snapshots remove have_mounts + populate_snapshot_info(vm, snapshot, response, have_mounts); // TODO@snapshots remove have_mounts else - populate_instance_info(vm, details, request->no_runtime_information(), deleted, have_mounts); + populate_instance_info(vm, response, request->no_runtime_information(), deleted, have_mounts); }; auto process_snapshot_pick = [populate_info, snapshots_only](VirtualMachine& vm, @@ -3425,15 +3425,18 @@ void mp::Daemon::reply_msg(grpc::ServerReaderWriterInterface* se } void mp::Daemon::populate_instance_info(VirtualMachine& vm, - mp::DetailedInfoItem* info, + mp::InfoReply& response, bool no_runtime_info, bool deleted, bool& have_mounts) { - const auto& name = vm.vm_name; + auto* info = response.add_details(); auto instance_info = info->mutable_instance_info(); auto present_state = vm.current_state(); + + const auto& name = vm.vm_name; info->set_name(name); + if (deleted) info->mutable_instance_status()->set_status(mp::InstanceStatus::DELETED); else diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 4d78fa29c6e..4e915a93df9 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -197,11 +197,8 @@ public slots: template void reply_msg(grpc::ServerReaderWriterInterface* server, std::string&& msg, bool sticky = false); - void populate_instance_info(VirtualMachine& vm, - DetailedInfoItem* info, - bool runtime_info, - bool deleted, - bool& have_mounts); + void + populate_instance_info(VirtualMachine& vm, InfoReply& response, bool runtime_info, bool deleted, bool& have_mounts); std::unique_ptr config; std::unordered_map vm_instance_specs; From 518da736caee2ea5deb04d63433ec18b3a7b208c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 19:31:46 +0100 Subject: [PATCH 582/627] [daemon] Remove lambda to populate generic info Call functions to populate either snapshot or instance info directly. --- src/daemon/daemon.cpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 8760c823bd3..304d7f0db55 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1730,20 +1730,13 @@ try // clang-format on bool snapshots_only = request->snapshots(); response.set_snapshots(snapshots_only); - auto populate_info = [&](VirtualMachine& vm, const std::shared_ptr& snapshot) { - if (snapshot) - populate_snapshot_info(vm, snapshot, response, have_mounts); // TODO@snapshots remove have_mounts - else - populate_instance_info(vm, response, request->no_runtime_information(), deleted, have_mounts); - }; - - auto process_snapshot_pick = [populate_info, snapshots_only](VirtualMachine& vm, - const SnapshotPick& snapshot_pick) { + auto process_snapshot_pick = [&response, &have_mounts, snapshots_only](VirtualMachine& vm, + const SnapshotPick& snapshot_pick) { for (const auto& snapshot_name : snapshot_pick.pick) { const auto snapshot = vm.get_snapshot(snapshot_name); // verify validity even if unused if (!snapshot_pick.all_or_none || !snapshots_only) - populate_info(vm, snapshot); + populate_snapshot_info(vm, snapshot, response, have_mounts); } }; @@ -1761,9 +1754,9 @@ try // clang-format on { if (snapshots_only) for (const auto& snapshot : vm.view_snapshots()) - populate_info(vm, snapshot); + populate_snapshot_info(vm, snapshot, response, have_mounts); else - populate_info(vm, nullptr); + populate_instance_info(vm, response, request->no_runtime_information(), deleted, have_mounts); } } catch (const NoSuchSnapshotException& e) From d598f041f14c4666564562a25b5649dc3db91f54 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 19:54:13 +0100 Subject: [PATCH 583/627] [daemon] Use explicit lambda-captures --- src/daemon/daemon.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 304d7f0db55..a44cd3ff857 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1740,7 +1740,14 @@ try // clang-format on } }; - auto fetch_detailed_report = [&](VirtualMachine& vm) { + auto fetch_detailed_report = [this, + &instance_snapshots_map, + process_snapshot_pick, + snapshots_only, + request, + &response, + &have_mounts, + &deleted](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; @@ -1809,7 +1816,7 @@ try // clang-format on request->snapshots() ? (void)response.mutable_snapshot_list() : (void)response.mutable_instance_list(); bool deleted = false; - auto fetch_instance = [&](VirtualMachine& vm) { + auto fetch_instance = [this, request, &response, &deleted](VirtualMachine& vm) { const auto& name = vm.vm_name; auto present_state = vm.current_state(); auto entry = response.mutable_instance_list()->add_instances(); @@ -1856,7 +1863,7 @@ try // clang-format on return grpc::Status::OK; }; - auto fetch_snapshot = [&](VirtualMachine& vm) { + auto fetch_snapshot = [&response](VirtualMachine& vm) { fmt::memory_buffer errors; const auto& name = vm.vm_name; From cf960c1b491be067d73d6c3f0c9554f894e9baf4 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 20:40:10 +0100 Subject: [PATCH 584/627] [daemon] Remove unneeded scope to take snapshot --- src/daemon/daemon.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index a44cd3ff857..11e59953a04 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2537,12 +2537,7 @@ try assert(spec_it != vm_instance_specs.end() && "missing instance specs"); SnapshotReply reply; - - { - const auto snapshot = vm_ptr->take_snapshot(spec_it->second, snapshot_name, request->comment()); - - reply.set_snapshot(snapshot->get_name()); - } + reply.set_snapshot(vm_ptr->take_snapshot(spec_it->second, snapshot_name, request->comment())->get_name()); server->Write(reply); } From a54f2bdfe0d8402aab21483b1384d8f0cd2eaee9 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 23:27:22 +0100 Subject: [PATCH 585/627] [rpc] Change the name of instance-snapshot pairs In info and delete requests. --- src/client/cli/cmd/alias.cpp | 2 +- src/client/cli/cmd/delete.cpp | 2 +- src/client/cli/cmd/exec.cpp | 2 +- src/client/cli/cmd/info.cpp | 2 +- src/daemon/daemon.cpp | 8 ++++---- src/rpc/multipass.proto | 4 ++-- tests/test_cli_client.cpp | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/client/cli/cmd/alias.cpp b/src/client/cli/cmd/alias.cpp index a231f30d2c5..814a6043c94 100644 --- a/src/client/cli/cmd/alias.cpp +++ b/src/client/cli/cmd/alias.cpp @@ -116,7 +116,7 @@ mp::ParseCode cmd::Alias::parse_args(mp::ArgParser* parser) auto instance = definition.left(colon_pos).toStdString(); auto working_directory = parser->isSet(no_alias_dir_mapping_option) ? "default" : "map"; - info_request.add_instances_snapshots()->set_instance_name(instance); + info_request.add_instance_snapshot_pairs()->set_instance_name(instance); info_request.set_verbosity_level(0); info_request.set_no_runtime_information(true); diff --git a/src/client/cli/cmd/delete.cpp b/src/client/cli/cmd/delete.cpp index 19b09644a18..1077f995295 100644 --- a/src/client/cli/cmd/delete.cpp +++ b/src/client/cli/cmd/delete.cpp @@ -135,7 +135,7 @@ mp::ParseCode cmd::Delete::parse_instances_snapshots(mp::ArgParser* parser) snapshot_found = true; } - request.add_instances_snapshots()->CopyFrom(item); + request.add_instance_snapshot_pairs()->CopyFrom(item); } return enforce_purged_snapshots(instances, snapshots, instance_found, snapshot_found); diff --git a/src/client/cli/cmd/exec.cpp b/src/client/cli/cmd/exec.cpp index e26036a93db..497c26126eb 100644 --- a/src/client/cli/cmd/exec.cpp +++ b/src/client/cli/cmd/exec.cpp @@ -102,7 +102,7 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser) info_request.set_verbosity_level(parser->verbosityLevel()); - info_request.add_instances_snapshots()->set_instance_name(instance_name); + info_request.add_instance_snapshot_pairs()->set_instance_name(instance_name); info_request.set_no_runtime_information(true); dispatch(&RpcMethod::info, info_request, on_info_success, on_info_failure); diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index 22ab54133ba..d3c4e52ba65 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -102,7 +102,7 @@ mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) else snapshot_found = true; - request.add_instances_snapshots()->CopyFrom(item); + request.add_instance_snapshot_pairs()->CopyFrom(item); } request.set_no_runtime_information(parser->isSet(noRuntimeInfoOption)); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 11e59953a04..abc6242c188 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -1776,13 +1776,13 @@ try // clang-format on auto [instance_selection, status] = select_instances_and_react(operative_instances, deleted_instances, - request->instances_snapshots(), + request->instance_snapshot_pairs(), InstanceGroup::All, require_existing_instances_reaction); if (status.ok()) { - instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); + instance_snapshots_map = map_snapshots_to_instances(request->instance_snapshot_pairs()); if ((status = cmd_vms(instance_selection.operative_selection, fetch_detailed_report)).ok()) { @@ -2272,7 +2272,7 @@ try // clang-format on auto [instance_selection, status] = select_instances_and_react(operative_instances, deleted_instances, - request->instances_snapshots(), + request->instance_snapshot_pairs(), InstanceGroup::All, require_existing_instances_reaction); @@ -2281,7 +2281,7 @@ try // clang-format on const bool purge = request->purge(); auto instances_dirty = false; - auto instance_snapshots_map = map_snapshots_to_instances(request->instances_snapshots()); + auto instance_snapshots_map = map_snapshots_to_instances(request->instance_snapshot_pairs()); verify_snapshot_picks(instance_selection, instance_snapshots_map); // avoid deleting if any snapshot is missing // start with deleted instances, to avoid iterator invalidation when moving instances there diff --git a/src/rpc/multipass.proto b/src/rpc/multipass.proto index 7d5f254ddfe..06ca35e8a13 100644 --- a/src/rpc/multipass.proto +++ b/src/rpc/multipass.proto @@ -166,7 +166,7 @@ message InstanceSnapshotPair { } message InfoRequest { - repeated InstanceSnapshotPair instances_snapshots = 1; + repeated InstanceSnapshotPair instance_snapshot_pairs = 1; int32 verbosity_level = 3; bool no_runtime_information = 4; bool snapshots = 5; @@ -425,7 +425,7 @@ message RestartReply { } message DeleteRequest { - repeated InstanceSnapshotPair instances_snapshots = 1; + repeated InstanceSnapshotPair instance_snapshot_pairs = 1; bool purge = 2; int32 verbosity_level = 3; } diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 605973a4761..36a70dd7eee 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -409,7 +409,7 @@ auto make_info_function(const std::string& source_path = "", const std::string& mp::InfoReply info_reply; - if (request.instances_snapshots(0).instance_name() == "primary") + if (request.instance_snapshot_pairs(0).instance_name() == "primary") { auto vm_info = info_reply.add_details(); vm_info->set_name("primary"); From 5db7f4455a4447467c3cea948eb8c25fd9bd445e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 16:01:04 +0100 Subject: [PATCH 586/627] [todos] Remove or update outdated snapshots TODOs --- include/multipass/snapshot.h | 2 +- src/client/cli/formatter/table_formatter.cpp | 3 ++- src/platform/backends/libvirt/libvirt_virtual_machine.cpp | 4 ++-- src/platform/backends/lxd/lxd_virtual_machine.cpp | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index b0abb949c67..10baafe4c20 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -35,7 +35,7 @@ struct VMMount; class Snapshot : private DisabledCopyMove { -public: // TODO@snapshots drop any accessors that we end up not needing +public: virtual ~Snapshot() = default; virtual int get_index() const = 0; diff --git a/src/client/cli/formatter/table_formatter.cpp b/src/client/cli/formatter/table_formatter.cpp index 9756d9f5f38..06656fcbf71 100644 --- a/src/client/cli/formatter/table_formatter.cpp +++ b/src/client/cli/formatter/table_formatter.cpp @@ -103,7 +103,8 @@ std::string generate_snapshot_details(const mp::DetailedInfoItem& item) fmt::format_to(std::back_inserter(buf), "{}\n", *child); } - // TODO@snapshots split and align string if it extends onto several lines + /* TODO split and align string if it extends onto several lines; but actually better implement generic word-wrapping + for all output, taking both terminal width and current indentation level into account */ fmt::format_to(std::back_inserter(buf), "{:<16}{}\n", "Comment:", diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 6a2f00ee91d..5a6ef0f2c2f 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -562,10 +562,10 @@ auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& /*snap std::shared_ptr /*parent*/) -> std::shared_ptr { - throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots + throw NotImplementedOnThisBackendException{"Snapshots"}; } auto mp::LibVirtVirtualMachine::make_specific_snapshot(const QString& /*filename*/) -> std::shared_ptr { - throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots + throw NotImplementedOnThisBackendException{"Snapshots"}; } diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index c12e4c5432b..4d972434d46 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -492,10 +492,10 @@ auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_n const VMSpecs& specs, std::shared_ptr parent) -> std::shared_ptr { - throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots + throw NotImplementedOnThisBackendException{"Snapshots"}; } std::shared_ptr mp::LXDVirtualMachine::make_specific_snapshot(const QString& /*filename*/) { - throw NotImplementedOnThisBackendException{"Snapshots"}; // TODO@snapshots + throw NotImplementedOnThisBackendException{"Snapshots"}; } From d437ceef8be105431301f5379b4d7bb4232ab819 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Tue, 24 Oct 2023 23:38:12 +0100 Subject: [PATCH 587/627] [tests] Refactor repeated mocking for daemon RPC --- tests/unix/test_daemon_rpc.cpp | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/unix/test_daemon_rpc.cpp b/tests/unix/test_daemon_rpc.cpp index 013868efab7..b09201dfe89 100644 --- a/tests/unix/test_daemon_rpc.cpp +++ b/tests/unix/test_daemon_rpc.cpp @@ -79,6 +79,16 @@ struct TestDaemonRpc : public mpt::DaemonTestFixture return mpt::MockDaemon(config_builder.build()); } + void mock_empty_list_reply(mpt::MockDaemon& mock_daemon) + { + EXPECT_CALL(mock_daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { + mp::ListReply reply; + reply.mutable_instance_list(); + server->Write(reply); + status_promise->set_value(grpc::Status::OK); + }); + } + std::unique_ptr mock_cert_provider{std::make_unique()}; std::unique_ptr mock_cert_store{std::make_unique()}; @@ -202,12 +212,7 @@ TEST_F(TestDaemonRpc, listCertExistsCompletesSuccesfully) EXPECT_CALL(*mock_cert_store, verify_cert(StrEq(mpt::client_cert))).WillOnce(Return(true)); mpt::MockDaemon daemon{make_secure_server()}; - EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { - mp::ListReply reply; - reply.mutable_instance_list(); - server->Write(reply); - status_promise->set_value(grpc::Status::OK); - }); + mock_empty_list_reply(daemon); send_command({"list"}); } @@ -221,12 +226,7 @@ TEST_F(TestDaemonRpc, listNoCertsExistWillVerifyAndComplete) EXPECT_CALL(*mock_cert_store, add_cert(StrEq(mpt::client_cert))).Times(1); mpt::MockDaemon daemon{make_secure_server()}; - EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { - mp::ListReply reply; - reply.mutable_instance_list(); - server->Write(reply); - status_promise->set_value(grpc::Status::OK); - }); + mock_empty_list_reply(daemon); send_command({"list"}); } @@ -308,12 +308,7 @@ TEST_F(TestDaemonRpc, listSettingServerPermissionsFailLogsErrorAndExits) logger_scope.mock_logger->expect_log(mpl::Level::error, error_msg); logger_scope.mock_logger->expect_log(mpl::Level::error, "Failed to set up autostart prerequisites", AnyNumber()); - EXPECT_CALL(daemon, list(_, _, _)).WillOnce([](auto, auto* server, auto* status_promise) { - mp::ListReply reply; - reply.mutable_instance_list(); - server->Write(reply); - status_promise->set_value(grpc::Status::OK); - }); + mock_empty_list_reply(daemon); send_command({"list"}); } From 7a03f1d7949d5dc43d8887f0d95aea56ac280f14 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 12:52:22 +0100 Subject: [PATCH 588/627] [snapshot] Mark a couple of accessors as noexcept --- include/multipass/snapshot.h | 4 ++-- src/platform/backends/shared/base_snapshot.h | 9 ++++----- tests/stub_snapshot.h | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/multipass/snapshot.h b/include/multipass/snapshot.h index 10baafe4c20..dc0f10eee0d 100644 --- a/include/multipass/snapshot.h +++ b/include/multipass/snapshot.h @@ -38,10 +38,10 @@ class Snapshot : private DisabledCopyMove public: virtual ~Snapshot() = default; - virtual int get_index() const = 0; + virtual int get_index() const noexcept = 0; virtual std::string get_name() const = 0; virtual std::string get_comment() const = 0; - virtual QDateTime get_creation_timestamp() const = 0; + virtual QDateTime get_creation_timestamp() const noexcept = 0; virtual int get_num_cores() const noexcept = 0; virtual MemorySize get_mem_size() const noexcept = 0; virtual MemorySize get_disk_space() const noexcept = 0; diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index f67c9250682..a07afcb0c9c 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -42,11 +42,10 @@ class BaseSnapshot : public Snapshot const VirtualMachine& vm); BaseSnapshot(const QString& filename, VirtualMachine& vm); - // TODO@snapshots tag as noexcept those that can be - int get_index() const override; + int get_index() const noexcept override; std::string get_name() const override; std::string get_comment() const override; - QDateTime get_creation_timestamp() const override; + QDateTime get_creation_timestamp() const noexcept override; int get_num_cores() const noexcept override; MemorySize get_mem_size() const noexcept override; MemorySize get_disk_space() const noexcept override; @@ -130,12 +129,12 @@ inline std::string multipass::BaseSnapshot::get_comment() const return comment; } -inline int multipass::BaseSnapshot::get_index() const +inline int multipass::BaseSnapshot::get_index() const noexcept { return index; } -inline QDateTime multipass::BaseSnapshot::get_creation_timestamp() const +inline QDateTime multipass::BaseSnapshot::get_creation_timestamp() const noexcept { return creation_timestamp; } diff --git a/tests/stub_snapshot.h b/tests/stub_snapshot.h index ced617d8cff..f384903ffd3 100644 --- a/tests/stub_snapshot.h +++ b/tests/stub_snapshot.h @@ -57,7 +57,7 @@ struct StubSnapshot : public Snapshot return nullptr; } - int get_index() const override + int get_index() const noexcept override { return 0; } From 3bb53f1eeb7c9e5fcf082edc09892dc3d782632f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 12:55:43 +0100 Subject: [PATCH 589/627] [vm] Remove noexcept from `view_snapshots` --- include/multipass/virtual_machine.h | 2 +- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- src/platform/backends/shared/base_virtual_machine.h | 2 +- tests/stub_virtual_machine.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 7d2cffb3bf1..52b6bfcdbca 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -88,7 +88,7 @@ class VirtualMachine : private DisabledCopyMove const VMMount& mount) = 0; using SnapshotVista = std::vector>; // using vista to avoid confusion with C++ views - virtual SnapshotVista view_snapshots() const noexcept = 0; + virtual SnapshotVista view_snapshots() const = 0; virtual int get_num_snapshots() const noexcept = 0; virtual std::shared_ptr get_snapshot(const std::string& name) const = 0; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 6717ef045e4..f9b97e54ad4 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -121,7 +121,7 @@ std::vector BaseVirtualMachine::get_all_ipv4(const SSHKeyProvider& return all_ipv4; } -auto BaseVirtualMachine::view_snapshots() const noexcept -> SnapshotVista +auto BaseVirtualMachine::view_snapshots() const -> SnapshotVista { SnapshotVista ret; diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 20c6dc8c166..8ab079cda86 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -50,7 +50,7 @@ class BaseVirtualMachine : public VirtualMachine throw NotImplementedOnThisBackendException("native mounts"); }; - SnapshotVista view_snapshots() const noexcept override; + SnapshotVista view_snapshots() const override; int get_num_snapshots() const noexcept override; std::shared_ptr get_snapshot(const std::string& name) const override; diff --git a/tests/stub_virtual_machine.h b/tests/stub_virtual_machine.h index 67dd1adb0ea..3009b79d1a1 100644 --- a/tests/stub_virtual_machine.h +++ b/tests/stub_virtual_machine.h @@ -126,7 +126,7 @@ struct StubVirtualMachine final : public multipass::VirtualMachine return std::make_unique(); } - SnapshotVista view_snapshots() const noexcept override + SnapshotVista view_snapshots() const override { return {}; } From f005906193c1b8431e5462b9b345694084e54995 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 12:58:02 +0100 Subject: [PATCH 590/627] [settings] Add noexcept to handler's constructor --- src/daemon/snapshot_settings_handler.cpp | 2 +- src/daemon/snapshot_settings_handler.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/daemon/snapshot_settings_handler.cpp b/src/daemon/snapshot_settings_handler.cpp index 3451a37fc66..0af41669a76 100644 --- a/src/daemon/snapshot_settings_handler.cpp +++ b/src/daemon/snapshot_settings_handler.cpp @@ -80,7 +80,7 @@ mp::SnapshotSettingsException::SnapshotSettingsException(const std::string& deta mp::SnapshotSettingsHandler::SnapshotSettingsHandler( std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, - const std::unordered_set& preparing_instances) + const std::unordered_set& preparing_instances) noexcept : operative_instances{operative_instances}, deleted_instances{deleted_instances}, preparing_instances{preparing_instances} diff --git a/src/daemon/snapshot_settings_handler.h b/src/daemon/snapshot_settings_handler.h index 5eecad76633..c613bbc6cad 100644 --- a/src/daemon/snapshot_settings_handler.h +++ b/src/daemon/snapshot_settings_handler.h @@ -35,7 +35,7 @@ class SnapshotSettingsHandler : public SettingsHandler public: SnapshotSettingsHandler(std::unordered_map& operative_instances, const std::unordered_map& deleted_instances, - const std::unordered_set& preparing_instances); + const std::unordered_set& preparing_instances) noexcept; std::set keys() const override; QString get(const QString& key) const override; From 8d9b599558bf66b383e7e3336508bd05c373ea14 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 13:02:31 +0100 Subject: [PATCH 591/627] [mounts] Add noexcept to new MountHandler accessor --- include/multipass/mount_handler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/mount_handler.h b/include/multipass/mount_handler.h index 56167d6a499..11e387dbb8e 100644 --- a/include/multipass/mount_handler.h +++ b/include/multipass/mount_handler.h @@ -67,7 +67,7 @@ class MountHandler : private DisabledCopyMove active = false; } - const VMMount& get_mount_spec() const + const VMMount& get_mount_spec() const noexcept { return mount_spec; } From 50194c6577f61c719c39b300ea7ceb4ec8549eee Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 17:08:51 +0100 Subject: [PATCH 592/627] [vmspecs] Move header to generic include folder --- {src/daemon => include/multipass}/vm_specs.h | 31 ++++++++++++++----- src/daemon/daemon.h | 3 +- src/daemon/instance_settings_handler.h | 2 +- .../backends/shared/base_snapshot.cpp | 2 +- .../backends/shared/base_virtual_machine.cpp | 2 +- tests/json_test_utils.h | 3 +- tests/test_instance_settings_handler.cpp | 2 +- 7 files changed, 29 insertions(+), 16 deletions(-) rename {src/daemon => include/multipass}/vm_specs.h (59%) diff --git a/src/daemon/vm_specs.h b/include/multipass/vm_specs.h similarity index 59% rename from src/daemon/vm_specs.h rename to include/multipass/vm_specs.h index af638990a36..42af152bed6 100644 --- a/src/daemon/vm_specs.h +++ b/include/multipass/vm_specs.h @@ -18,10 +18,10 @@ #ifndef MULTIPASS_VM_SPECS_H #define MULTIPASS_VM_SPECS_H -#include -#include -#include -#include +#include "memory_size.h" +#include "network_interface.h" +#include "virtual_machine.h" +#include "vm_mount.h" #include #include @@ -48,10 +48,25 @@ struct VMSpecs 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) == - 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); + 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) == 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); } inline bool operator!=(const VMSpecs& a, const VMSpecs& b) // TODO drop in C++20 diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 4e915a93df9..790b1c87fae 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -20,13 +20,12 @@ #include "daemon_config.h" #include "daemon_rpc.h" -#include "multipass/virtual_machine.h" -#include "vm_specs.h" #include #include #include #include +#include #include #include diff --git a/src/daemon/instance_settings_handler.h b/src/daemon/instance_settings_handler.h index 9a17253e1c5..9b8692e9b71 100644 --- a/src/daemon/instance_settings_handler.h +++ b/src/daemon/instance_settings_handler.h @@ -18,11 +18,11 @@ #ifndef MULTIPASS_INSTANCE_SETTINGS_HANDLER_H #define MULTIPASS_INSTANCE_SETTINGS_HANDLER_H -#include "vm_specs.h" #include #include #include +#include #include diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index f898b27920e..12a0d5af80b 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -16,12 +16,12 @@ */ #include "base_snapshot.h" -#include "daemon/vm_specs.h" // TODO@snapshots move this #include #include // TODO@snapshots may be able to drop after extracting JSON utilities #include #include +#include #include diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index f9b97e54ad4..39140dad794 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -16,7 +16,6 @@ */ #include "base_virtual_machine.h" -#include "daemon/vm_specs.h" // TODO@snapshots move this #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include diff --git a/tests/json_test_utils.h b/tests/json_test_utils.h index 1c8df6cf4e5..7bd74e73efa 100644 --- a/tests/json_test_utils.h +++ b/tests/json_test_utils.h @@ -21,8 +21,7 @@ #include "temp_dir.h" #include - -#include +#include #include #include diff --git a/tests/test_instance_settings_handler.cpp b/tests/test_instance_settings_handler.cpp index d5954bbe397..12c9eeeeafe 100644 --- a/tests/test_instance_settings_handler.cpp +++ b/tests/test_instance_settings_handler.cpp @@ -20,9 +20,9 @@ #include #include +#include #include -#include #include From 476a1270c0e7ad63bd11745e3c5693367f62118a Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 17:32:03 +0100 Subject: [PATCH 593/627] [snapshot] Throw on unsupported state --- src/platform/backends/shared/base_snapshot.cpp | 5 ++++- src/platform/backends/shared/base_virtual_machine.cpp | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 12a0d5af80b..ae36135f537 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -16,6 +16,7 @@ */ #include "base_snapshot.h" +#include "multipass/virtual_machine.h" #include #include // TODO@snapshots may be able to drop after extracting JSON utilities @@ -146,9 +147,11 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p captured{captured} { assert(index > 0 && "snapshot indices need to start at 1"); + using St = VirtualMachine::State; + if (state != St::off && state != St::stopped) + throw std::runtime_error{fmt::format("Unsupported VM state in snapshot: {}", static_cast(state))}; if (index > max_snapshots) throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", max_snapshots)}; - if (name.empty()) throw std::runtime_error{"Snapshot names cannot be empty"}; if (num_cores < 1) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 39140dad794..79edcbf7fd3 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -522,8 +522,6 @@ void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& spec assert_vm_stopped(state); // precondition auto snapshot = get_snapshot(name); - - // TODO@snapshots convert into runtime_error (persisted info could have been tampered with) assert(snapshot->get_state() == St::off || snapshot->get_state() == St::stopped); snapshot->apply(); From b96bcd99e2f8a686d916039c5dd5a713ddd3cf2c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 17:34:34 +0100 Subject: [PATCH 594/627] [snapshot] Throw on non-positive snapshot index --- src/platform/backends/shared/base_snapshot.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index ae36135f537..2f0a3d5d2f3 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -150,6 +150,8 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p using St = VirtualMachine::State; if (state != St::off && state != St::stopped) throw std::runtime_error{fmt::format("Unsupported VM state in snapshot: {}", static_cast(state))}; + if (index < 1) + throw std::runtime_error{fmt::format("Snapshot index not positive: {}", index)}; if (index > max_snapshots) throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", max_snapshots)}; if (name.empty()) From 3f94d6fb85a3a29c5e43e480d77ff21a38d660cb Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 17:35:48 +0100 Subject: [PATCH 595/627] [snapshot] Fix index overflow exception contents --- src/platform/backends/shared/base_snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 2f0a3d5d2f3..51d8bf734ef 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -153,7 +153,7 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p if (index < 1) throw std::runtime_error{fmt::format("Snapshot index not positive: {}", index)}; if (index > max_snapshots) - throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", max_snapshots)}; + throw std::runtime_error{fmt::format("Maximum number of snapshots exceeded: {}", index)}; if (name.empty()) throw std::runtime_error{"Snapshot names cannot be empty"}; if (num_cores < 1) From 781bf474196d477618b2dd26111b32e4a4bc8307 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 18:28:12 +0100 Subject: [PATCH 596/627] [vm] Replace TODO with justification Remove a TODO to remove a method that turns out to be justified. This was requested in review but, after discussion, we agreed to drop it. --- include/multipass/virtual_machine.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/multipass/virtual_machine.h b/include/multipass/virtual_machine.h index 52b6bfcdbca..baf3dec5027 100644 --- a/include/multipass/virtual_machine.h +++ b/include/multipass/virtual_machine.h @@ -99,7 +99,8 @@ class VirtualMachine : private DisabledCopyMove virtual std::shared_ptr take_snapshot(const VMSpecs& specs, const std::string& snapshot_name, const std::string& comment) = 0; - virtual void rename_snapshot(const std::string& old_name, const std::string& new_name) = 0; // TODO@snapshots remove + virtual void rename_snapshot(const std::string& old_name, + const std::string& new_name) = 0; // only VM can avoid repeated names virtual void delete_snapshot(const std::string& name) = 0; virtual void restore_snapshot(const std::string& name, VMSpecs& specs) = 0; virtual void load_snapshots() = 0; From 2cd456fe745fc6d6b2c6a7d69ed82d94a82070f3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 18:57:08 +0100 Subject: [PATCH 597/627] [mount] Add a couple of constructors to VMMount In preparation for a constructor to read JSON. --- include/multipass/vm_mount.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/multipass/vm_mount.h b/include/multipass/vm_mount.h index 3f1e911b24f..7a442c992c5 100644 --- a/include/multipass/vm_mount.h +++ b/include/multipass/vm_mount.h @@ -32,6 +32,15 @@ struct VMMount Native = 1 }; + VMMount() = default; + VMMount(const std::string& sourcePath, id_mappings gidMappings, id_mappings uidMappings, MountType mountType) + : source_path(sourcePath), + gid_mappings(std::move(gidMappings)), + uid_mappings(std::move(uidMappings)), + mount_type(mountType) + { + } + std::string source_path; id_mappings gid_mappings; id_mappings uid_mappings; From 594d708012bd726b7419ec937fccd7031e95d489 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:02:54 +0100 Subject: [PATCH 598/627] [mount] Move ctor implementation to cpp --- include/multipass/vm_mount.h | 8 +------- src/utils/CMakeLists.txt | 3 ++- src/utils/vm_mount.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 src/utils/vm_mount.cpp diff --git a/include/multipass/vm_mount.h b/include/multipass/vm_mount.h index 7a442c992c5..0114ed5a1b3 100644 --- a/include/multipass/vm_mount.h +++ b/include/multipass/vm_mount.h @@ -33,13 +33,7 @@ struct VMMount }; VMMount() = default; - VMMount(const std::string& sourcePath, id_mappings gidMappings, id_mappings uidMappings, MountType mountType) - : source_path(sourcePath), - gid_mappings(std::move(gidMappings)), - uid_mappings(std::move(uidMappings)), - mount_type(mountType) - { - } + VMMount(const std::string& sourcePath, id_mappings gidMappings, id_mappings uidMappings, MountType mountType); std::string source_path; id_mappings gid_mappings; diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index ced4a8824b3..cd5d0e4d2a2 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -21,7 +21,8 @@ function(add_target TARGET_NAME) standard_paths.cpp timer.cpp utils.cpp - vm_image_vault_utils.cpp) + vm_image_vault_utils.cpp + vm_mount.cpp) target_link_libraries(${TARGET_NAME} cert diff --git a/src/utils/vm_mount.cpp b/src/utils/vm_mount.cpp new file mode 100644 index 00000000000..d605485ab74 --- /dev/null +++ b/src/utils/vm_mount.cpp @@ -0,0 +1,31 @@ +/* + * 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 + +namespace mp = multipass; + +mp::VMMount::VMMount(const std::string& sourcePath, + id_mappings gidMappings, + id_mappings uidMappings, + MountType mountType) + : source_path(sourcePath), + gid_mappings(std::move(gidMappings)), + uid_mappings(std::move(uidMappings)), + mount_type(mountType) +{ +} From cdde27e893c49ec1a57dcb242159e3d7c0f7eb5d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:17:47 +0100 Subject: [PATCH 599/627] [mount] Construct mounts from json --- include/multipass/vm_mount.h | 3 +++ src/utils/vm_mount.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/multipass/vm_mount.h b/include/multipass/vm_mount.h index 0114ed5a1b3..58020f2d12f 100644 --- a/include/multipass/vm_mount.h +++ b/include/multipass/vm_mount.h @@ -20,6 +20,8 @@ #include +#include + #include namespace multipass @@ -33,6 +35,7 @@ struct VMMount }; VMMount() = default; + VMMount(const QJsonObject& json); VMMount(const std::string& sourcePath, id_mappings gidMappings, id_mappings uidMappings, MountType mountType); std::string source_path; diff --git a/src/utils/vm_mount.cpp b/src/utils/vm_mount.cpp index d605485ab74..d93b28d03f7 100644 --- a/src/utils/vm_mount.cpp +++ b/src/utils/vm_mount.cpp @@ -17,8 +17,38 @@ #include +#include + namespace mp = multipass; +namespace +{ +mp::VMMount parse_json(const QJsonObject& json) +{ + mp::id_mappings uid_mappings; + mp::id_mappings gid_mappings; + auto source_path = json["source_path"].toString().toStdString(); + + for (const QJsonValueRef uid_entry : json["uid_mappings"].toArray()) + { + uid_mappings.push_back( + {uid_entry.toObject()["host_uid"].toInt(), uid_entry.toObject()["instance_uid"].toInt()}); + } + + for (const QJsonValueRef gid_entry : json["gid_mappings"].toArray()) + { + gid_mappings.push_back( + {gid_entry.toObject()["host_gid"].toInt(), gid_entry.toObject()["instance_gid"].toInt()}); + } + + uid_mappings = mp::unique_id_mappings(uid_mappings); + gid_mappings = mp::unique_id_mappings(gid_mappings); + auto mount_type = mp::VMMount::MountType(json["mount_type"].toInt()); + + return mp::VMMount{std::move(source_path), std::move(gid_mappings), std::move(uid_mappings), mount_type}; +} +} // namespace + mp::VMMount::VMMount(const std::string& sourcePath, id_mappings gidMappings, id_mappings uidMappings, @@ -29,3 +59,7 @@ mp::VMMount::VMMount(const std::string& sourcePath, mount_type(mountType) { } + +mp::VMMount::VMMount(const QJsonObject& json) : VMMount{parse_json(json)} // delegate on copy ctor +{ +} From 815261860c875616c3b907cde3c081d7eac44386 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:30:06 +0100 Subject: [PATCH 600/627] [daemon] Use new VMMount constructor from JSON --- src/daemon/daemon.cpp | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index abc6242c188..49c7d55e2f5 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -325,30 +325,8 @@ std::unordered_map load_db(const mp::Path& data_path, for (QJsonValueRef entry : record["mounts"].toArray()) { - mp::id_mappings uid_mappings; - mp::id_mappings gid_mappings; - - auto target_path = entry.toObject()["target_path"].toString().toStdString(); - auto source_path = entry.toObject()["source_path"].toString().toStdString(); - - for (QJsonValueRef uid_entry : entry.toObject()["uid_mappings"].toArray()) - { - uid_mappings.push_back( - {uid_entry.toObject()["host_uid"].toInt(), uid_entry.toObject()["instance_uid"].toInt()}); - } - - for (QJsonValueRef gid_entry : entry.toObject()["gid_mappings"].toArray()) - { - gid_mappings.push_back( - {gid_entry.toObject()["host_gid"].toInt(), gid_entry.toObject()["instance_gid"].toInt()}); - } - - uid_mappings = mp::unique_id_mappings(uid_mappings); - gid_mappings = mp::unique_id_mappings(gid_mappings); - auto mount_type = mp::VMMount::MountType(entry.toObject()["mount_type"].toInt()); - - mp::VMMount mount{source_path, gid_mappings, uid_mappings, mount_type}; - mounts[target_path] = mount; + const auto& json = entry.toObject(); + mounts[json["target_path"].toString().toStdString()] = mp::VMMount{json}; } reconstructed_records[key] = {num_cores, From b36a26e096ad3c926dcf0053a6f83e7f30f31145 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:34:34 +0100 Subject: [PATCH 601/627] [snapshot] Use new VMMount constructor from JSON --- .../backends/shared/base_snapshot.cpp | 31 +++---------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 51d8bf734ef..8335f253baf 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -19,7 +19,6 @@ #include "multipass/virtual_machine.h" #include -#include // TODO@snapshots may be able to drop after extracting JSON utilities #include #include #include @@ -69,35 +68,13 @@ QJsonObject read_snapshot_json(const QString& filename) return json["snapshot"].toObject(); } -std::unordered_map load_mounts(const QJsonArray& json) +std::unordered_map load_mounts(const QJsonArray& mounts_json) { std::unordered_map mounts; - for (const auto& entry : json) + for (const auto& entry : mounts_json) { - mp::id_mappings uid_mappings; - mp::id_mappings gid_mappings; - - auto target_path = entry.toObject()["target_path"].toString().toStdString(); - auto source_path = entry.toObject()["source_path"].toString().toStdString(); - - for (const QJsonValueRef uid_entry : entry.toObject()["uid_mappings"].toArray()) - { - uid_mappings.push_back( - {uid_entry.toObject()["host_uid"].toInt(), uid_entry.toObject()["instance_uid"].toInt()}); - } - - for (const QJsonValueRef gid_entry : entry.toObject()["gid_mappings"].toArray()) - { - gid_mappings.push_back( - {gid_entry.toObject()["host_gid"].toInt(), gid_entry.toObject()["instance_gid"].toInt()}); - } - - uid_mappings = mp::unique_id_mappings(uid_mappings); - gid_mappings = mp::unique_id_mappings(gid_mappings); - auto mount_type = mp::VMMount::MountType(entry.toObject()["mount_type"].toInt()); - - mp::VMMount mount{source_path, gid_mappings, uid_mappings, mount_type}; - mounts[target_path] = std::move(mount); + const auto& json = entry.toObject(); + mounts[json["target_path"].toString().toStdString()] = mp::VMMount{json}; } return mounts; From 0a288ecd9aa8185cd878ed6b2058984ae4ac722f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:51:05 +0100 Subject: [PATCH 602/627] [mount] Serialize mounts to JSON --- include/multipass/vm_mount.h | 2 ++ src/utils/vm_mount.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/include/multipass/vm_mount.h b/include/multipass/vm_mount.h index 58020f2d12f..b3f6b37eff4 100644 --- a/include/multipass/vm_mount.h +++ b/include/multipass/vm_mount.h @@ -38,6 +38,8 @@ struct VMMount VMMount(const QJsonObject& json); VMMount(const std::string& sourcePath, id_mappings gidMappings, id_mappings uidMappings, MountType mountType); + QJsonObject serialize() const; + std::string source_path; id_mappings gid_mappings; id_mappings uid_mappings; diff --git a/src/utils/vm_mount.cpp b/src/utils/vm_mount.cpp index d93b28d03f7..b798eed92b1 100644 --- a/src/utils/vm_mount.cpp +++ b/src/utils/vm_mount.cpp @@ -63,3 +63,38 @@ mp::VMMount::VMMount(const std::string& sourcePath, mp::VMMount::VMMount(const QJsonObject& json) : VMMount{parse_json(json)} // delegate on copy ctor { } + +QJsonObject mp::VMMount::serialize() const +{ + QJsonObject ret; + ret.insert("source_path", QString::fromStdString(source_path)); + + QJsonArray uid_mappings_json; + + for (const auto& map : uid_mappings) + { + QJsonObject map_entry; + map_entry.insert("host_uid", map.first); + map_entry.insert("instance_uid", map.second); + + uid_mappings_json.append(map_entry); + } + + ret.insert("uid_mappings", uid_mappings_json); + + QJsonArray gid_mappings_json; + + for (const auto& map : gid_mappings) + { + QJsonObject map_entry; + map_entry.insert("host_gid", map.first); + map_entry.insert("instance_gid", map.second); + + gid_mappings_json.append(map_entry); + } + + ret.insert("gid_mappings", gid_mappings_json); + + ret.insert("mount_type", static_cast(mount_type)); + return ret; +} From 172e189f621f3acfd64435ca100af3cf4875a92d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:55:05 +0100 Subject: [PATCH 603/627] [daemon] Use new VMMount serialize method --- src/daemon/daemon.cpp | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 49c7d55e2f5..a3c1be70626 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -378,37 +378,8 @@ QJsonObject vm_spec_to_json(const mp::VMSpecs& specs) QJsonArray json_mounts; for (const auto& mount : specs.mounts) { - QJsonObject entry; - entry.insert("source_path", QString::fromStdString(mount.second.source_path)); + auto entry = mount.second.serialize(); entry.insert("target_path", QString::fromStdString(mount.first)); - - QJsonArray uid_mappings; - - for (const auto& map : mount.second.uid_mappings) - { - QJsonObject map_entry; - map_entry.insert("host_uid", map.first); - map_entry.insert("instance_uid", map.second); - - uid_mappings.append(map_entry); - } - - entry.insert("uid_mappings", uid_mappings); - - QJsonArray gid_mappings; - - for (const auto& map : mount.second.gid_mappings) - { - QJsonObject map_entry; - map_entry.insert("host_gid", map.first); - map_entry.insert("instance_gid", map.second); - - gid_mappings.append(map_entry); - } - - entry.insert("gid_mappings", gid_mappings); - - entry.insert("mount_type", static_cast(mount.second.mount_type)); json_mounts.append(entry); } From 4ea69ff0dd0cbae7c18c122008afcdf2ab33cfcd Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:59:04 +0100 Subject: [PATCH 604/627] [snapshot] Use new VMMount serialize method --- .../backends/shared/base_snapshot.cpp | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 8335f253baf..0c86e48ac04 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -206,37 +206,8 @@ QJsonObject mp::BaseSnapshot::serialize() const QJsonArray json_mounts; for (const auto& mount : mounts) { - QJsonObject entry; - entry.insert("source_path", QString::fromStdString(mount.second.source_path)); + auto entry = mount.second.serialize(); entry.insert("target_path", QString::fromStdString(mount.first)); - - QJsonArray uid_mappings; - - for (const auto& map : mount.second.uid_mappings) - { - QJsonObject map_entry; - map_entry.insert("host_uid", map.first); - map_entry.insert("instance_uid", map.second); - - uid_mappings.append(map_entry); - } - - entry.insert("uid_mappings", uid_mappings); - - QJsonArray gid_mappings; - - for (const auto& map : mount.second.gid_mappings) - { - QJsonObject map_entry; - map_entry.insert("host_gid", map.first); - map_entry.insert("instance_gid", map.second); - - gid_mappings.append(map_entry); - } - - entry.insert("gid_mappings", gid_mappings); - - entry.insert("mount_type", static_cast(mount.second.mount_type)); json_mounts.append(entry); } From 001d626c42312584567db5bc4d42e4666c0e20f3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 20:01:22 +0100 Subject: [PATCH 605/627] [snapshot] Remove incorrect TODO --- src/platform/backends/shared/base_snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 0c86e48ac04..c5b963894c7 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -25,7 +25,7 @@ #include -#include // TODO@snapshots may be able to drop after extracting JSON utilities +#include #include #include From 41a1590ac7715f8d43d90c6b21ed40ab48b32a0c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 25 Oct 2023 11:58:08 +0100 Subject: [PATCH 606/627] [cosmetic] Fix extra newline --- src/daemon/instance_settings_handler.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/daemon/instance_settings_handler.h b/src/daemon/instance_settings_handler.h index 9b8692e9b71..d2236be533f 100644 --- a/src/daemon/instance_settings_handler.h +++ b/src/daemon/instance_settings_handler.h @@ -18,7 +18,6 @@ #ifndef MULTIPASS_INSTANCE_SETTINGS_HANDLER_H #define MULTIPASS_INSTANCE_SETTINGS_HANDLER_H - #include #include #include From 60435c96137d7a5daf1b738ffb9b2445750082f3 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 21 Sep 2023 13:15:53 +0100 Subject: [PATCH 607/627] [cli] DRY in description of `info` command --- src/client/cli/cmd/info.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index d3c4e52ba65..b30f0506cb4 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -53,7 +53,7 @@ QString cmd::Info::short_help() const QString cmd::Info::description() const { - return QStringLiteral("Display information about instances or snapshots"); + return short_help(); } mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) From b6e073296492af2da94a556a0fcae41cf6d53d2e Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 21 Sep 2023 13:18:48 +0100 Subject: [PATCH 608/627] [cli] Rename positional argument to `info` command To reflect that it can designate either an instance or a snapshot. --- src/client/cli/cmd/info.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/info.cpp b/src/client/cli/cmd/info.cpp index b30f0506cb4..5594d30fee0 100644 --- a/src/client/cli/cmd/info.cpp +++ b/src/client/cli/cmd/info.cpp @@ -58,7 +58,7 @@ QString cmd::Info::description() const mp::ParseCode cmd::Info::parse_args(mp::ArgParser* parser) { - parser->addPositionalArgument("instance", + parser->addPositionalArgument("instance/snapshot", "Names of instances or snapshots to display information about", "[.snapshot] [[.snapshot] ...]"); From 65965f2a399eb099d39c15491ebf041bed738701 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 20 Oct 2023 19:06:38 +0100 Subject: [PATCH 609/627] [utils] Fix include form --- src/utils/json_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index e2bbe2fa565..8e73568abb9 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -17,8 +17,8 @@ * */ -#include "multipass/format.h" #include +#include #include #include From b9488a121797e39b0568d4b84a429a311e5070e8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 23 Oct 2023 12:30:18 +0100 Subject: [PATCH 610/627] [snapshots] Fix attempts to move from const --- src/platform/backends/shared/base_snapshot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index c5b963894c7..82d17fd664f 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -238,8 +238,8 @@ auto mp::BaseSnapshot::erase_helper() throw std::runtime_error{"Could not create temporary directory"}; const auto snapshot_filename = derive_snapshot_filename(); - const auto snapshot_filepath = storage_dir.filePath(snapshot_filename); - const auto deleting_filepath = tmp_dir->filePath(snapshot_filename); + auto snapshot_filepath = storage_dir.filePath(snapshot_filename); + auto deleting_filepath = tmp_dir->filePath(snapshot_filename); QFile snapshot_file{snapshot_filepath}; if (!MP_FILEOPS.rename(snapshot_file, deleting_filepath)) From 4eeac93f57b54ebc14c79cae0a820ea40bd22ca6 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Wed, 25 Oct 2023 19:40:25 +0100 Subject: [PATCH 611/627] [cli] Use "hint" instead of "tip" in help text To better align with current plans for the GUI. --- src/client/cli/cmd/snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index c648993223f..7d5d1a1df15 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -76,7 +76,7 @@ mp::ParseCode cmd::Snapshot::parse_args(mp::ArgParser* parser) "number of snapshots that were ever taken for .", "name"); QCommandLineOption comment_opt{{"comment", "c", "m"}, - "An optional free comment to associate with the snapshot. (Tip: quote the text to " + "An optional free comment to associate with the snapshot. (Hint: quote the text to " "avoid spaces being parsed by your shell)", "comment"}; parser->addOptions({name_opt, comment_opt}); From 39a4e43dc956e644400fdda9e2b8632286a62f5c Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 23 Oct 2023 16:47:58 +0100 Subject: [PATCH 612/627] [qemu] Use uniform initialization in QemuSnapshot --- src/platform/backends/qemu/qemu_snapshot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index f12e363b846..85f86a0ea08 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -60,12 +60,12 @@ mp::QemuSnapshot::QemuSnapshot(const std::string& name, const VMSpecs& specs, QemuVirtualMachine& vm, VirtualMachineDescription& desc) - : BaseSnapshot(name, comment, std::move(parent), specs, vm), desc{desc}, image_path{desc.image.image_path} + : BaseSnapshot{name, comment, std::move(parent), specs, vm}, desc{desc}, image_path{desc.image.image_path} { } mp::QemuSnapshot::QemuSnapshot(const QString& filename, QemuVirtualMachine& vm, VirtualMachineDescription& desc) - : BaseSnapshot(filename, vm), desc{desc}, image_path{desc.image.image_path} + : BaseSnapshot{filename, vm}, desc{desc}, image_path{desc.image.image_path} { } From a13934a5c6bb85458b3a78b2f18ce42c9eced8f0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 26 Oct 2023 10:40:58 +0100 Subject: [PATCH 613/627] [cosmetic] Use conventional alias-namespace prefix --- src/platform/backends/shared/base_snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index c5b963894c7..98fa960f53d 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -254,7 +254,7 @@ auto mp::BaseSnapshot::erase_helper() }); } -void multipass::BaseSnapshot::erase() +void mp::BaseSnapshot::erase() { const std::unique_lock lock{mutex}; assert(captured && "precondition: only captured snapshots can be erased"); From 8b5ba52d66946da61c38fcf5cf94e6dddbf69906 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 26 Oct 2023 16:16:50 +0100 Subject: [PATCH 614/627] [cli] Add using declaration to improve formatting --- src/client/cli/cmd/restore.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/client/cli/cmd/restore.cpp b/src/client/cli/cmd/restore.cpp index 4f3b001657d..557987b7b48 100644 --- a/src/client/cli/cmd/restore.cpp +++ b/src/client/cli/cmd/restore.cpp @@ -45,9 +45,8 @@ mp::ReturnCode cmd::Restore::run(mp::ArgParser* parser) return standard_failure_handler_for(name(), cerr, status); }; - auto streaming_callback = [this, - &spinner](mp::RestoreReply& reply, - grpc::ClientReaderWriterInterface* client) { + using Client = grpc::ClientReaderWriterInterface; + auto streaming_callback = [this, &spinner](mp::RestoreReply& reply, Client* client) { if (!reply.log_line().empty()) spinner.print(cerr, reply.log_line()); From 0a27d0a4881a48740574b9b6e375f562b50f4534 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 26 Oct 2023 16:23:05 +0100 Subject: [PATCH 615/627] [cli] Fix double space in snapshot spinner msg --- src/client/cli/cmd/snapshot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/cli/cmd/snapshot.cpp b/src/client/cli/cmd/snapshot.cpp index 7d5d1a1df15..486ce277053 100644 --- a/src/client/cli/cmd/snapshot.cpp +++ b/src/client/cli/cmd/snapshot.cpp @@ -44,7 +44,7 @@ mp::ReturnCode cmd::Snapshot::run(mp::ArgParser* parser) return standard_failure_handler_for(name(), cerr, status); }; - spinner.start("Taking snapshot "); + spinner.start("Taking snapshot"); return dispatch(&RpcMethod::snapshot, request, on_success, From cae393ce369e6903026885c7a7f112560e13af90 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Thu, 26 Oct 2023 16:24:56 +0100 Subject: [PATCH 616/627] [tests] Fix missing include --- tests/json_test_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/json_test_utils.h b/tests/json_test_utils.h index 7bd74e73efa..c44b53c5fdb 100644 --- a/tests/json_test_utils.h +++ b/tests/json_test_utils.h @@ -24,6 +24,7 @@ #include #include +#include #include #include From acf850a59f5bee58ae0992fe4f069ed101e37e31 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 16:50:33 +0100 Subject: [PATCH 617/627] [snapshot] Avoid deriving the ID string each time Store the (now immutable) ID string in a snapshot field and provide an accessor to it, rather than deriving it each time it is needed. Adapt client code accordingly. --- src/platform/backends/qemu/qemu_snapshot.cpp | 6 +++--- src/platform/backends/shared/base_snapshot.cpp | 6 +----- src/platform/backends/shared/base_snapshot.h | 10 ++++++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform/backends/qemu/qemu_snapshot.cpp b/src/platform/backends/qemu/qemu_snapshot.cpp index 85f86a0ea08..cd02cc8decb 100644 --- a/src/platform/backends/qemu/qemu_snapshot.cpp +++ b/src/platform/backends/qemu/qemu_snapshot.cpp @@ -71,7 +71,7 @@ mp::QemuSnapshot::QemuSnapshot(const QString& filename, QemuVirtualMachine& vm, void mp::QemuSnapshot::capture_impl() { - const auto tag = derive_id(); + const auto& tag = get_id(); // Avoid creating more than one snapshot with the same tag (creation would succeed, but we'd then be unable to // identify the snapshot by tag) @@ -86,7 +86,7 @@ void mp::QemuSnapshot::capture_impl() void mp::QemuSnapshot::erase_impl() { - const auto tag = derive_id(); + const auto& tag = get_id(); if (backend::instance_image_has_snapshot(image_path, tag)) mp::backend::checked_exec_qemu_img(make_delete_spec(tag, image_path)); else @@ -107,6 +107,6 @@ void mp::QemuSnapshot::apply_impl() desc.mem_size = get_mem_size(); desc.disk_space = get_disk_space(); - mp::backend::checked_exec_qemu_img(make_restore_spec(derive_id(), image_path)); + mp::backend::checked_exec_qemu_img(make_restore_spec(get_id(), image_path)); rollback.dismiss(); } diff --git a/src/platform/backends/shared/base_snapshot.cpp b/src/platform/backends/shared/base_snapshot.cpp index 907e36a9a95..d613993b95a 100644 --- a/src/platform/backends/shared/base_snapshot.cpp +++ b/src/platform/backends/shared/base_snapshot.cpp @@ -113,6 +113,7 @@ mp::BaseSnapshot::BaseSnapshot(const std::string& name, // NOLINT(modernize-p comment{comment}, parent{std::move(parent)}, index{index}, + id{snapshot_template.arg(index)}, creation_timestamp{std::move(creation_timestamp)}, num_cores{num_cores}, mem_size{mem_size}, @@ -225,11 +226,6 @@ void mp::BaseSnapshot::persist() const MP_JSONUTILS.write_json(serialize(), snapshot_filepath); } -QString mp::BaseSnapshot::derive_id() const -{ - return snapshot_template.arg(index); -} - auto mp::BaseSnapshot::erase_helper() { // Remove snapshot file diff --git a/src/platform/backends/shared/base_snapshot.h b/src/platform/backends/shared/base_snapshot.h index a07afcb0c9c..eda4fd6fdbb 100644 --- a/src/platform/backends/shared/base_snapshot.h +++ b/src/platform/backends/shared/base_snapshot.h @@ -69,12 +69,12 @@ class BaseSnapshot : public Snapshot void apply() final; protected: + const QString& get_id() const noexcept; + virtual void capture_impl() = 0; virtual void erase_impl() = 0; virtual void apply_impl() = 0; - QString derive_id() const; - private: BaseSnapshot(const QJsonObject& json, VirtualMachine& vm); BaseSnapshot(const std::string& name, @@ -103,6 +103,7 @@ class BaseSnapshot : public Snapshot // This class is non-copyable and having these const simplifies thread safety const int index; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) + const QString id; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const QDateTime creation_timestamp; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const int num_cores; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) const MemorySize mem_size; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members) @@ -244,4 +245,9 @@ inline void multipass::BaseSnapshot::apply() // those cannot be affected by apply_impl (except by setters, which already persist) } +inline const QString& multipass::BaseSnapshot::get_id() const noexcept +{ + return id; +} + #endif // MULTIPASS_BASE_SNAPSHOT_H From 1bdc9c1980c963b51717f3a5d6d5699b320109c1 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 18:24:47 +0100 Subject: [PATCH 618/627] [exceptions] Rename file to match type --- ...e_not_found_exception.h => file_open_failed_exception.h} | 6 +++--- src/platform/backends/shared/base_virtual_machine.cpp | 2 +- src/utils/utils.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename include/multipass/exceptions/{file_not_found_exception.h => file_open_failed_exception.h} (87%) diff --git a/include/multipass/exceptions/file_not_found_exception.h b/include/multipass/exceptions/file_open_failed_exception.h similarity index 87% rename from include/multipass/exceptions/file_not_found_exception.h rename to include/multipass/exceptions/file_open_failed_exception.h index d4eff3f8076..b1416e9b3c4 100644 --- a/include/multipass/exceptions/file_not_found_exception.h +++ b/include/multipass/exceptions/file_open_failed_exception.h @@ -15,8 +15,8 @@ * */ -#ifndef MULTIPASS_FILE_NOT_FOUND_EXCEPTION_H -#define MULTIPASS_FILE_NOT_FOUND_EXCEPTION_H +#ifndef MULTIPASS_FILE_OPEN_FAILED_EXCEPTION_H +#define MULTIPASS_FILE_OPEN_FAILED_EXCEPTION_H #include #include @@ -34,4 +34,4 @@ class FileOpenFailedException : public std::runtime_error }; } // namespace multipass -#endif // MULTIPASS_FILE_NOT_FOUND_EXCEPTION_H +#endif // MULTIPASS_FILE_OPEN_FAILED_EXCEPTION_H diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 79edcbf7fd3..4fddddab2d2 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -17,7 +17,7 @@ #include "base_virtual_machine.h" -#include +#include #include #include #include diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index f7f0732a98c..2ceb3647fd7 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include From a917147ee30733792c5276249fd579ab6735805f Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 18:40:16 +0100 Subject: [PATCH 619/627] [vm] Move default implementation to base VM Move throwing default implementation of `make_specific_snapshot` overloads to the base VM class. --- .../backends/libvirt/libvirt_virtual_machine.cpp | 14 -------------- .../backends/libvirt/libvirt_virtual_machine.h | 7 ------- src/platform/backends/lxd/lxd_virtual_machine.cpp | 13 ------------- src/platform/backends/lxd/lxd_virtual_machine.h | 7 ------- .../backends/shared/base_virtual_machine.cpp | 13 +++++++++++++ .../backends/shared/base_virtual_machine.h | 4 ++-- 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp index 5a6ef0f2c2f..82c5dd0e296 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.cpp +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.cpp @@ -555,17 +555,3 @@ void mp::LibVirtVirtualMachine::resize_disk(const MemorySize& new_size) mp::backend::resize_instance_image(new_size, desc.image.image_path); desc.disk_space = new_size; } - -auto mp::LibVirtVirtualMachine::make_specific_snapshot(const std::string& /*snapshot_name*/, - const std::string& /*comment*/, - const VMSpecs& /*specs*/, - std::shared_ptr /*parent*/) - -> std::shared_ptr -{ - throw NotImplementedOnThisBackendException{"Snapshots"}; -} - -auto mp::LibVirtVirtualMachine::make_specific_snapshot(const QString& /*filename*/) -> std::shared_ptr -{ - throw NotImplementedOnThisBackendException{"Snapshots"}; -} diff --git a/src/platform/backends/libvirt/libvirt_virtual_machine.h b/src/platform/backends/libvirt/libvirt_virtual_machine.h index bac0737fa75..4a4d4c1a8c6 100644 --- a/src/platform/backends/libvirt/libvirt_virtual_machine.h +++ b/src/platform/backends/libvirt/libvirt_virtual_machine.h @@ -61,13 +61,6 @@ class LibVirtVirtualMachine final : public BaseVirtualMachine static ConnectionUPtr open_libvirt_connection(const LibvirtWrapper::UPtr& libvirt_wrapper); -protected: - std::shared_ptr make_specific_snapshot(const QString& filename) override; - std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, - const std::string& comment, - const VMSpecs& specs, - std::shared_ptr parent) override; - private: DomainUPtr initialize_domain_info(virConnectPtr connection); DomainUPtr checked_vm_domain() const; diff --git a/src/platform/backends/lxd/lxd_virtual_machine.cpp b/src/platform/backends/lxd/lxd_virtual_machine.cpp index 4d972434d46..1d3d3f5142d 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.cpp +++ b/src/platform/backends/lxd/lxd_virtual_machine.cpp @@ -486,16 +486,3 @@ mp::LXDVirtualMachine::make_native_mount_handler(const SSHKeyProvider* ssh_key_p return std::make_unique(manager, this, ssh_key_provider, target, mount); } - -auto mp::LXDVirtualMachine::make_specific_snapshot(const std::string& snapshot_name, - const std::string& comment, - const VMSpecs& specs, - std::shared_ptr parent) -> std::shared_ptr -{ - throw NotImplementedOnThisBackendException{"Snapshots"}; -} - -std::shared_ptr mp::LXDVirtualMachine::make_specific_snapshot(const QString& /*filename*/) -{ - throw NotImplementedOnThisBackendException{"Snapshots"}; -} diff --git a/src/platform/backends/lxd/lxd_virtual_machine.h b/src/platform/backends/lxd/lxd_virtual_machine.h index 5c46cd8b9e1..5f224a88ce3 100644 --- a/src/platform/backends/lxd/lxd_virtual_machine.h +++ b/src/platform/backends/lxd/lxd_virtual_machine.h @@ -60,13 +60,6 @@ class LXDVirtualMachine : public BaseVirtualMachine std::unique_ptr make_native_mount_handler(const SSHKeyProvider* ssh_key_provider, const std::string& target, const VMMount& mount) override; -protected: - std::shared_ptr make_specific_snapshot(const QString& filename) override; - std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, - const std::string& comment, - const VMSpecs& specs, - std::shared_ptr parent) override; - private: const QString name; const std::string username; diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 4fddddab2d2..babf62f6f3d 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -545,4 +545,17 @@ void BaseVirtualMachine::restore_snapshot(const std::string& name, VMSpecs& spec rollback.dismiss(); } +std::shared_ptr BaseVirtualMachine::make_specific_snapshot(const std::string& /*snapshot_name*/, + const std::string& /*comment*/, + const VMSpecs& /*specs*/, + std::shared_ptr /*parent*/) +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; +} + +std::shared_ptr BaseVirtualMachine::make_specific_snapshot(const QString& /*filename*/) +{ + throw NotImplementedOnThisBackendException{"Snapshots"}; +} + } // namespace multipass diff --git a/src/platform/backends/shared/base_virtual_machine.h b/src/platform/backends/shared/base_virtual_machine.h index 8ab079cda86..8649a0bfcbe 100644 --- a/src/platform/backends/shared/base_virtual_machine.h +++ b/src/platform/backends/shared/base_virtual_machine.h @@ -71,11 +71,11 @@ class BaseVirtualMachine : public VirtualMachine int get_snapshot_count() const override; protected: - virtual std::shared_ptr make_specific_snapshot(const QString& filename) = 0; + virtual std::shared_ptr make_specific_snapshot(const QString& filename); virtual std::shared_ptr make_specific_snapshot(const std::string& snapshot_name, const std::string& comment, const VMSpecs& specs, - std::shared_ptr parent) = 0; + std::shared_ptr parent); private: using SnapshotMap = std::unordered_map>; From e24e52408a190cdeb49cdb3dbc0f0eca0198a616 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 19:02:48 +0100 Subject: [PATCH 620/627] [utils] Implement remaining string trimmers --- include/multipass/utils.h | 6 ++++++ src/utils/utils.cpp | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 9a2d0d0468c..68990ec36fe 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -101,6 +101,12 @@ bool valid_mac_address(const std::string& mac); // string helpers bool has_only_digits(const std::string& value); +std::string& trim( + std::string& s, + std::function filter = [](char ch) { return std::isspace(ch); }); +std::string& trim_begin( + std::string& s, + std::function filter = [](char ch) { return std::isspace(ch); }); std::string& trim_end( std::string& s, std::function filter = [](char ch) { return std::isspace(ch); }); std::string& trim_newline(std::string& s); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 2ceb3647fd7..0485dd4062d 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -238,6 +238,18 @@ std::string mp::utils::to_cmd(const std::vector& args, QuoteType qu return cmd; } +std::string& mp::utils::trim(std::string& s, std::function filter) +{ + return trim_begin(trim_end(s, filter)); +} + +std::string& mp::utils::trim_begin(std::string& s, std::function filter) +{ + const auto it = std::find_if(s.begin(), s.end(), filter); + s.erase(s.begin(), it); + return s; +} + std::string& mp::utils::trim_end(std::string& s, std::function filter) { auto rev_it = std::find_if_not(s.rbegin(), s.rend(), filter); From a0f1612f2a5faccdd0b6151b2a01688fa462bef0 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 19:04:45 +0100 Subject: [PATCH 621/627] [vm] Use generic trim util on file contents --- src/platform/backends/shared/base_virtual_machine.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index babf62f6f3d..c0c9403ecaf 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -31,8 +31,6 @@ #include -#include - namespace mp = multipass; namespace mpl = multipass::logging; namespace mpu = multipass::utils; @@ -62,14 +60,10 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted snapshot->set_parent(deleted_parent); } -std::string trim(const std::string& s) -{ - return std::regex_replace(s, std::regex{R"(^\s+|\s+$)"}, ""); -} - std::string trimmed_contents_of(const QString& file_path) { - return trim(mpu::contents_of(file_path)); + auto contents = mpu::contents_of(file_path); + return mpu::trim(contents); } } // namespace From bc29248debf73711eb719fcb3996aab46b38e6a2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 23:32:49 +0100 Subject: [PATCH 622/627] [utils] Generalize trimmers Generalize trim utilities to operate on both lvalues and rvalues. Take the chance to drop the `std::function` wrapper for the filter predicate. --- include/multipass/utils.h | 49 ++++++++++++++++--- .../backends/shared/base_virtual_machine.cpp | 3 +- src/utils/utils.cpp | 19 ------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 68990ec36fe..75cf54138c6 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -101,14 +101,47 @@ bool valid_mac_address(const std::string& mac); // string helpers bool has_only_digits(const std::string& value); -std::string& trim( - std::string& s, - std::function filter = [](char ch) { return std::isspace(ch); }); -std::string& trim_begin( - std::string& s, - std::function filter = [](char ch) { return std::isspace(ch); }); -std::string& trim_end( - std::string& s, std::function filter = [](char ch) { return std::isspace(ch); }); + +template +Str&& trim_begin(Str&& s, Filter&& filter) +{ + const auto it = std::find_if_not(s.begin(), s.end(), std::forward(filter)); + s.erase(s.begin(), it); + return std::forward(s); +} + +template +Str&& trim_begin(Str&& s) +{ + return trim_begin(std::forward(s), [](auto ch) { return std::isspace(ch); }); +} + +template +Str&& trim_end(Str&& s, Filter&& filter) +{ + auto rev_it = std::find_if_not(s.rbegin(), s.rend(), std::forward(filter)); + s.erase(rev_it.base(), s.end()); + return std::forward(s); +} + +template +Str&& trim_end(Str&& s) +{ + return trim_end(std::forward(s), [](auto ch) { return std::isspace(ch); }); +} + +template +Str&& trim(Str&& s, Filter&& filter) +{ + return trim_begin(trim_end(std::forward(s), std::forward(filter))); +} + +template +Str&& trim(Str&& s) +{ + return trim(std::forward(s), [](auto ch) { return std::isspace(ch); }); +} + std::string& trim_newline(std::string& s); std::string escape_char(const std::string& s, char c); std::string escape_for_shell(const std::string& s); diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index c0c9403ecaf..6ac33af7eba 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -62,8 +62,7 @@ void update_parents_rollback_helper(const std::shared_ptr& deleted std::string trimmed_contents_of(const QString& file_path) { - auto contents = mpu::contents_of(file_path); - return mpu::trim(contents); + return mpu::trim(mpu::contents_of(file_path)); } } // namespace diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 0485dd4062d..c213efd1325 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -238,25 +238,6 @@ std::string mp::utils::to_cmd(const std::vector& args, QuoteType qu return cmd; } -std::string& mp::utils::trim(std::string& s, std::function filter) -{ - return trim_begin(trim_end(s, filter)); -} - -std::string& mp::utils::trim_begin(std::string& s, std::function filter) -{ - const auto it = std::find_if(s.begin(), s.end(), filter); - s.erase(s.begin(), it); - return s; -} - -std::string& mp::utils::trim_end(std::string& s, std::function filter) -{ - auto rev_it = std::find_if_not(s.rbegin(), s.rend(), filter); - s.erase(rev_it.base(), s.end()); - return s; -} - std::string& mp::utils::trim_newline(std::string& s) { assert(!s.empty() && '\n' == s.back()); From 5eb64606a43c9322120750b7d4b9b57795b5cfe8 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 23:47:53 +0100 Subject: [PATCH 623/627] [utils] Avoid repeating isspace lambda --- include/multipass/utils.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 75cf54138c6..e7f68b7d9d9 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -102,6 +102,11 @@ bool valid_mac_address(const std::string& mac); // string helpers bool has_only_digits(const std::string& value); +namespace detail +{ +auto is_space = static_cast(std::isspace); +} + template Str&& trim_begin(Str&& s, Filter&& filter) { @@ -113,7 +118,7 @@ Str&& trim_begin(Str&& s, Filter&& filter) template Str&& trim_begin(Str&& s) { - return trim_begin(std::forward(s), [](auto ch) { return std::isspace(ch); }); + return trim_begin(std::forward(s), detail::is_space); } template @@ -127,7 +132,7 @@ Str&& trim_end(Str&& s, Filter&& filter) template Str&& trim_end(Str&& s) { - return trim_end(std::forward(s), [](auto ch) { return std::isspace(ch); }); + return trim_end(std::forward(s), detail::is_space); } template @@ -139,7 +144,7 @@ Str&& trim(Str&& s, Filter&& filter) template Str&& trim(Str&& s) { - return trim(std::forward(s), [](auto ch) { return std::isspace(ch); }); + return trim(std::forward(s), detail::is_space); } std::string& trim_newline(std::string& s); From d40e8e6b447bcece9e7194b7c746cb27c6d89306 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Fri, 27 Oct 2023 23:53:08 +0100 Subject: [PATCH 624/627] [utils] Separate template declaration/definition For better readability. --- include/multipass/utils.h | 91 ++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index e7f68b7d9d9..90fc57954c9 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -101,52 +101,18 @@ bool valid_mac_address(const std::string& mac); // string helpers bool has_only_digits(const std::string& value); - -namespace detail -{ -auto is_space = static_cast(std::isspace); -} - template -Str&& trim_begin(Str&& s, Filter&& filter) -{ - const auto it = std::find_if_not(s.begin(), s.end(), std::forward(filter)); - s.erase(s.begin(), it); - return std::forward(s); -} - +Str&& trim_begin(Str&& s, Filter&& filter); template -Str&& trim_begin(Str&& s) -{ - return trim_begin(std::forward(s), detail::is_space); -} - +Str&& trim_begin(Str&& s); template -Str&& trim_end(Str&& s, Filter&& filter) -{ - auto rev_it = std::find_if_not(s.rbegin(), s.rend(), std::forward(filter)); - s.erase(rev_it.base(), s.end()); - return std::forward(s); -} - +Str&& trim_end(Str&& s, Filter&& filter); template -Str&& trim_end(Str&& s) -{ - return trim_end(std::forward(s), detail::is_space); -} - +Str&& trim_end(Str&& s); template -Str&& trim(Str&& s, Filter&& filter) -{ - return trim_begin(trim_end(std::forward(s), std::forward(filter))); -} - +Str&& trim(Str&& s, Filter&& filter); template -Str&& trim(Str&& s) -{ - return trim(std::forward(s), detail::is_space); -} - +Str&& trim(Str&& s); std::string& trim_newline(std::string& s); std::string escape_char(const std::string& s, char c); std::string escape_for_shell(const std::string& s); @@ -277,6 +243,51 @@ class Utils : public Singleton }; } // namespace multipass +namespace multipass::utils::detail +{ +inline constexpr auto is_space = static_cast(std::isspace); +} + +template +Str&& multipass::utils::trim_begin(Str&& s, Filter&& filter) +{ + const auto it = std::find_if_not(s.begin(), s.end(), std::forward(filter)); + s.erase(s.begin(), it); + return std::forward(s); +} + +template +Str&& multipass::utils::trim_begin(Str&& s) +{ + return trim_begin(std::forward(s), detail::is_space); +} + +template +Str&& multipass::utils::trim_end(Str&& s, Filter&& filter) +{ + auto rev_it = std::find_if_not(s.rbegin(), s.rend(), std::forward(filter)); + s.erase(rev_it.base(), s.end()); + return std::forward(s); +} + +template +Str&& multipass::utils::trim_end(Str&& s) +{ + return trim_end(std::forward(s), detail::is_space); +} + +template +Str&& multipass::utils::trim(Str&& s, Filter&& filter) +{ + return trim_begin(trim_end(std::forward(s), std::forward(filter))); +} + +template +Str&& multipass::utils::trim(Str&& s) +{ + return trim(std::forward(s), detail::is_space); +} + template void multipass::utils::try_action_for(OnTimeoutCallable&& on_timeout, std::chrono::milliseconds timeout, TryAction&& try_action, Args&&... args) From bc394d860201bd0f2ef2aecd21ff28c61809c606 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 30 Oct 2023 13:55:21 +0200 Subject: [PATCH 625/627] [utils] Bind `std::isspace` safely --- include/multipass/utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 90fc57954c9..cdf2f5df6d6 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -245,7 +245,8 @@ class Utils : public Singleton namespace multipass::utils::detail { -inline constexpr auto is_space = static_cast(std::isspace); +// see https://en.cppreference.com/w/cpp/string/byte/isspace#Notes +inline constexpr auto is_space = [](unsigned char c) { return std::isspace(c); }; } template From 6baa7a4398fb90ab52a482826335d36df8189e1d Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 30 Oct 2023 14:15:37 +0200 Subject: [PATCH 626/627] [utils] Fix ignored custom filter in trim --- include/multipass/utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index cdf2f5df6d6..bcd3a494d13 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -280,7 +280,8 @@ Str&& multipass::utils::trim_end(Str&& s) template Str&& multipass::utils::trim(Str&& s, Filter&& filter) { - return trim_begin(trim_end(std::forward(s), std::forward(filter))); + auto&& ret = trim_end(std::forward(s), filter); + return trim_begin(std::forward(ret), std::forward(filter)); } template From 2f856e549c43ddbb065ea8c37462847eecab84f2 Mon Sep 17 00:00:00 2001 From: Ricardo Abreu Date: Mon, 30 Oct 2023 15:15:09 +0200 Subject: [PATCH 627/627] [utils] Fix formatting --- include/multipass/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/multipass/utils.h b/include/multipass/utils.h index bcd3a494d13..a604f3af76b 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -247,7 +247,7 @@ namespace multipass::utils::detail { // see https://en.cppreference.com/w/cpp/string/byte/isspace#Notes inline constexpr auto is_space = [](unsigned char c) { return std::isspace(c); }; -} +} // namespace multipass::utils::detail template Str&& multipass::utils::trim_begin(Str&& s, Filter&& filter)