diff --git a/cilium/bpf_metadata.cc b/cilium/bpf_metadata.cc index 59a89dbea..b75689b40 100644 --- a/cilium/bpf_metadata.cc +++ b/cilium/bpf_metadata.cc @@ -309,6 +309,7 @@ Config::Config(const ::cilium::BpfMetadata& config, if (!created) { npmap_->maybeAddInitTarget(context.initManager()); } + npmap_->addListenerInitTarget(context.initManager()); } } diff --git a/cilium/network_policy.cc b/cilium/network_policy.cc index 932eda2e9..c1ab0aa88 100644 --- a/cilium/network_policy.cc +++ b/cilium/network_policy.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,7 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/config/subscription.h" +#include "envoy/event/timer.h" #include "envoy/http/header_map.h" #include "envoy/init/manager.h" #include "envoy/network/address.h" @@ -422,6 +424,8 @@ class NetworkPolicyMapImpl : public ManagedGrpcSubscription { // init target which starts gRPC subscription Init::TargetImpl init_target_; + Init::SharedTargetImpl initial_policy_target_{"Cilium NetworkPolicy first update", []() {}}; + Event::TimerPtr initial_policy_timer_; std::shared_ptr transport_factory_context_; // Declared after transport_factory_context_ so that the cache, which retains a shared reference @@ -1943,6 +1947,10 @@ NetworkPolicyMap::~NetworkPolicyMap() { context_.mainThreadDispatcher().post([impl = std::move(impl_)]() mutable { impl.reset(); }); } +void NetworkPolicyMap::addListenerInitTarget(Init::Manager& listener_init_manager) { + listener_init_manager.add(impl_->initial_policy_target_); +} + bool NetworkPolicyMap::exists(const std::string& endpoint_policy_name) const { return impl_->getPolicyInstanceImpl(endpoint_policy_name); } @@ -2001,6 +2009,14 @@ NetworkPolicyMapImpl::NetworkPolicyMapImpl( store(new PolicyMapSnapshot()); ENVOY_LOG(trace, "NetworkPolicyMapImpl({}) created.", instance_id_); + initial_policy_timer_ = context_.mainThreadDispatcher().createTimer([this]() { + if (initial_policy_target_.ready()) { + ENVOY_LOG(warn, "Cilium NetworkPolicy: no policy update within 5 seconds, starting listeners " + "without network policy"); + } + }); + initial_policy_timer_->enableTimer(std::chrono::seconds(5)); + if (context_.admin().has_value()) { ENVOY_LOG(debug, "Registering NetworkPolicies to config tracker"); config_tracker_entry_ = context_.admin()->getConfigTracker().add( @@ -2229,6 +2245,7 @@ absl::Status NetworkPolicyMapImpl::onConfigUpdate( installNewPolicyMap(std::move(pending_resource_map), version_init_manager, std::move(version_name), policy_stream_state); + initial_policy_target_.ready(); return absl::OkStatus(); } @@ -2341,6 +2358,7 @@ absl::Status NetworkPolicyMapImpl::onConfigUpdate( } installNewPolicyMap(std::move(pending_resource_map), version_init_manager, std::move(version_name), policy_stream_state); + initial_policy_target_.ready(); return absl::OkStatus(); } diff --git a/cilium/network_policy.h b/cilium/network_policy.h index ad45e8413..75a59347f 100644 --- a/cilium/network_policy.h +++ b/cilium/network_policy.h @@ -204,6 +204,7 @@ class NetworkPolicyMap : public Singleton::Instance, public Logger::Loggable(&factory_context_.server_factory_context_.dispatcher_); policy_map_ = std::make_shared(factory_context_, Cilium::CILIUM_XDS_API_CONFIG, false); } @@ -269,6 +274,7 @@ class CiliumNetworkPolicyTest : public ::testing::Test { NiceMock factory_context_; NiceMock secret_manager_; std::shared_ptr policy_map_; + NiceMock* initial_policy_timer_; NiceMock store_; uint16_t proxy_id_ = 42; }; @@ -277,6 +283,48 @@ TEST_F(CiliumNetworkPolicyTest, UpdatesRejectedStatName) { EXPECT_EQ("cilium.policy.updates_rejected", updatesRejectedStatName()); } +TEST_F(CiliumNetworkPolicyTest, ListenerInitWaitsForFirstPolicyUpdate) { + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_FALSE(initialized); + EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); + EXPECT_TRUE(initialized); +} + +TEST_F(CiliumNetworkPolicyTest, ListenerInitStartedAfterFirstPolicyUpdateDoesNotWait) { + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_TRUE(initialized); +} + +TEST_F(CiliumNetworkPolicyTest, ListenerAddedAfterFirstPolicyUpdateDoesNotWait) { + EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_TRUE(initialized); +} + +TEST_F(CiliumNetworkPolicyTest, ListenerInitWaitsUntilFirstPolicyUpdateTimeout) { + Init::ManagerImpl listener_init_manager("listener"); + policy_map_->addListenerInitTarget(listener_init_manager); + bool initialized = false; + Init::WatcherImpl listener_watcher("listener", [&initialized]() { initialized = true; }); + listener_init_manager.initialize(listener_watcher); + EXPECT_FALSE(initialized); + initial_policy_timer_->invokeCallback(); + EXPECT_TRUE(initialized); +} + TEST_F(CiliumNetworkPolicyTest, EmptyPolicyUpdate) { EXPECT_TRUE(subscriptionCallbacks().onConfigUpdate({}, "1").ok()); EXPECT_FALSE(validate("10.1.2.3", "")); // Policy not found