Merge branch 'feature/wsl-for-apps' into user/beenachauhan/wsladiag-v2

This commit is contained in:
beena352 2025-12-05 23:46:21 -08:00 committed by GitHub
commit f54b4ef039
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 410 additions and 63 deletions

View File

@ -53,7 +53,7 @@ WSLAContainerLauncher::WSLAContainerLauncher(
{
}
RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session)
std::pair<HRESULT, std::optional<RunningWSLAContainer>> WSLAContainerLauncher::LaunchNoThrow(IWSLASession& Session)
{
WSLA_CONTAINER_OPTIONS options{};
options.Image = m_image.c_str();
@ -68,7 +68,19 @@ RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session)
// TODO: Support volumes, ports, flags, shm size, container networking mode, etc.
wil::com_ptr<IWSLAContainer> container;
THROW_IF_FAILED(Session.CreateContainer(&options, &container));
auto result = Session.CreateContainer(&options, &container);
if (FAILED(result))
{
return std::pair<HRESULT, std::optional<RunningWSLAContainer>>(result, std::optional<RunningWSLAContainer>{});
}
return RunningWSLAContainer{std::move(container), std::move(m_fds)};
return std::make_pair(S_OK, RunningWSLAContainer{std::move(container), std::move(m_fds)});
}
RunningWSLAContainer WSLAContainerLauncher::Launch(IWSLASession& Session)
{
auto [result, container] = LaunchNoThrow(Session);
THROW_IF_FAILED(result);
return std::move(container.value());
}

View File

@ -33,7 +33,7 @@ private:
std::vector<WSLA_PROCESS_FD> m_fds;
};
class WSLAContainerLauncher : public WSLAProcessLauncher
class WSLAContainerLauncher : private WSLAProcessLauncher
{
public:
NON_COPYABLE(WSLAContainerLauncher);
@ -51,6 +51,7 @@ public:
void AddPort(uint16_t WindowsPort, uint16_t ContainerPort, int Family);
RunningWSLAContainer Launch(IWSLASession& Session);
std::pair<HRESULT, std::optional<RunningWSLAContainer>> LaunchNoThrow(IWSLASession& Session);
private:
std::string m_image;

View File

@ -95,6 +95,15 @@ std::pair<int, bool> RunningWSLAProcess::GetExitState()
return {code, state == WslaProcessStateSignalled};
}
WSLA_PROCESS_STATE RunningWSLAProcess::State()
{
WSLA_PROCESS_STATE state{};
int code{};
GetState(&state, &code);
return state;
}
std::string WSLAProcessLauncher::FormatResult(const RunningWSLAProcess::ProcessResult& result)
{
auto stdOut = result.Output.find(1);

View File

@ -51,6 +51,7 @@ public:
virtual wil::unique_handle GetStdHandle(int Index) = 0;
virtual wil::unique_event GetExitEvent() = 0;
std::pair<int, bool> GetExitState();
WSLA_PROCESS_STATE State();
protected:
virtual void GetState(WSLA_PROCESS_STATE* State, int* Code) = 0;

View File

@ -1666,23 +1666,28 @@ int WslaShell(_In_ std::wstring_view commandLine)
process.emplace(std::move(initProcess), std::move(fds));
}
// Save original console modes so they can be restored on exit.
DWORD OriginalInputMode{};
DWORD OriginalOutputMode{};
UINT OriginalOutputCP = GetConsoleOutputCP();
THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdin, &OriginalInputMode));
THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdout, &OriginalOutputMode));
auto restoreConsoleMode = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
SetConsoleMode(Stdin, OriginalInputMode);
SetConsoleMode(Stdout, OriginalOutputMode);
SetConsoleOutputCP(OriginalOutputCP);
});
// Configure console for interactive usage.
{
DWORD OutputMode{};
THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdout, &OutputMode));
DWORD InputMode = OriginalInputMode;
WI_SetAllFlags(InputMode, (ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT));
WI_ClearAllFlags(InputMode, (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT));
THROW_IF_WIN32_BOOL_FALSE(::SetConsoleMode(Stdin, InputMode));
WI_SetAllFlags(OutputMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(Stdout, OutputMode));
}
{
DWORD InputMode{};
THROW_LAST_ERROR_IF(!::GetConsoleMode(Stdin, &InputMode));
WI_SetAllFlags(InputMode, (ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT));
WI_ClearAllFlags(InputMode, (ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT));
THROW_IF_WIN32_BOOL_FALSE(SetConsoleMode(Stdin, InputMode));
}
DWORD OutputMode = OriginalOutputMode;
WI_SetAllFlags(OutputMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
THROW_IF_WIN32_BOOL_FALSE(::SetConsoleMode(Stdout, OutputMode));
THROW_LAST_ERROR_IF(!::SetConsoleOutputCP(CP_UTF8));

View File

@ -2,7 +2,7 @@ set(SOURCES
application.manifest
main.rc
ServiceMain.cpp
ServiceProcessLauncher.h
ServiceProcessLauncher.cpp
WSLAContainer.cpp
WSLAProcess.cpp
WSLASession.cpp
@ -12,7 +12,7 @@ set(SOURCES
)
set(HEADERS
ServiceProcessLauncher.cpp
ServiceProcessLauncher.h
WSLAContainer.h
WSLAProcess.h
WSLASession.h

View File

@ -26,6 +26,43 @@ static std::vector<std::string> defaultNerdctlRunArgs{//"--pull=never", // TODO:
"--ulimit",
"nofile=65536:65536"};
WSLAContainer::WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess, const char* name, const char* image) :
m_parentVM(parentVM), m_containerProcess(std::move(containerProcess)), m_name(name), m_image(image)
{
m_state = WslaContainerStateCreated;
// TODO: Find a better way to wait for the container to be fully started.
auto status = GetNerdctlStatus();
while (status != "running")
{
if (status == "exited" || m_containerProcess.State() != WslaProcessStateRunning)
{
m_state = WslaContainerStateExited;
return;
}
// TODO: empty string is returned while the container image is still downloading.
// Remove this logic once the image pull is separated from container creation.
if (status.has_value() && status != "created")
{
THROW_HR_MSG(
E_UNEXPECTED, "Unexpected nerdctl status '%hs', for container '%hs'", status.value_or("<empty>").c_str(), m_name.c_str());
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
status = GetNerdctlStatus();
}
// TODO: move to start() once create() and start() are split to different methods.
m_state = WslaContainerStateRunning;
}
const std::string& WSLAContainer::Image() const noexcept
{
return m_image;
}
HRESULT WSLAContainer::Start()
{
return E_NOTIMPL;
@ -37,13 +74,45 @@ HRESULT WSLAContainer::Stop(int Signal, ULONG TimeoutMs)
}
HRESULT WSLAContainer::Delete()
try
{
return E_NOTIMPL;
std::lock_guard lock{m_lock};
// Validate that the container is in the exited state.
RETURN_HR_IF_MSG(
HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
m_state != WslaContainerStateExited,
"Cannot delete container '%hs', state: %i",
m_name.c_str(),
m_state);
ServiceProcessLauncher launcher(nerdctlPath, {nerdctlPath, "rm", "-f", m_name});
auto result = launcher.Launch(*m_parentVM).WaitAndCaptureOutput();
THROW_HR_IF_MSG(E_FAIL, result.Code != 0, "%hs", launcher.FormatResult(result).c_str());
m_state = WslaContainerStateDeleted;
return S_OK;
}
CATCH_RETURN();
WSLA_CONTAINER_STATE WSLAContainer::State() noexcept
{
std::lock_guard lock{m_lock};
// If the container is running, refresh the init process state before returning.
if (m_state == WslaContainerStateRunning && m_containerProcess.State() != WSLAProcessStateRunning)
{
m_state = WslaContainerStateExited;
}
return m_state;
}
HRESULT WSLAContainer::GetState(WSLA_CONTAINER_STATE* State)
HRESULT WSLAContainer::GetState(WSLA_CONTAINER_STATE* Result)
{
return E_NOTIMPL;
*Result = State();
return S_OK;
}
HRESULT WSLAContainer::GetInitProcess(IWSLAProcess** Process)
@ -103,7 +172,7 @@ Microsoft::WRL::ComPtr<WSLAContainer> WSLAContainer::Create(const WSLA_CONTAINER
launcher.AddFd(containerOptions.InitProcessOptions.Fds[i]);
}
return wil::MakeOrThrow<WSLAContainer>(&parentVM, launcher.Launch(parentVM));
return wil::MakeOrThrow<WSLAContainer>(&parentVM, launcher.Launch(parentVM), containerOptions.Name, containerOptions.Image);
}
std::vector<std::string> WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector<std::string>&& inputOptions)
@ -160,4 +229,26 @@ std::vector<std::string> WSLAContainer::PrepareNerdctlRunCommand(const WSLA_CONT
}
return args;
}
}
std::optional<std::string> WSLAContainer::GetNerdctlStatus()
{
ServiceProcessLauncher launcher(nerdctlPath, {nerdctlPath, "inspect", "-f", "{{.State.Status}}", m_name});
auto result = launcher.Launch(*m_parentVM).WaitAndCaptureOutput();
if (result.Code != 0)
{
// Can happen if the container is not found.
// TODO: Find a way to validate that the container is indeed not found, and not some other error.
return {};
}
auto& status = result.Output[1];
while (!status.empty() && status.back() == '\n')
{
status.pop_back();
}
// N.B. nerdctl inspect can return with exit code 0 and no output. Return an empty optional if that happens.
return status.empty() ? std::optional<std::string>{} : status;
}

View File

@ -24,13 +24,9 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLAContainer
: public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLAContainer, IFastRundown>
{
public:
WSLAContainer() = default; // TODO
WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess) :
m_parentVM(parentVM), m_containerProcess(std::move(containerProcess))
{
}
WSLAContainer(const WSLAContainer&) = delete;
WSLAContainer& operator=(const WSLAContainer&) = delete;
NON_COPYABLE(WSLAContainer);
WSLAContainer(WSLAVirtualMachine* parentVM, ServiceRunningProcess&& containerProcess, const char* name, const char* image);
IFACEMETHOD(Start)() override;
IFACEMETHOD(Stop)(_In_ int Signal, _In_ ULONG TimeoutMs) override;
@ -39,11 +35,20 @@ public:
IFACEMETHOD(GetInitProcess)(_Out_ IWSLAProcess** process) override;
IFACEMETHOD(Exec)(_In_ const WSLA_PROCESS_OPTIONS* Options, _Out_ IWSLAProcess** Process, _Out_ int* Errno) override;
const std::string& Image() const noexcept;
WSLA_CONTAINER_STATE State() noexcept;
static Microsoft::WRL::ComPtr<WSLAContainer> Create(const WSLA_CONTAINER_OPTIONS& Options, WSLAVirtualMachine& parentVM);
private:
std::optional<std::string> GetNerdctlStatus();
ServiceRunningProcess m_containerProcess;
std::string m_name;
std::string m_image;
WSLA_CONTAINER_STATE m_state = WslaContainerStateInvalid;
WSLAVirtualMachine* m_parentVM = nullptr;
std::mutex m_lock;
static std::vector<std::string> PrepareNerdctlRunCommand(const WSLA_CONTAINER_OPTIONS& options, std::vector<std::string>&& inputOptions);
};

View File

@ -224,25 +224,66 @@ try
RETURN_HR_IF_NULL(E_POINTER, containerOptions);
std::lock_guard lock{m_lock};
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_virtualMachine);
ClearDeletedContainers();
// Validate that no container with the same name already exists.
auto it = m_containers.find(containerOptions->Name);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), it != m_containers.end());
// Validate that name & images are within length limits.
RETURN_HR_IF(E_INVALIDARG, strlen(containerOptions->Name) > WSLA_MAX_CONTAINER_NAME_LENGTH);
RETURN_HR_IF(E_INVALIDARG, strlen(containerOptions->Image) > WSLA_MAX_IMAGE_NAME_LENGTH);
// TODO: Log entrance into the function.
auto container = WSLAContainer::Create(*containerOptions, *m_virtualMachine.Get());
THROW_IF_FAILED(container.CopyTo(__uuidof(IWSLAContainer), (void**)Container));
RETURN_IF_FAILED(container.CopyTo(__uuidof(IWSLAContainer), (void**)Container));
m_containers.emplace(containerOptions->Name, std::move(container));
return S_OK;
}
CATCH_RETURN();
HRESULT WSLASession::OpenContainer(LPCWSTR Name, IWSLAContainer** Container)
HRESULT WSLASession::OpenContainer(LPCSTR Name, IWSLAContainer** Container)
try
{
return E_NOTIMPL;
}
std::lock_guard lock{m_lock};
auto it = m_containers.find(Name);
RETURN_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), it == m_containers.end(), "Container not found: '%hs'", Name);
HRESULT WSLASession::ListContainers(WSLA_CONTAINER** Images, ULONG* Count)
{
return E_NOTIMPL;
THROW_IF_FAILED(it->second.CopyTo(__uuidof(IWSLAContainer), (void**)Container));
return S_OK;
}
CATCH_RETURN();
HRESULT WSLASession::ListContainers(WSLA_CONTAINER** Containers, ULONG* Count)
try
{
*Count = 0;
*Containers = nullptr;
std::lock_guard lock{m_lock};
ClearDeletedContainers();
auto output = wil::make_unique_cotaskmem<WSLA_CONTAINER[]>(m_containers.size());
size_t index = 0;
for (const auto& [name, container] : m_containers)
{
THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Image, container->Image().c_str()) != 0);
THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Name, name.c_str()) != 0);
THROW_IF_FAILED(container->GetState(&output[index].State));
index++;
}
*Count = static_cast<ULONG>(m_containers.size());
*Containers = output.release();
return S_OK;
}
CATCH_RETURN();
HRESULT WSLASession::GetVirtualMachine(IWSLAVirtualMachine** VirtualMachine)
{
@ -318,4 +359,15 @@ try
m_virtualMachine.Reset();
return S_OK;
}
CATCH_RETURN();
CATCH_RETURN();
void WSLASession::ClearDeletedContainers()
{
std::lock_guard lock{m_lock};
auto deleted = std::erase_if(m_containers, [](const auto e) { return e.second->State() == WslaContainerStateDeleted; });
if (deleted > 0)
{
WSL_LOG("ClearedDeletedContainers", TraceLoggingValue(deleted, "Count"));
}
}

View File

@ -16,6 +16,7 @@ Abstract:
#include "wslaservice.h"
#include "WSLAVirtualMachine.h"
#include "WSLAContainer.h"
namespace wsl::windows::service::wsla {
@ -41,7 +42,7 @@ public:
// Container management.
IFACEMETHOD(CreateContainer)(_In_ const WSLA_CONTAINER_OPTIONS* Options, _Out_ IWSLAContainer** Container) override;
IFACEMETHOD(OpenContainer)(_In_ LPCWSTR Name, _In_ IWSLAContainer** Container) override;
IFACEMETHOD(OpenContainer)(_In_ LPCSTR Name, _In_ IWSLAContainer** Container) override;
IFACEMETHOD(ListContainers)(_Out_ WSLA_CONTAINER** Images, _Out_ ULONG* Count) override;
// VM management.
@ -62,15 +63,15 @@ private:
void ConfigureStorage(const WSLA_SESSION_SETTINGS& Settings);
void Ext4Format(const std::string& Device);
void ClearDeletedContainers();
WSLA_SESSION_SETTINGS m_sessionSettings; // TODO: Revisit to see if we should have session settings as a member or not
WSLAUserSessionImpl* m_userSession = nullptr;
Microsoft::WRL::ComPtr<WSLAVirtualMachine> m_virtualMachine;
std::wstring m_displayName;
std::filesystem::path m_storageVhdPath;
std::mutex m_lock;
// TODO: Add container tracking here. Could reuse m_lock for that.
std::map<std::string, Microsoft::WRL::ComPtr<WSLAContainer>> m_containers;
std::recursive_mutex m_lock;
};
} // namespace wsl::windows::service::wsla

View File

@ -20,6 +20,12 @@ cpp_quote("#ifdef __cplusplus")
cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLAUserSession;")
cpp_quote("#endif")
#define WSLA_MAX_CONTAINER_NAME_LENGTH 255
#define WSLA_MAX_IMAGE_NAME_LENGTH 255
cpp_quote("#define WSLA_MAX_CONTAINER_NAME_LENGTH 255")
cpp_quote("#define WSLA_MAX_IMAGE_NAME_LENGTH 255")
typedef
struct _WSLA_VERSION {
ULONG Major;
@ -96,7 +102,7 @@ struct WSLA_REGISTRY_AUTHENTICATION_INFORMATION
struct WSLA_IMAGE_INFORMATION
{
LPWSTR Name;
char Image[WSLA_MAX_IMAGE_NAME_LENGTH + 1];
LPWSTR Hash;
ULONGLONG Size;
ULONGLONG DownloadTimestamp;
@ -156,14 +162,14 @@ enum WSLA_CONTAINER_STATE
WslaContainerStateCreated = 1,
WslaContainerStateRunning = 2,
WslaContainerStateExited = 3,
WslaContainerStateFailed = 4,
WslaContainerStateDeleted = 4,
// TODO: More states might be added to reflect all nerdctl's states.
};
struct WSLA_CONTAINER
{
LPWSTR Name;
LPWSTR Image;
char Name[WSLA_MAX_CONTAINER_NAME_LENGTH + 1];
char Image[WSLA_MAX_IMAGE_NAME_LENGTH + 1];
enum WSLA_CONTAINER_STATE State;
// TODO: Add creation timestamp and other fields that the command line tool might want to display.
@ -279,7 +285,7 @@ interface IWSLASession : IUnknown
// Container management.
HRESULT CreateContainer([in] const struct WSLA_CONTAINER_OPTIONS* Options, [out] IWSLAContainer** Container);
HRESULT OpenContainer([in] LPCWSTR Name, [out] IWSLAContainer** Container);
HRESULT OpenContainer([in] LPCSTR Name, [out] IWSLAContainer** Container);
HRESULT ListContainers([out, size_is(, *Count)] struct WSLA_CONTAINER** Images, [out] ULONG* Count);
// Create a process at the VM level. This is meant for debugging.

View File

@ -1042,13 +1042,7 @@ class WSLATests
TEST_METHOD(CreateContainer)
{
WSL2_TEST_ONLY();
#ifdef _ARM64_
LogSkipped("Skipping CreateContainer test case for ARM64");
return;
#else
SKIP_TEST_ARM64();
auto storagePath = std::filesystem::current_path() / "test-storage";
@ -1062,9 +1056,6 @@ class WSLATests
}
});
auto installedVhdPath =
std::filesystem::path(wsl::windows::common::wslutil::GetMsiPackagePath().value()) / L"wslarootfs.vhd";
auto settings = GetDefaultSessionSettings();
settings.NetworkingMode = WSLANetworkingModeNAT;
settings.StoragePath = storagePath.c_str();
@ -1126,6 +1117,179 @@ class WSLATests
ValidateProcessOutput(process, {{1, ""}});
}
#endif
// Validate error paths
{
WSLAContainerLauncher launcher("debian:latest", std::string(WSLA_MAX_CONTAINER_NAME_LENGTH + 1, 'a'), "/bin/cat");
auto [hresult, container] = launcher.LaunchNoThrow(*session);
VERIFY_ARE_EQUAL(hresult, E_INVALIDARG);
}
{
WSLAContainerLauncher launcher(std::string(WSLA_MAX_IMAGE_NAME_LENGTH + 1, 'a'), "dummy", "/bin/cat");
auto [hresult, container] = launcher.LaunchNoThrow(*session);
VERIFY_ARE_EQUAL(hresult, E_INVALIDARG);
}
// TODO: Add logic to detect when starting the container fails, and enable this test case.
/*{
WSLAContainerLauncher launcher("invalid-image-name", "dummy", "/bin/cat");
auto [hresult, container] = launcher.LaunchNoThrow(*session);
VERIFY_ARE_EQUAL(hresult, E_FAIL); // TODO: Have a nicer error code when the image is not found.
}*/
}
};
TEST_METHOD(ContainerState)
{
WSL2_TEST_ONLY();
SKIP_TEST_ARM64();
auto storagePath = std::filesystem::current_path() / "test-storage";
auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
std::error_code error;
std::filesystem::remove_all(storagePath, error);
if (error)
{
LogError("Failed to cleanup storage path %ws: %s", storagePath.c_str(), error.message().c_str());
}
});
auto settings = GetDefaultSessionSettings();
settings.NetworkingMode = WSLANetworkingModeNAT;
settings.StoragePath = storagePath.c_str();
settings.MaximumStorageSizeMb = 1024;
auto session = CreateSession(settings);
auto expectContainerList = [&](const std::vector<std::tuple<std::string, std::string, WSLA_CONTAINER_STATE>>& expectedContainers) {
wil::unique_cotaskmem_array_ptr<WSLA_CONTAINER> containers;
VERIFY_SUCCEEDED(session->ListContainers(&containers, containers.size_address<ULONG>()));
VERIFY_ARE_EQUAL(expectedContainers.size(), containers.size());
for (size_t i = 0; i < expectedContainers.size(); i++)
{
const auto& [expectedName, expectedImage, expectedState] = expectedContainers[i];
VERIFY_ARE_EQUAL(expectedName, containers[i].Name);
VERIFY_ARE_EQUAL(expectedImage, containers[i].Image);
VERIFY_ARE_EQUAL(expectedState, containers[i].State);
}
};
{
// Validate that the container list is initially empty.
expectContainerList({});
// Start one container and wait for it to exit.
{
WSLAContainerLauncher launcher("debian:latest", "exited-container", "echo", {"OK"});
auto container = launcher.Launch(*session);
auto process = container.GetInitProcess();
ValidateProcessOutput(process, {{1, "OK\n"}});
expectContainerList({{"exited-container", "debian:latest", WslaContainerStateExited}});
}
// Create a stuck container.
WSLAContainerLauncher launcher(
"debian:latest", "test-container-1", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr);
auto container = launcher.Launch(*session);
// Verify that the container is in running state.
VERIFY_ARE_EQUAL(container.State(), WslaContainerStateRunning);
expectContainerList(
{{"exited-container", "debian:latest", WslaContainerStateExited},
{"test-container-1", "debian:latest", WslaContainerStateRunning}});
// Kill the container init process and expect it to be in exited state.
auto initProcess = container.GetInitProcess();
initProcess.Get().Signal(9);
// Wait for the process to actually exit.
wsl::shared::retry::RetryWithTimeout<void>(
[&]() {
initProcess.GetExitState(); // Throw if the process hasn't exited yet.
},
std::chrono::milliseconds{100},
std::chrono::seconds{30});
// Expect the container to be in exited state.
VERIFY_ARE_EQUAL(container.State(), WslaContainerStateExited);
expectContainerList(
{{"exited-container", "debian:latest", WslaContainerStateExited},
{"test-container-1", "debian:latest", WslaContainerStateExited}});
// Open a new reference to the same container.
wil::com_ptr<IWSLAContainer> sameContainer;
VERIFY_SUCCEEDED(session->OpenContainer("test-container-1", &sameContainer));
// Verify that the state matches.
WSLA_CONTAINER_STATE state{};
VERIFY_SUCCEEDED(sameContainer->GetState(&state));
VERIFY_ARE_EQUAL(state, WslaContainerStateExited);
VERIFY_SUCCEEDED(container.Get().Delete());
}
// Verify that trying to open a non existing container fails.
{
wil::com_ptr<IWSLAContainer> sameContainer;
VERIFY_ARE_EQUAL(session->OpenContainer("does-not-exist", &sameContainer), HRESULT_FROM_WIN32(ERROR_NOT_FOUND));
}
// Validate that container names are unique.
{
WSLAContainerLauncher launcher(
"debian:latest", "test-unique-name", "/bin/cat", {}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr);
auto container = launcher.Launch(*session);
VERIFY_ARE_EQUAL(container.State(), WslaContainerStateRunning);
// Validate that a container with the same name cannot be started
VERIFY_ARE_EQUAL(
WSLAContainerLauncher("debian:latest", "test-unique-name", "echo", {"OK"}).LaunchNoThrow(*session).first,
HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
// Validate that running containers can't be deleted.
VERIFY_ARE_EQUAL(container.Get().Delete(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
// Kill the container.
auto initProcess = container.GetInitProcess();
initProcess.Get().Signal(9);
auto r = initProcess.WaitAndCaptureOutput();
LogInfo("Output: %hs|%hs", r.Output[1].c_str(), r.Output[2].c_str());
// Wait for the process to actually exit.
wsl::shared::retry::RetryWithTimeout<void>(
[&]() {
initProcess.GetExitState(); // Throw if the process hasn't exited yet.
},
std::chrono::milliseconds{100},
std::chrono::seconds{30});
expectContainerList(
{{"exited-container", "debian:latest", WslaContainerStateExited},
{"test-unique-name", "debian:latest", WslaContainerStateExited}});
// Verify that stopped containers can be deleted.
VERIFY_SUCCEEDED(container.Get().Delete());
// Verify that deleted containers can't be deleted again.
VERIFY_ARE_EQUAL(container.Get().Delete(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
// Verify that deleted containers don't show up in the container list.
expectContainerList({{"exited-container", "debian:latest", WslaContainerStateExited}});
// Verify that the same name can be reused now that the container is deleted.
WSLAContainerLauncher otherLauncher(
"debian:latest", "test-unique-name", "echo", {"OK"}, {}, ProcessFlags::Stdin | ProcessFlags::Stdout | ProcessFlags::Stderr);
auto result = otherLauncher.Launch(*session).GetInitProcess().WaitAndCaptureOutput();
VERIFY_ARE_EQUAL(result.Output[1], "OK\n");
VERIFY_ARE_EQUAL(result.Code, 0);
}
}
};