Skip to content

Commit

Permalink
Merge pull request #3348 from canonical/check-for-suspend-support
Browse files Browse the repository at this point in the history
[lxd] check for suspend support before attempting operation
  • Loading branch information
Chris Townsend authored Jan 19, 2024
2 parents 445ae7c + c109166 commit dd25b5b
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 1 deletion.
1 change: 1 addition & 0 deletions include/multipass/virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class VirtualMachineFactory : private DisabledCopyMove
// List all the network interfaces seen by the backend.
virtual std::vector<NetworkInterfaceInfo> networks() const = 0;
virtual void require_snapshots_support() const = 0;
virtual void require_suspend_support() const = 0;
virtual std::string bridge_name_for(const std::string& iface_name) const = 0;

protected:
Expand Down
1 change: 1 addition & 0 deletions src/daemon/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2277,6 +2277,7 @@ try // clang-format on

if (status.ok())
{
config->factory->require_suspend_support();
status = cmd_vms(instance_selection.operative_selection, [this](auto& vm) {
stop_mounts(vm.vm_name);

Expand Down
7 changes: 6 additions & 1 deletion src/platform/backends/lxd/lxd_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory
const Path& cache_dir_path, const Path& data_dir_path,
const days& days_to_expire) override;
void configure(VirtualMachineDescription& vm_desc) override;

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

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

Expand All @@ -65,4 +65,9 @@ class LXDVirtualMachineFactory : public BaseVirtualMachineFactory
};
} // namespace multipass

inline void multipass::LXDVirtualMachineFactory::require_suspend_support() const
{
throw NotImplementedOnThisBackendException{"suspend"};
}

#endif // MULTIPASS_LXD_VIRTUAL_MACHINE_FACTORY_H
6 changes: 6 additions & 0 deletions src/platform/backends/shared/base_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class BaseVirtualMachineFactory : public VirtualMachineFactory

void require_snapshots_support() const override;

void require_suspend_support() const override;

std::string bridge_name_for(const std::string& iface_name) const override
{
return "";
Expand Down Expand Up @@ -114,4 +116,8 @@ inline void multipass::BaseVirtualMachineFactory::require_snapshots_support() co
throw NotImplementedOnThisBackendException{"snapshots"};
}

inline void multipass::BaseVirtualMachineFactory::require_suspend_support() const
{
}

#endif // MULTIPASS_BASE_VIRTUAL_MACHINE_FACTORY_H
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ add_executable(multipass_tests
test_daemon_launch.cpp
test_daemon_mount.cpp
test_daemon_start.cpp
test_daemon_suspend.cpp
test_daemon_umount.cpp
test_delayed_shutdown.cpp
test_disabled_copy_move.cpp
Expand Down
7 changes: 7 additions & 0 deletions tests/daemon_test_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,3 +642,10 @@ template grpc::Status mpt::DaemonTestFixture::call_daemon_slot(
void (mp::Daemon::*)(const mp::InfoRequest*, grpc::ServerReaderWriterInterface<mp::InfoReply, mp::InfoRequest>*,
std::promise<grpc::Status>*),
const mp::InfoRequest&, StrictMock<mpt::MockServerReaderWriter<mp::InfoReply, mp::InfoRequest>>&);
template grpc::Status mpt::DaemonTestFixture::call_daemon_slot(
mp::Daemon&,
void (mp::Daemon::*)(const mp::SuspendRequest*,
grpc::ServerReaderWriterInterface<mp::SuspendReply, mp::SuspendRequest>*,
std::promise<grpc::Status>*),
const mp::SuspendRequest&,
StrictMock<mpt::MockServerReaderWriter<mp::SuspendReply, mp::SuspendRequest>>&&);
8 changes: 8 additions & 0 deletions tests/lxd/test_lxd_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,14 @@ TEST_F(LXDBackend, unimplemented_functions_logs_trace_message)
backend.prepare_instance_image(image, default_description);
}

TEST_F(LXDBackend, factoryDoesNotSupportSuspend)
{
mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
MP_EXPECT_THROW_THAT(backend.require_suspend_support(),
mp::NotImplementedOnThisBackendException,
mpt::match_what(HasSubstr("suspend")));
}

TEST_F(LXDBackend, image_fetch_type_returns_expected_type)
{
mp::LXDVirtualMachineFactory backend{std::move(mock_network_access_manager), data_dir.path(), base_url};
Expand Down
1 change: 1 addition & 0 deletions tests/mock_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct MockVirtualMachineFactory : public VirtualMachineFactory
MOCK_METHOD(void, configure, (VirtualMachineDescription&), (override));
MOCK_METHOD(std::vector<NetworkInterfaceInfo>, networks, (), (const, override));
MOCK_METHOD(void, require_snapshots_support, (), (const, override));
MOCK_METHOD(void, require_suspend_support, (), (const, override));
MOCK_METHOD(std::string, bridge_name_for, (const std::string&), (const, override));

// originally protected:
Expand Down
5 changes: 5 additions & 0 deletions tests/stub_virtual_machine_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ struct StubVirtualMachineFactory : public multipass::BaseVirtualMachineFactory
return std::make_unique<StubVMImageVault>();
}

void require_suspend_support() const override
{
throw NotImplementedOnThisBackendException{"suspend"};
}

std::unique_ptr<TempDir> tmp_dir;
};
}
Expand Down
6 changes: 6 additions & 0 deletions tests/test_base_virtual_machine_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,10 @@ TEST_F(BaseFactory, prepareNetworkingGutsPreparesEachRequestedNetwork)
EXPECT_EQ(extra_nets.size(), num_nets);
EXPECT_THAT(extra_nets, Each(Eq(tag)));
}

TEST_F(BaseFactory, factoryHasDefaultSuspendSupport)
{
MockBaseFactory factory;
EXPECT_NO_THROW(factory.mp::BaseVirtualMachineFactory::require_suspend_support());
}
} // namespace
143 changes: 143 additions & 0 deletions tests/test_daemon_suspend.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/

#include "common.h"
#include "daemon_test_fixture.h"
#include "mock_mount_handler.h"
#include "mock_platform.h"
#include "mock_server_reader_writer.h"
#include "mock_settings.h"
#include "mock_virtual_machine.h"
#include "mock_vm_image_vault.h"

#include <multipass/exceptions/not_implemented_on_this_backend_exception.h>

namespace mp = multipass;
namespace mpt = multipass::test;

using namespace testing;

namespace
{
struct TestDaemonSuspend : public mpt::DaemonTestFixture
{
void SetUp() override
{
EXPECT_CALL(mock_settings, register_handler).WillRepeatedly(Return(nullptr));
EXPECT_CALL(mock_settings, unregister_handler).Times(AnyNumber());

config_builder.vault = std::make_unique<NiceMock<mpt::MockVMImageVault>>();
}

const std::string mock_instance_name{"real-zebraphant"};
const std::string mac_addr{"52:54:00:73:76:28"};
const std::string fake_target_path{"/home/ubuntu/foo"};

std::vector<mp::NetworkInterface> extra_interfaces;

mpt::MockPlatform::GuardedMock platform_attr{mpt::MockPlatform::inject<NiceMock>()};
mpt::MockPlatform* mock_platform = platform_attr.first;

mpt::MockSettings::GuardedMock mock_settings_injection = mpt::MockSettings::inject();
mpt::MockSettings& mock_settings = *mock_settings_injection.first;
};
} // namespace

TEST_F(TestDaemonSuspend, suspendNotSupportedDoesNotStopMounts)
{
auto mock_factory = use_a_mock_vm_factory();
std::unordered_map<std::string, mp::VMMount> mounts{
{fake_target_path, {"foo", {}, {}, mp::VMMount::MountType::Native}}};

const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces, mounts));
config_builder.data_directory = temp_dir->path();

auto mock_mount_handler = std::make_unique<mpt::MockMountHandler>();
EXPECT_CALL(*mock_mount_handler, deactivate_impl).Times(0);

auto mock_vm = std::make_unique<NiceMock<mpt::MockVirtualMachine>>(mock_instance_name);
EXPECT_CALL(*mock_vm, make_native_mount_handler(_, fake_target_path, _))
.WillOnce(Return(std::move(mock_mount_handler)));

EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce(Return(std::move(mock_vm)));
EXPECT_CALL(*mock_factory, require_suspend_support)
.WillOnce(Throw(mp::NotImplementedOnThisBackendException{"suspend"}));

mp::Daemon daemon{config_builder.build()};

mp::SuspendRequest request;
request.mutable_instance_names()->add_instance_name(mock_instance_name);

auto status = call_daemon_slot(daemon,
&mp::Daemon::suspend,
request,
StrictMock<mpt::MockServerReaderWriter<mp::SuspendReply, mp::SuspendRequest>>{});

EXPECT_EQ(status.error_code(), grpc::StatusCode::FAILED_PRECONDITION);
EXPECT_THAT(status.error_message(), HasSubstr(("The suspend feature is not implemented on this backend.")));
}

TEST_F(TestDaemonSuspend, suspendStopsMounts)
{
auto mock_factory = use_a_mock_vm_factory();
std::unordered_map<std::string, mp::VMMount> mounts{
{fake_target_path, {"foo", {}, {}, mp::VMMount::MountType::Native}}};

const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces, mounts));
config_builder.data_directory = temp_dir->path();

auto mock_mount_handler = std::make_unique<mpt::MockMountHandler>();
EXPECT_CALL(*mock_mount_handler, is_active).WillOnce(Return(true));
EXPECT_CALL(*mock_mount_handler, deactivate_impl);

auto mock_vm = std::make_unique<NiceMock<mpt::MockVirtualMachine>>(mock_instance_name);
EXPECT_CALL(*mock_vm, make_native_mount_handler(_, fake_target_path, _))
.WillOnce(Return(std::move(mock_mount_handler)));

EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce(Return(std::move(mock_vm)));

mp::Daemon daemon{config_builder.build()};

mp::SuspendRequest request;
request.mutable_instance_names()->add_instance_name(mock_instance_name);

auto status = call_daemon_slot(daemon,
&mp::Daemon::suspend,
request,
StrictMock<mpt::MockServerReaderWriter<mp::SuspendReply, mp::SuspendRequest>>{});

EXPECT_TRUE(status.ok());
}

TEST_F(TestDaemonSuspend, suspendNotSupportedReturnsErrorStatus)
{
const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces));
config_builder.data_directory = temp_dir->path();

mp::Daemon daemon{config_builder.build()};

mp::SuspendRequest request;
request.mutable_instance_names()->add_instance_name(mock_instance_name);

auto status = call_daemon_slot(daemon,
&mp::Daemon::suspend,
request,
StrictMock<mpt::MockServerReaderWriter<mp::SuspendReply, mp::SuspendRequest>>{});

EXPECT_EQ(status.error_code(), grpc::StatusCode::FAILED_PRECONDITION);
EXPECT_THAT(status.error_message(), HasSubstr(("The suspend feature is not implemented on this backend.")));
}

0 comments on commit dd25b5b

Please sign in to comment.