mirror of
https://github.com/microsoft/WSL.git
synced 2025-12-10 00:44:55 -06:00
Merge branch 'feature/wsl-for-apps' into user/beenachauhan/wsladiag-v2
This commit is contained in:
commit
f54b4ef039
@ -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());
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user