Skip to content
Draft
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
111 changes: 100 additions & 11 deletions test/launchdarkly/mzcompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

from materialize.mzcompose import DEFAULT_MZ_ENVIRONMENT_ID, DEFAULT_ORG_ID
from materialize.mzcompose.composition import Composition, Service
from materialize.mzcompose.services.balancerd import Balancerd
from materialize.mzcompose.services.materialized import Materialized
from materialize.mzcompose.services.metadata_store import CockroachOrPostgresMetadata
from materialize.mzcompose.services.testdrive import Testdrive
Expand All @@ -52,6 +53,9 @@
# A unique feature flag key to use for this test.
LD_FEATURE_FLAG_KEY = f"ci-test-{BUILDKITE_JOB_ID}"

# Balancerd has no key-map: LD flag keys must match config names exactly.
BALANCERD_DURATION_FLAG = "balancerd_sigterm_connection_wait"

SERVICES = [
CockroachOrPostgresMetadata(),
Materialized(
Expand All @@ -66,6 +70,18 @@
external_metadata_store=True,
),
Testdrive(no_reset=True, seed=1),
Balancerd(
command=[
"service",
"--pgwire-listen-addr=0.0.0.0:6875",
"--https-listen-addr=0.0.0.0:6876",
"--internal-http-listen-addr=0.0.0.0:6878",
"--static-resolver-addr=localhost:6875",
"--https-resolver-template=localhost:6876",
f"--launchdarkly-sdk-key={LAUNCHDARKLY_SDK_KEY}",
"--config-sync-loop-interval=1s",
],
),
]


Expand Down Expand Up @@ -244,6 +260,72 @@ def sys(command: str) -> None:
pass # ignore exceptions on cleanup


def workflow_balancerd_duration_sync(c: Composition) -> None:
"""A malformed Duration flag blocks ALL balancerd config syncing."""
if LAUNCHDARKLY_API_TOKEN is None:
raise UIError("Missing LAUNCHDARKLY_API_TOKEN environment variable")
if LAUNCHDARKLY_SDK_KEY is None:
raise UIError("Missing LAUNCHDARKLY_SDK_KEY environment variable")

ld_client = LaunchDarklyClient(
configuration=launchdarkly_api.Configuration(
api_key=dict(ApiKey=LAUNCHDARKLY_API_TOKEN),
),
project_key="default",
environment_key="ci-cd",
)

tags = (
["ci-test", f"gh-{BUILDKITE_PULL_REQUEST}"]
if BUILDKITE_PULL_REQUEST
else ["ci-test"]
)

try:
# off=valid "9m", on=malformed "10 munites". Targeting starts OFF.
ld_client.create_flag(
BALANCERD_DURATION_FLAG,
tags=tags,
variations=[
Variation(value="9m", name="valid"),
Variation(value="10 munites", name="malformed"),
],
defaults=Defaults(off_variation=0, on_variation=1),
)

sleep(3)

# Initial sync() succeeds with "9m", spawning the background sync loop.
c.up("balancerd")
sleep(5)

logs = c.invoke("logs", "balancerd", capture=True).stdout
assert "SyncedConfigSet:" not in logs

# Switch to malformed value; the sync loop starts failing every tick.
ld_client.update_targeting(BALANCERD_DURATION_FLAG, on=True)
sleep(10)

logs = c.invoke("logs", "balancerd", capture=True).stdout
assert "SyncedConfigSet:" in logs
assert "unknown time unit" in logs
error_count = logs.count("SyncedConfigSet:")
assert error_count >= 3, f"expected >=3 sync errors, got {error_count}"

c.stop("balancerd")
except launchdarkly_api.ApiException as e:
raise UIError(dedent(f"""
Error when calling the Launch Darkly API.
- Status: {e.status},
- Reason: {e.reason},
"""))
finally:
try:
ld_client.delete_flag(BALANCERD_DURATION_FLAG)
except Exception:
pass


class LaunchDarklyClient:
"""
A test-specific LaunchDarkly client that simulates a client modifying
Expand All @@ -260,7 +342,22 @@ def __init__(
self.project_key = project_key
self.environment_key = environment_key

def create_flag(self, feature_flag_key: str, tags: list[str] = []) -> Any:
def create_flag(
self,
feature_flag_key: str,
tags: list[str] = [],
variations: list[Any] | None = None,
defaults: Any | None = None,
) -> Any:
if variations is None:
variations = [
Variation(value=1073741824, name="1 GiB"),
Variation(value=2147483648, name="2 GiB"),
Variation(value=3221225472, name="3 GiB"),
Variation(value=4294967295, name="4 GiB - 1 (max size)"),
]
if defaults is None:
defaults = Defaults(off_variation=0, on_variation=1)
with launchdarkly_api.ApiClient(self.configuration) as api_client:
api = feature_flags_api.FeatureFlagsApi(api_client)
return api.post_feature_flag(
Expand All @@ -272,18 +369,10 @@ def create_flag(self, feature_flag_key: str, tags: list[str] = []) -> Any:
using_environment_id=True,
using_mobile_key=True,
),
variations=[
Variation(value=1073741824, name="1 GiB"),
Variation(value=2147483648, name="2 GiB"),
Variation(value=3221225472, name="3 GiB"),
Variation(value=4294967295, name="4 GiB - 1 (max size)"),
],
variations=variations,
temporary=False,
tags=tags,
defaults=Defaults(
off_variation=0,
on_variation=1,
),
defaults=defaults,
),
)

Expand Down
Loading