Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ interface IWSLCSession : IUnknown
// Network management.
HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback);
HRESULT DeleteNetwork([in] LPCSTR Name);
HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count);
HRESULT ListNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count);
HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output);
HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount);
Comment thread
beena352 marked this conversation as resolved.

Expand Down
1 change: 1 addition & 0 deletions src/windows/wslc/commands/NetworkListCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ namespace wsl::windows::wslc {
std::vector<Argument> NetworkListCommand::GetArguments() const
{
return {
Argument::Create(ArgType::Filter, false, Limit::Unlimited),
Comment thread
beena352 marked this conversation as resolved.
Argument::Create(ArgType::Format),
Argument::Create(ArgType::Quiet, false, std::nullopt, Localization::WSLCCLI_NetworkListQuietArgDesc()),
};
Expand Down
12 changes: 10 additions & 2 deletions src/windows/wslc/services/NetworkService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,19 @@ void NetworkService::Delete(models::Session& session, const std::string& name)
THROW_IF_FAILED(session.Get()->DeleteNetwork(name.c_str()));
}

std::vector<WSLCNetworkInformation> NetworkService::List(models::Session& session)
std::vector<WSLCNetworkInformation> NetworkService::List(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters)
{
std::vector<WSLCFilter> filterEntries;
filterEntries.reserve(filters.size());
for (const auto& [key, value] : filters)
{
filterEntries.push_back({.Key = key.c_str(), .Value = value.c_str()});
}

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> rawNetworks;
ULONG count = 0;
THROW_IF_FAILED(session.Get()->ListNetworks(&rawNetworks, &count));
THROW_IF_FAILED(session.Get()->ListNetworks(
filterEntries.empty() ? nullptr : filterEntries.data(), static_cast<ULONG>(filterEntries.size()), &rawNetworks, &count));

std::vector<WSLCNetworkInformation> networks;
networks.reserve(count);
Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslc/services/NetworkService.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct NetworkService
{
static void Create(Terminal& terminal, models::Session& session, const models::CreateNetworkOptions& createOptions);
static void Delete(models::Session& session, const std::string& name);
static std::vector<WSLCNetworkInformation> List(models::Session& session);
static std::vector<WSLCNetworkInformation> List(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters = {});
static wsl::windows::common::wslc_schema::Network Inspect(models::Session& session, const std::string& name);
static models::PruneNetworksResult Prune(models::Session& session, const std::vector<std::pair<std::string, std::string>>& filters = {});
static void Connect(models::Session& session, const models::ConnectNetworkOptions& connectOptions);
Expand Down
4 changes: 3 additions & 1 deletion src/windows/wslc/tasks/NetworkTasks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ void GetNetworks(CLIExecutionContext& context)
{
WI_ASSERT(context.Data.Contains(Data::Session));
auto& session = context.Data.Get<Data::Session>();
context.Data.Add<Data::Networks>(NetworkService::List(session));

auto filters = context.Args.GetAllValues<ArgType::Filter>();
context.Data.Add<Data::Networks>(NetworkService::List(session, filters));
}

void InspectNetworks(CLIExecutionContext& context)
Expand Down
11 changes: 9 additions & 2 deletions src/windows/wslcsession/DockerHTTPClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,16 @@ void DockerHTTPClient::DisconnectContainerFromNetwork(const std::string& Network
Transaction(verb::post, URL::Create("/networks/{}/disconnect", NetworkName), Request);
}

std::vector<docker_schema::Network> DockerHTTPClient::ListNetworks()
std::vector<docker_schema::Network> DockerHTTPClient::ListNetworks(const std::map<std::string, std::vector<std::string>>& filters)
{
return Transaction<docker_schema::EmptyRequest, std::vector<docker_schema::Network>>(verb::get, URL::Create("/networks"));
auto url = URL::Create("/networks");

if (!filters.empty())
{
url.SetParameter("filters", nlohmann::json(filters).dump());
}

return Transaction<docker_schema::EmptyRequest, std::vector<docker_schema::Network>>(verb::get, url);
}

docker_schema::Network DockerHTTPClient::InspectNetwork(const std::string& Name)
Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslcsession/DockerHTTPClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class DockerHTTPClient
// Network management.
common::docker_schema::CreateNetworkResponse CreateNetwork(const common::docker_schema::CreateNetwork& Request);
void RemoveNetwork(const std::string& Name);
std::vector<common::docker_schema::Network> ListNetworks();
std::vector<common::docker_schema::Network> ListNetworks(const std::map<std::string, std::vector<std::string>>& filters = {});
common::docker_schema::Network InspectNetwork(const std::string& Name);
void ConnectContainerToNetwork(const std::string& NetworkName, const common::docker_schema::ContainerNetworkRequest& Request);
void DisconnectContainerFromNetwork(const std::string& NetworkName, const common::docker_schema::ContainerNetworkRequest& Request);
Expand Down
44 changes: 43 additions & 1 deletion src/windows/wslcsession/WSLCSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2979,7 +2979,7 @@ try
}
CATCH_RETURN();

HRESULT WSLCSession::ListNetworks(WSLCNetworkInformation** Networks, ULONG* Count)
HRESULT WSLCSession::ListNetworks(const WSLCFilter* Filters, ULONG FiltersCount, WSLCNetworkInformation** Networks, ULONG* Count)
try
{
WSLCExecutionContext context(this);
Expand All @@ -2990,9 +2990,46 @@ try
*Networks = nullptr;
*Count = 0;

auto filters = wsl::windows::common::wslutil::ParseKeyMultiValuePairs(Filters, FiltersCount);
const bool filtered = !filters.empty();

if (filtered)
{
// Scope the filtered query to WSLC-managed networks.
filters["label"].push_back(WSLCNetworkManagedLabel);
}

auto lock = AcquireLease();
if (filtered)
{
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_runtime.HasDocker());
}

std::lock_guard networksLock(m_networksLock);

std::optional<std::unordered_set<std::string>> dockerNames;
if (filtered)
{
std::vector<docker_schema::Network> dockerNetworks;
try
{
dockerNetworks = m_runtime.Docker().ListNetworks(filters);
}
CATCH_AND_THROW_DOCKER_USER_ERROR("Failed to list networks");

dockerNames.emplace();
for (const auto& dockerNetwork : dockerNetworks)
{
dockerNames->insert(dockerNetwork.Name);

// Only report networks that we manage.
if (!m_networks.contains(dockerNetwork.Name))
{
WSL_LOG("ListedUnknownNetwork", TraceLoggingValue(dockerNetwork.Name.c_str(), "NetworkName"));
}
}
}

if (m_networks.empty())
{
return S_OK;
Expand All @@ -3003,6 +3040,11 @@ try
ULONG index = 0;
for (const auto& [name, entry] : m_networks)
{
if (dockerNames.has_value() && !dockerNames->contains(name))
{
continue;
Comment thread
beena352 marked this conversation as resolved.
}

THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Name, name.c_str()) != 0);
THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Id, entry.Id.c_str()) != 0);
THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Driver, entry.Driver.c_str()) != 0);
Expand Down
4 changes: 3 additions & 1 deletion src/windows/wslcsession/WSLCSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession
// Network management.
IFACEMETHOD(CreateNetwork)(_In_ const WSLCNetworkOptions* Options, _In_opt_ IWarningCallback* WarningCallback) override;
IFACEMETHOD(DeleteNetwork)(_In_ LPCSTR Name) override;
IFACEMETHOD(ListNetworks)(_Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count) override;
IFACEMETHOD(ListNetworks)
(_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCNetworkInformation** Networks, _Out_ ULONG* Count)
override;
IFACEMETHOD(InspectNetwork)(_In_ LPCSTR Name, _Out_ LPSTR* Output) override;
IFACEMETHOD(PruneNetworks)
(_In_reads_opt_(FiltersCount) const WSLCFilter* Filters, _In_ ULONG FiltersCount, _Out_ WSLCNetworkName** Networks, _Out_ ULONG* NetworksCount)
Expand Down
94 changes: 85 additions & 9 deletions test/windows/WSLCTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5374,7 +5374,7 @@ class WSLCTests

// List should start empty.
wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(0u, networks.size());

WSLCNetworkOptions options{};
Expand All @@ -5387,7 +5387,7 @@ class WSLCTests
auto cleanup = wil::scope_exit([&]() { LOG_IF_FAILED(m_defaultSession->DeleteNetwork(networkName.c_str())); });

// Verify it appears in the list with correct fields.
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(1u, networks.size());
VERIFY_ARE_EQUAL(networkName, std::string(networks[0].Name));
VERIFY_ARE_EQUAL(std::string("bridge"), std::string(networks[0].Driver));
Expand All @@ -5400,7 +5400,7 @@ class WSLCTests
VERIFY_SUCCEEDED(m_defaultSession->DeleteNetwork(networkName.c_str()));

// List should be empty again.
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(0u, networks.size());

// Delete non-existent should fail.
Expand All @@ -5418,6 +5418,82 @@ class WSLCTests
VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));
}

WSLC_TEST_METHOD(ListNetworksFilters)
{
const std::string netA = "wslc-flt-net-a";
const std::string netB = "wslc-flt-net-b";
const std::string netC = "wslc-flt-net-c";
const std::string testLabelKey = "wslc.test.list_filter";
const std::string testLabelValue = "1";
const std::string testLabelKV = testLabelKey + "=" + testLabelValue;
const std::string managedLabel = "com.microsoft.wsl.network.managed";

auto cleanup = wil::scope_exit([&]() {
for (const auto& name : {netA, netB, netC})
{
LOG_IF_FAILED(m_defaultSession->DeleteNetwork(name.c_str()));
}
});

CreateNamedNetwork(netA, {{testLabelKey.c_str(), testLabelValue.c_str()}, {"env", "prod"}, {"tier", "web"}});
CreateNamedNetwork(netB, {{testLabelKey.c_str(), testLabelValue.c_str()}, {"env", "test"}});
CreateNamedNetwork(netC, {{testLabelKey.c_str(), testLabelValue.c_str()}, {"env", "prod"}});

auto expectListFails = [&](HRESULT expected, const std::vector<WSLCFilter>& filters) {
const WSLCFilter* filtersPtr = filters.empty() ? nullptr : filters.data();
const ULONG filtersCount = static_cast<ULONG>(filters.size());

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_ARE_EQUAL(
expected, m_defaultSession->ListNetworks(filtersPtr, filtersCount, networks.addressof(), networks.size_address<ULONG>()));
};

auto expectList = [&](const std::vector<std::string>& expected,
const std::vector<WSLCFilter>& filters,
const std::source_location& source = std::source_location::current()) {
const WSLCFilter* filtersPtr = filters.empty() ? nullptr : filters.data();
const ULONG filtersCount = static_cast<ULONG>(filters.size());

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(filtersPtr, filtersCount, networks.addressof(), networks.size_address<ULONG>()));

std::vector<std::string> names;
for (const auto& n : networks)
{
names.emplace_back(n.Name);
VERIFY_IS_TRUE(strlen(n.Id) > 0);
VERIFY_ARE_EQUAL(std::string("bridge"), std::string(n.Driver));
}

VerifyAreEqualUnordered(expected, names, source);
};

const std::vector<std::string> all{netA, netB, netC};

expectList(all, {{"label", testLabelKV.c_str()}});

// label=<key>=<value> selects a subset within this test's scope.
expectList({netA, netC}, {{"label", testLabelKV.c_str()}, {"label", "env=prod"}});
expectList({netB}, {{"label", testLabelKV.c_str()}, {"label", "env=test"}});

// Multiple label filters are AND'd.
expectList({netA}, {{"label", testLabelKV.c_str()}, {"label", "env=prod"}, {"label", "tier=web"}});

// label=<key> (key-only) matches any stored value.
expectList(all, {{"label", testLabelKV.c_str()}, {"label", "env"}});

// driver filter combined with the test-scope label.
expectList(all, {{"label", testLabelKV.c_str()}, {"driver", "bridge"}});
expectList({}, {{"label", testLabelKV.c_str()}, {"driver", "nonexistent"}});

// Explicit managed-label filter is idempotent with the auto-injected one.
expectList(all, {{"label", testLabelKV.c_str()}, {"label", managedLabel.c_str()}});

// Null filter key/value is rejected.
expectListFails(E_POINTER, {{nullptr, "anything"}});
expectListFails(E_POINTER, {{"label", nullptr}});
}

WSLC_TEST_METHOD(PruneNetworksTest)
{
auto expectPrune = [&](const std::vector<std::string>& expected,
Expand Down Expand Up @@ -5457,7 +5533,7 @@ class WSLCTests
expectPrune({a, b});

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
for (const auto& n : networks)
{
VERIFY_ARE_NOT_EQUAL(a, std::string(n.Name));
Expand Down Expand Up @@ -5598,7 +5674,7 @@ class WSLCTests
VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(1u, networks.size());
VERIFY_ARE_EQUAL(networkName, std::string(networks[0].Name));
}
Expand Down Expand Up @@ -5661,7 +5737,7 @@ class WSLCTests
VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&options, nullptr));

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(1u, networks.size());
VERIFY_ARE_EQUAL(networkName, std::string(networks[0].Name));
VERIFY_ARE_EQUAL(std::string("bridge"), std::string(networks[0].Driver));
Expand Down Expand Up @@ -5899,7 +5975,7 @@ class WSLCTests
ResetTestSession();

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(1u, networks.size());
VERIFY_ARE_EQUAL(networkName, std::string(networks[0].Name));
VERIFY_ARE_EQUAL(std::string("bridge"), std::string(networks[0].Driver));
Expand Down Expand Up @@ -5952,11 +6028,11 @@ class WSLCTests
VERIFY_SUCCEEDED(m_defaultSession->CreateNetwork(&optionsC, nullptr));

wil::unique_cotaskmem_array_ptr<WSLCNetworkInformation> networks;
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(3u, networks.size());

VERIFY_SUCCEEDED(m_defaultSession->DeleteNetwork(networkNameB.c_str()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(networks.addressof(), networks.size_address<ULONG>()));
VERIFY_SUCCEEDED(m_defaultSession->ListNetworks(nullptr, 0, networks.addressof(), networks.size_address<ULONG>()));
VERIFY_ARE_EQUAL(2u, networks.size());
}

Expand Down
Loading