Skip to content

Commit

Permalink
Fix kubernetes engine (#394)
Browse files Browse the repository at this point in the history
* Fix Kubernetes engine

Signed-off-by: Tyler Gu <[email protected]>

* Add missing modules

Signed-off-by: Tyler Gu <[email protected]>

* Debug workflow

Signed-off-by: Tyler Gu <[email protected]>

* Debug workflow

Signed-off-by: Tyler Gu <[email protected]>

* Debug workflow

Signed-off-by: Tyler Gu <[email protected]>

* Debug workflow

Signed-off-by: Tyler Gu <[email protected]>

* Debug workflow

Signed-off-by: Tyler Gu <[email protected]>

---------

Signed-off-by: Tyler Gu <[email protected]>
  • Loading branch information
tylergu authored Oct 8, 2024
1 parent 73ad3d9 commit 72f131a
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/unittest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
name: .coverage.${{ github.sha }}.unittest
path: .coverage.${{ github.sha }}.unittest
retention-days: 1
include-hidden-files: true
integration-test:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -74,6 +75,7 @@ jobs:
name: .coverage.${{ github.sha }}.integration-test
path: .coverage.${{ github.sha }}.integration-test
retention-days: 1
include-hidden-files: true
coverage-report:
runs-on: ubuntu-latest
needs: [unittest, integration-test]
Expand Down
89 changes: 89 additions & 0 deletions acto/input/constraint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
""""""

from typing import Literal, Optional

import pydantic

from acto.common import PropertyPath


class XorCondition(pydantic.BaseModel):
"""Condition that is the xor of two properties"""

left: PropertyPath
right: PropertyPath
type: Literal["xor"]

def solve(
self, assumptions: list[tuple[PropertyPath, bool]]
) -> Optional[tuple[PropertyPath, bool]]:
"""Solve the condition given the assumptions"""
left = None
right = None
for assumption in assumptions:
if assumption[0] == self.left:
left = assumption[1]
if assumption[0] == self.right:
right = assumption[1]
if left is None and right is None:
return None
if left is None and right is not None:
return (self.left, not right)
if left is not None and right is None:
return (self.right, not left)
return None


# class Condition:

# def __init__(self) -> None:
# pass


# class Value(abc.ABC):

# def __init__(self) -> None:
# pass

# def resolve(self, cr: ValueWithSchema) -> Any:
# pass


# class PropertyValue(Value):

# def __init__(self, property_path: PropertyPath) -> None:
# self.property_path = property_path

# def resolve(self, cr: ValueWithSchema) -> Any:
# return cr.get_value_by_path(self.property_path.path)


# class ConstantValue(Value):

# def __init__(self, value: Any) -> None:
# self.value = value

# def resolve(self, cr: ValueWithSchema) -> Any:
# return self.value


# class EqualCondition(Condition):

# def __init__(self, left: Value, right: Value) -> None:
# self.left = left
# self.right = right

# def solve(
# self, cr: ValueWithSchema, assumptions: list[Condition]
# ) -> Optional[list[Condition]]:
# return self.left.resolve(cr) == self.right.resolve(cr)


# class XorCondition(Condition):

# def __init__(self, left: Condition, right: Condition) -> None:
# self.left = left
# self.right = right

# def solve(self, cr: ValueWithSchema) -> bool:
# return self.left.solve(cr) ^ self.right.solve(cr)
10 changes: 8 additions & 2 deletions acto/kubectl_client/helm.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,26 @@ def install(
release_name: str,
chart: str,
namespace: str,
namespace_existed: Optional[bool] = None,
repo: Optional[str] = None,
version: Optional[str] = None,
args: Optional[list] = None,
) -> subprocess.CompletedProcess:
"""Installs a helm chart"""
"""Installs a helm chart. It uses the --wait flag to wait for the deployment to be ready"""
cmd = [
"install",
release_name,
chart,
"--namespace",
namespace,
"--create-namespace",
"--wait",
]
if namespace_existed is False:
cmd.append("--create-namespace")
if repo:
cmd.extend(["--repo", repo])
if version:
cmd.extend(["--version", version])
if args:
cmd.extend(args)
return self.helm(cmd)
5 changes: 5 additions & 0 deletions acto/kubernetes_engine/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,8 @@ def get_node_list(self, name: str):
# no nodes can be found, returning an empty array
return []
return p.stdout.strip().split("\n")

@staticmethod
def cluster_name(acto_namespace: int, worker_id: int) -> str:
"""Helper function to generate cluster name"""
return f"acto-{acto_namespace}-cluster-{worker_id}"
2 changes: 1 addition & 1 deletion acto/kubernetes_engine/minikube.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def create_cluster(self, name: str, kubeconfig: str):
else:
raise RuntimeError("Missing kubeconfig for minikube create")

cmd.extend(["--nodes", str(self.num_nodes)])
cmd.extend(["--nodes", str(self.num_nodes + 1)])

if self._k8s_version != "":
cmd.extend(["--kubernetes-version", str(self._k8s_version)])
Expand Down
76 changes: 69 additions & 7 deletions acto/lib/operator_config.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from typing import Optional

import pydantic
from typing_extensions import Self

from acto.input.constraint import XorCondition

DELEGATED_NAMESPACE = "__DELEGATED__"


class ApplyStep(pydantic.BaseModel, extra="forbid"):
"""Configuration for each step of kubectl apply"""

file: str = pydantic.Field(
description="Path to the file for kubectl apply")
file: str = pydantic.Field(description="Path to the file for kubectl apply")
operator: bool = pydantic.Field(
description="If the file contains the operator deployment",
default=False,
Expand All @@ -35,20 +37,73 @@ class WaitStep(pydantic.BaseModel, extra="forbid"):
)


class HelmInstallStep(pydantic.BaseModel, extra="forbid"):
"""Configuration for each step of helm install"""

release_name: str = pydantic.Field(
description="Name of the release for helm install",
default="operator-release",
)
chart: str = pydantic.Field(
description="Path to the chart for helm install"
)
namespace: Optional[str] = pydantic.Field(
description="Namespace for installing the chart. If not specified, "
+ "use the namespace in the chart or Acto namespace. "
+ "If set to null, use the namespace in the chart",
default=DELEGATED_NAMESPACE,
)
repo: Optional[str] = pydantic.Field(
description="Name of the helm repository", default=None
)
version: Optional[str] = pydantic.Field(
description="Version of the helm chart", default=None
)
operator: bool = pydantic.Field(
description="If the file contains the operator deployment",
default=False,
)
operator_deployment_name: Optional[str] = pydantic.Field(
description="The deployment name of the operator in the operator pod, "
"required if there are multiple deployments in the operator pod",
default=None,
)
operator_container_name: Optional[str] = pydantic.Field(
description="The container name of the operator in the operator pod, "
"required if there are multiple containers in the operator pod",
default=None,
)

@pydantic.model_validator(mode="after")
def check_operator_helm_install(self) -> Self:
"""Check if the operator helm install is valid"""
if self.operator:
if (
not self.operator_deployment_name
or not self.operator_container_name
):
raise ValueError(
"operator_deployment_name and operator_container_name "
+ "are required for operator helm install for operator"
)
return self


class DeployStep(pydantic.BaseModel, extra="forbid"):
"""A step of deploying a resource"""

apply: ApplyStep = pydantic.Field(
apply: Optional[ApplyStep] = pydantic.Field(
description="Configuration for each step of kubectl apply", default=None
)
wait: WaitStep = pydantic.Field(
wait: Optional[WaitStep] = pydantic.Field(
description="Configuration for each step of waiting for the operator",
default=None,
)
helm_install: Optional[HelmInstallStep] = pydantic.Field(
description="Configuration for each step of helm install", default=None
)

# TODO: Add support for helm and kustomize
# helm: str = pydantic.Field(
# description="Path to the file for helm install")
# TODO: Add support and kustomize
# kustomize: str = pydantic.Field(
# description="Path to the file for kustomize build")

Expand Down Expand Up @@ -130,6 +185,10 @@ class OperatorConfig(pydantic.BaseModel, extra="forbid"):
default=None,
description="Name of the CRD, required if there are multiple CRDs",
)
crd_version: Optional[str] = pydantic.Field(
default=None,
description="Version of the CRD, required if there are multiple CRD versions",
)
example_dir: Optional[str] = pydantic.Field(
default=None, description="Path to the example dir"
)
Expand All @@ -139,6 +198,9 @@ class OperatorConfig(pydantic.BaseModel, extra="forbid"):
focus_fields: Optional[list[list[str]]] = pydantic.Field(
default=None, description="List of focus fields"
)
constraints: Optional[list[XorCondition]] = pydantic.Field(
default=None, description="List of constraints"
)


if __name__ == "__main__":
Expand Down
6 changes: 4 additions & 2 deletions test/integration_tests/test_kubernetes_engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import pytest

# from acto.kubernetes_engine.base import KubernetesEngine
from acto.kubernetes_engine.base import KubernetesEngine
from acto.kubernetes_engine.kind import Kind
from acto.kubernetes_engine.minikube import Minikube

testcases = [("kind", 3, "v1.27.3")]
testcases = [("kind", 4, "v1.27.3")]


@pytest.mark.kubernetes_engine
Expand All @@ -18,6 +19,7 @@ def test_kubernetes_engines(cluster_type: str, num_nodes, version):
config_path = os.path.join(os.path.expanduser("~"), ".kube/test-config")
name = "test-cluster"

cluster_instance: KubernetesEngine
if cluster_type == "kind":
cluster_instance = Kind(
acto_namespace=0, num_nodes=num_nodes, version=version
Expand All @@ -34,7 +36,7 @@ def test_kubernetes_engines(cluster_type: str, num_nodes, version):
cluster_instance.create_cluster(name, config_path)

node_list = cluster_instance.get_node_list(name)
assert len(node_list) == num_nodes
assert len(node_list) == num_nodes + 1

cluster_instance.delete_cluster(name, config_path)
with pytest.raises(RuntimeError):
Expand Down

0 comments on commit 72f131a

Please sign in to comment.