Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
EnvironmentdStatefulSet,
ListenersConfigMap,
MaterializedAliasService,
MzInstanceIdentity,
)
from materialize.cloudtest.k8s.minio import Minio
from materialize.cloudtest.k8s.mysql import mysql_resources
Expand All @@ -52,9 +53,10 @@ def __init__(
apply_node_selectors: bool = False,
) -> None:
self.tag = tag
self.instance_identity = MzInstanceIdentity.generate()
self.secret = EnvironmentdSecret()
self.listeners_configmap = ListenersConfigMap()
self.environmentd = EnvironmentdService()
self.environmentd = EnvironmentdService(self.instance_identity)
self.materialized_alias = MaterializedAliasService()
self.testdrive = TestdrivePod(
release_mode=release_mode,
Expand Down Expand Up @@ -86,6 +88,7 @@ def get_resources(self, log_filter: str | None) -> list[K8sResource]:
self.secret,
self.listeners_configmap,
EnvironmentdStatefulSet(
instance_identity=self.instance_identity,
release_mode=self.release_mode,
tag=self.tag,
log_filter=log_filter,
Expand Down
2 changes: 1 addition & 1 deletion misc/python/materialize/cloudtest/k8s/api/k8s_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def sql_query(
return cursor.fetchall()

def http_get(self, path: str) -> Any:
url = f"http://localhost:{self.node_port('internalhttp')}/{path.lstrip('/')}"
url = f"http://localhost:{self.node_port('internal-http')}/{path.lstrip('/')}"
response = requests.get(url)
response.raise_for_status()
return response.text
65 changes: 61 additions & 4 deletions misc/python/materialize/cloudtest/k8s/environmentd.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
import json
import operator
import os
import random
import string
import urllib.parse
import uuid
from collections.abc import Callable
from dataclasses import dataclass

from kubernetes.client import (
V1ConfigMap,
Expand Down Expand Up @@ -54,6 +58,47 @@
get_default_system_parameters,
)

MZ_ORGANIZATION_NAME_LABEL = "materialize.cloud/organization-name"
MZ_RESOURCE_ID_LABEL = "materialize.cloud/mz-resource-id"


@dataclass(frozen=True)
class MzInstanceIdentity:
"""Identifies one Materialize instance to self-managed tooling.

The operator stamps an organization name and a resource ID on every
Kubernetes object it provisions for an instance, and tooling such as
mz-debug discovers an instance's objects through those labels. cloudtest
deploys environmentd directly rather than through the operator, so it has to
stamp the labels itself.

Every object of an instance must carry the same values, and no two instances
living in the same namespace may share them.
"""

# Names the instance. mz-debug takes this as its `--mz-instance-name`.
organization_name: str

# Ties an object to its instance.
resource_id: str

@classmethod
def generate(cls) -> "MzInstanceIdentity":
# Resource IDs are ten characters of a lowercase alphabet, because the
# operator builds DNS-1035 object names out of them. DNS-1035 names are
# case insensitive, so mixed case would not keep them apart.
resource_id_alphabet = string.ascii_lowercase + string.digits
return cls(
organization_name=str(uuid.uuid4()),
resource_id="".join(random.choices(resource_id_alphabet, k=10)),
)

def labels(self) -> dict[str, str]:
return {
MZ_ORGANIZATION_NAME_LABEL: self.organization_name,
MZ_RESOURCE_ID_LABEL: self.resource_id,
}


class EnvironmentdSecret(K8sSecret):
def __init__(self, namespace: str = DEFAULT_K8S_NAMESPACE) -> None:
Expand Down Expand Up @@ -82,16 +127,23 @@ def __init__(self, namespace: str = DEFAULT_K8S_NAMESPACE) -> None:


class EnvironmentdService(K8sService):
def __init__(self, namespace: str = DEFAULT_K8S_NAMESPACE) -> None:
def __init__(
self,
instance_identity: MzInstanceIdentity,
namespace: str = DEFAULT_K8S_NAMESPACE,
) -> None:
super().__init__(namespace)
service_port = V1ServicePort(name="sql", port=6875)
http_port = V1ServicePort(name="http", port=6876)
internal_port = V1ServicePort(name="internal", port=6877)
internal_http_port = V1ServicePort(name="internalhttp", port=6878)
internal_http_port = V1ServicePort(name="internal-http", port=6878)
self.service = V1Service(
api_version="v1",
kind="Service",
metadata=V1ObjectMeta(name="environmentd", labels={"app": "environmentd"}),
metadata=V1ObjectMeta(
name="environmentd",
labels={"app": "environmentd", **instance_identity.labels()},
),
spec=V1ServiceSpec(
type="NodePort",
ports=[service_port, internal_port, http_port, internal_http_port],
Expand Down Expand Up @@ -119,6 +171,7 @@ def __init__(self, namespace: str = DEFAULT_K8S_NAMESPACE) -> None:
class EnvironmentdStatefulSet(K8sStatefulSet):
def __init__(
self,
instance_identity: MzInstanceIdentity,
tag: str | None = None,
release_mode: bool = True,
coverage_mode: bool = False,
Expand All @@ -129,6 +182,7 @@ def __init__(
cockroach_namespace: str = DEFAULT_K8S_NAMESPACE,
apply_node_selectors: bool = False,
) -> None:
self.instance_identity = instance_identity
self.tag = tag
self.release_mode = release_mode
self.coverage_mode = coverage_mode
Expand All @@ -142,7 +196,10 @@ def __init__(
super().__init__(namespace)

def generate_stateful_set(self) -> V1StatefulSet:
metadata = V1ObjectMeta(name="environmentd", labels={"app": "environmentd"})
metadata = V1ObjectMeta(
name="environmentd",
labels={"app": "environmentd", **self.instance_identity.labels()},
)
label_selector = V1LabelSelector(match_labels={"app": "environmentd"})

ports = [V1ContainerPort(container_port=5432, name="sql")]
Expand Down
Loading
Loading