Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jv integration tests for runtime config #1899

Merged
merged 11 commits into from
Dec 9, 2024
5 changes: 3 additions & 2 deletions collector/lib/ConfigLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ bool ConfigLoader::LoadConfiguration(CollectorConfig& config) {
bool ConfigLoader::LoadConfiguration(CollectorConfig& config, const YAML::Node& node) {
const auto& config_file = CONFIG_FILE.value();

if (node.IsNull() || !node.IsDefined()) {
if (node.IsNull() || !node.IsDefined() || !node.IsMap()) {
CLOG(ERROR) << "Unable to read config from " << config_file;
return false;
}

YAML::Node networking_node = node["networking"];
if (!networking_node) {
if (!networking_node || networking_node.IsNull()) {
CLOG(DEBUG) << "No networking in " << config_file;
return true;
}
Expand Down
20 changes: 14 additions & 6 deletions collector/test/ConfigLoaderTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,22 @@ TEST(CollectorConfigTest, TestYamlConfigToConfigInvalid) {
}
}

TEST(CollectorConfigTest, TestYamlConfigToConfigEmpty) {
YAML::Node yamlNode = YAML::Load("");
CollectorConfig config;
ASSERT_FALSE(ConfigLoader::LoadConfiguration(config, yamlNode));
TEST(CollectorConfigTest, TestYamlConfigToConfigEmptyOrMalformed) {
std::vector<std::string> tests = {
R"(
asdf
)",
R"()"};

for (const auto& yamlStr : tests) {
YAML::Node yamlNode = YAML::Load(yamlStr);
CollectorConfig config;
ASSERT_FALSE(ConfigLoader::LoadConfiguration(config, yamlNode));

auto runtime_config = config.GetRuntimeConfig();
auto runtime_config = config.GetRuntimeConfig();

EXPECT_FALSE(runtime_config.has_value());
EXPECT_FALSE(runtime_config.has_value());
}
}

TEST(CollectorConfigTest, TestPerContainerRateLimit) {
Expand Down
4 changes: 4 additions & 0 deletions integration-tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,7 @@ func TestUdpNetworkFlow(t *testing.T) {
}
suite.Run(t, new(suites.UdpNetworkFlow))
}

func TestRuntimeConfigFile(t *testing.T) {
suite.Run(t, new(suites.RuntimeConfigFileTestSuite))
}
54 changes: 54 additions & 0 deletions integration-tests/pkg/assert/assert.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,67 @@
package assert

import (
"encoding/json"
"fmt"
"strings"
"testing"
"time"

"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"

"github.com/stackrox/collector/integration-tests/pkg/collector"
"github.com/stackrox/collector/integration-tests/pkg/log"
"github.com/stackrox/collector/integration-tests/pkg/types"
)

var (
runtimeConfigErrorMsg = "Runtime configuration was not updated"
)

func AssertExternalIps(t *testing.T, enabled bool, collectorIP string) {
tickTime := 1 * time.Second
timeout := 3 * time.Minute
AssertRepeated(t, tickTime, timeout, runtimeConfigErrorMsg, func() bool {
body, err := collector.IntrospectionQuery(collectorIP, "/state/config")
assert.NoError(t, err)
var response types.RuntimeConfig
err = json.Unmarshal(body, &response)
assert.NoError(t, err)

return response.Networking.ExternalIps.Enable == enabled
})
}

func AssertNoRuntimeConfig(t *testing.T, collectorIP string) {
tickTime := 1 * time.Second
timeout := 3 * time.Minute
AssertRepeated(t, tickTime, timeout, runtimeConfigErrorMsg, func() bool {
body, err := collector.IntrospectionQuery(collectorIP, "/state/config")
assert.NoError(t, err)
return strings.TrimSpace(string(body)) == "{}"
})
}

func AssertRepeated(t *testing.T, tickTime time.Duration, timeout time.Duration, msg string, condition func() bool) {
tick := time.NewTicker(tickTime)
timer := time.After(timeout)

for {
select {
case <-tick.C:
if condition() {
// Condition has been met
return
}

case <-timer:
log.Error("Timeout reached: " + msg)
t.FailNow()
}
}
}

func ElementsMatchFunc[N any](expected []N, actual []N, equal func(a, b N) bool) bool {
if len(expected) != len(actual) {
return false
Expand Down
1 change: 1 addition & 0 deletions integration-tests/pkg/collector/collector_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func NewDockerCollectorManager(e executor.Executor, name string) *DockerCollecto
"/host/etc:ro": "/etc",
"/host/usr/lib:ro": "/usr/lib",
"/host/sys/kernel/debug:ro": "/sys/kernel/debug",
"/etc/stackrox:ro": "/tmp/collector-test",
}

return &DockerCollectorManager{
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/pkg/collector/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/stackrox/collector/integration-tests/pkg/log"
)

func (k *K8sCollectorManager) IntrospectionQuery(endpoint string) ([]byte, error) {
uri := fmt.Sprintf("http://%s:8080%s", k.IP(), endpoint)
func IntrospectionQuery(collectorIP string, endpoint string) ([]byte, error) {
uri := fmt.Sprintf("http://%s:8080%s", collectorIP, endpoint)
resp, err := http.Get(uri)
if err != nil {
return nil, err
Expand Down
6 changes: 3 additions & 3 deletions integration-tests/pkg/mock_sensor/expect_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (s *MockSensor) ExpectSameElementsConnections(t *testing.T, containerID str
}

connections := s.Connections(containerID)
if collectorAssert.ElementsMatchFunc(connections, expected, equal) {
if collectorAssert.ElementsMatchFunc(expected, connections, equal) {
return true
}

Expand All @@ -100,13 +100,13 @@ func (s *MockSensor) ExpectSameElementsConnections(t *testing.T, containerID str
select {
case <-timer:
connections := s.Connections(containerID)
return collectorAssert.AssertElementsMatchFunc(t, connections, expected, equal)
return collectorAssert.AssertElementsMatchFunc(t, expected, connections, equal)
case conn := <-s.LiveConnections():
if conn.GetContainerId() != containerID {
continue
}
connections := s.Connections(containerID)
if collectorAssert.ElementsMatchFunc(connections, expected, equal) {
if collectorAssert.ElementsMatchFunc(expected, connections, equal) {
return true
}
}
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/pkg/mock_sensor/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,8 @@ func (m *MockSensor) pushConnection(containerID string, connection *sensorAPI.Ne
CloseTimestamp: connection.GetCloseTimestamp().String(),
}

if _, ok := m.connections[containerID]; ok {
m.connections[containerID] = append(m.connections[containerID], conn)
if c, ok := m.connections[containerID]; ok {
m.connections[containerID] = append(c, conn)
} else {
m.connections[containerID] = []types.NetworkInfo{conn}
}
Expand Down
27 changes: 27 additions & 0 deletions integration-tests/pkg/types/runtime_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package types

import (
"gopkg.in/yaml.v3"
)

type RuntimeConfig struct {
Networking struct {
ExternalIps struct {
Enable bool `yaml:"enable"`
} `yaml:"externalIps"`
} `yaml:"networking"`
}

func (n *RuntimeConfig) Equal(other RuntimeConfig) bool {
return n.Networking.ExternalIps.Enable == other.Networking.ExternalIps.Enable
}

func (n *RuntimeConfig) GetRuntimeConfigStr() (string, error) {
yamlBytes, err := yaml.Marshal(n)

if err != nil {
return "", err
}

return string(yamlBytes), err
}
16 changes: 16 additions & 0 deletions integration-tests/suites/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,22 @@ func (s *IntegrationTestSuiteBase) execShellCommand(command string) error {
return err
}

func (s *IntegrationTestSuiteBase) createDirectory(dir string) {
if _, err := os.Stat(dir); err == nil {
return
}
err := os.Mkdir(dir, os.ModePerm)
s.Require().NoError(err)
}

func (s *IntegrationTestSuiteBase) deleteFile(file string) {
if _, err := os.Stat(file); os.IsNotExist(err) {
return
}
err := os.Remove(file)
s.Require().NoError(err)
}

func (s *IntegrationTestSuiteBase) waitForFileToBeDeleted(file string) error {
timer := time.After(10 * time.Second)
ticker := time.NewTicker(time.Second)
Expand Down
71 changes: 8 additions & 63 deletions integration-tests/suites/k8s/config_reload.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package k8s

import (
"encoding/json"
"strings"
"time"

"github.com/stackrox/collector/integration-tests/pkg/assert"
"github.com/stackrox/collector/integration-tests/pkg/collector"
"github.com/stackrox/collector/integration-tests/pkg/log"

Expand All @@ -28,14 +25,6 @@ networking:
CONFIG_MAP_NAME = "collector-config"
)

type ConfigQueryResponse struct {
Networking struct {
ExternalIps struct {
Enable bool
}
}
}

type K8sConfigReloadTestSuite struct {
K8sTestSuiteBase
}
Expand All @@ -62,7 +51,7 @@ func (k *K8sConfigReloadTestSuite) TestCreateConfigurationAfterStart() {
})

log.Info("Checking runtime configuration is not in use")
k.assertNoRuntimeConfig()
assert.AssertNoRuntimeConfig(k.T(), k.Collector().IP())

log.Info("Checking external IPs is enabled")
configMap := coreV1.ConfigMap{
Expand All @@ -75,21 +64,21 @@ func (k *K8sConfigReloadTestSuite) TestCreateConfigurationAfterStart() {
},
}
k.createConfigMap(&configMap)
k.assertExternalIps(true)
assert.AssertExternalIps(k.T(), true, k.Collector().IP())

log.Info("Checking external IPs is disabled")
configMap.Data["runtime_config.yaml"] = EXT_IP_DISABLE
k.updateConfigMap(&configMap)
k.assertExternalIps(false)
assert.AssertExternalIps(k.T(), false, k.Collector().IP())

log.Info("Checking runtime configuration is not in use")
k.deleteConfigMap(CONFIG_MAP_NAME)
k.assertNoRuntimeConfig()
assert.AssertNoRuntimeConfig(k.T(), k.Collector().IP())

log.Info("Checking external IPs is enabled again")
configMap.Data["runtime_config.yaml"] = EXT_IP_ENABLE
k.createConfigMap(&configMap)
k.assertExternalIps(true)
assert.AssertExternalIps(k.T(), true, k.Collector().IP())
}

func (k *K8sConfigReloadTestSuite) TestConfigurationReload() {
Expand All @@ -110,54 +99,10 @@ func (k *K8sConfigReloadTestSuite) TestConfigurationReload() {
"ROX_COLLECTOR_INTROSPECTION_ENABLE": "true",
},
})
k.assertExternalIps(true)
assert.AssertExternalIps(k.T(), true, k.Collector().IP())

log.Info("Checking external IPs is disabled")
configMap.Data["runtime_config.yaml"] = EXT_IP_DISABLE
k.updateConfigMap(&configMap)
k.assertExternalIps(false)
}

func (k *K8sConfigReloadTestSuite) queryConfig() []byte {
log.Info("Querying: /state/config")
body, err := k.Collector().IntrospectionQuery("/state/config")
k.Require().NoError(err)
log.Info("Response: %q", body)
return body
}

func (k *K8sConfigReloadTestSuite) assertRepeated(condition func() bool) {
tick := time.Tick(10 * time.Second)
timer := time.After(3 * time.Minute)

for {
select {
case <-tick:
if condition() {
// Condition has been met
return
}

case <-timer:
k.FailNow("Runtime configuration was not updated")
}
}
}

func (k *K8sConfigReloadTestSuite) assertExternalIps(enable bool) {
k.assertRepeated(func() bool {
body := k.queryConfig()
var response ConfigQueryResponse
err := json.Unmarshal(body, &response)
k.Require().NoError(err)

return response.Networking.ExternalIps.Enable == enable
})
}

func (k *K8sConfigReloadTestSuite) assertNoRuntimeConfig() {
k.assertRepeated(func() bool {
body := k.queryConfig()
return strings.TrimSpace(string(body)) == "{}"
})
assert.AssertExternalIps(k.T(), false, k.Collector().IP())
}
2 changes: 1 addition & 1 deletion integration-tests/suites/k8s/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (k *K8sNamespaceTestSuite) TestK8sNamespace() {
for _, tt := range k.tests {
endpoint := fmt.Sprintf("/state/containers/%s", tt.containerID)
log.Info("Querying: %s", endpoint)
raw, err := k.Collector().IntrospectionQuery(endpoint)
raw, err := collector.IntrospectionQuery(k.Collector().IP(), endpoint)
k.Require().NoError(err)
log.Info("Response: %s", raw)

Expand Down
Loading
Loading