Skip to content

Commit

Permalink
Fix namespace behavior of ApplyStep in OperatorConfig
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Gu <[email protected]>
  • Loading branch information
tylergu committed Dec 13, 2023
1 parent 0994a3b commit 898e44f
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 56 deletions.
66 changes: 48 additions & 18 deletions acto/deploy.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import time

import kubernetes
import yaml

from acto.kubectl_client.kubectl import KubectlClient
from acto.lib.operator_config import DeployConfig
import acto.utils as utils
from acto.common import *
from acto.kubectl_client.kubectl import KubectlClient
from acto.lib.operator_config import DELEGATED_NAMESPACE, DeployConfig
from acto.utils import get_thread_logger
from acto.utils.preprocess import add_acto_label


def wait_for_pod_ready(apiclient: kubernetes.client.ApiClient):
logger = get_thread_logger(with_prefix=True)
logger.debug('Waiting for all pods to be ready')
logger.debug("Waiting for all pods to be ready")
time.sleep(5)
pod_ready = False
for tick in range(600):
Expand All @@ -22,17 +23,17 @@ def wait_for_pod_ready(apiclient: kubernetes.client.ApiClient):

all_pods_ready = True
for pod in pods:
if pod.status.phase == 'Succeeded':
if pod.status.phase == "Succeeded":
continue
if not utils.is_pod_ready(pod):
all_pods_ready = False

if all_pods_ready:
logger.info('Operator ready')
logger.info("Operator ready")
pod_ready = True
break
time.sleep(5)
logger.info('All pods took %d seconds to get ready' % (tick * 5))
logger.info("All pods took %d seconds to get ready" % (tick * 5))
if not pod_ready:
logger.error("Some pods failed to be ready within timeout")
return False
Expand All @@ -51,7 +52,7 @@ def __init__(self, deploy_config: DeployConfig) -> None:
self._operator_yaml = step.apply.file
break
else:
raise Exception('No operator yaml found in deploy config')
raise Exception("No operator yaml found in deploy config")

@property
def operator_yaml(self) -> str:
Expand All @@ -63,22 +64,41 @@ def deploy(self,
kubectl_client: KubectlClient,
namespace: str):
logger = get_thread_logger(with_prefix=True)
print_event('Deploying operator...')
print_event("Deploying operator...")
api_client = kubernetes_client(kubeconfig, context_name)

ret = utils.create_namespace(api_client, namespace)
if ret == None:
logger.error('Failed to create namespace')
if ret is None:
logger.error("Failed to create namespace")

# Run the steps in the deploy config one by one
for step in self._deploy_config.steps:
if step.apply:
args = ["apply", "--server-side", "-f", step.apply.file,
"--context", context_name]

# Use the namespace from the argument if the namespace is delegated
# If the namespace from the config is explicitly specified,
# use the specified namespace
# If the namespace from the config is set to None, do not apply
# with namespace
if step.apply.namespace == DELEGATED_NAMESPACE:
args += ["-n", namespace]
elif step.apply.namespace is not None:
args += ["-n", step.apply.namespace]

# Apply the yaml file and then wait for the pod to be ready
kubectl_client.kubectl(
['apply', '--server-side', '-f', step.apply.file, '-n', namespace,
'--context', context_name])
if not wait_for_pod_ready(api_client):
logger.error('Failed to deploy operator')
p = kubectl_client.kubectl(args)
if p.returncode != 0:
logger.error(
"Failed to deploy operator due to error from kubectl" +
f" (returncode={p.returncode})" +
f" (stdout={p.stdout})" +
f" (stderr={p.stderr})")
return False
elif not wait_for_pod_ready(api_client):
logger.error(
"Failed to deploy operator due to timeout waiting for pod to be ready")
return False
elif step.wait:
# Simply wait for the specified duration
Expand All @@ -87,10 +107,12 @@ def deploy(self,
# Add acto label to the operator pod
add_acto_label(api_client, namespace)
if not wait_for_pod_ready(api_client):
logger.error('Failed to deploy operator')
logger.error("Failed to deploy operator")
return False

print_event('Operator deployed')
time.sleep(20)

print_event("Operator deployed")
return True

def deploy_with_retry(self,
Expand All @@ -104,5 +126,13 @@ def deploy_with_retry(self,
if self.deploy(kubeconfig, context_name, kubectl_client, namespace):
return True
else:
logger.error('Failed to deploy operator, retrying...')
logger.error("Failed to deploy operator, retrying...")
return False

def operator_name(self) -> str:
with open(self._operator_yaml) as f:
operator_yamls = yaml.load_all(f, Loader=yaml.FullLoader)
for yaml_ in operator_yamls:
if yaml_["kind"] == "Deployment":
return yaml_["metadata"]["name"]
return None
84 changes: 46 additions & 38 deletions acto/lib/operator_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,97 +2,105 @@

from pydantic import BaseModel, Field

DELEGATED_NAMESPACE = "__DELEGATED__"

class ApplyStep(BaseModel, extra='forbid'):

class ApplyStep(BaseModel, extra="forbid"):
"""Configuration for each step of kubectl apply"""
file: str = Field(
description='Path to the file for kubectl apply')
description="Path to the file for kubectl apply")
operator: Optional[bool] = Field(
description='If the file contains the operator deployment',
description="If the file contains the operator deployment",
default=False)
namespace: Optional[str] = Field(
description="Namespace for applying the file, if not specified," +
"use the namespace in the file or Acto namespace",
default=DELEGATED_NAMESPACE)


class WaitStep(BaseModel, extra='forbid'):
class WaitStep(BaseModel, extra="forbid"):
"""Configuration for each step of waiting for the operator"""
duration: int = Field(
description='Wait for the specified seconds',
description="Wait for the specified seconds",
default=10)


class DeployStep(BaseModel, extra='forbid'):
class DeployStep(BaseModel, extra="forbid"):
apply: ApplyStep = Field(
description='Configuration for each step of kubectl apply', default=None)
description="Configuration for each step of kubectl apply",
default=None)
wait: WaitStep = Field(
description='Configuration for each step of waiting for the operator', default=None)
description="Configuration for each step of waiting for the operator",
default=None)

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


class DeployConfig(BaseModel, extra='forbid'):
class DeployConfig(BaseModel, extra="forbid"):
"""Configuration for deploying the operator"""
steps: List[DeployStep] = Field(
description='Steps to deploy the operator',
description="Steps to deploy the operator",
min_items=1,)


class AnalysisConfig(BaseModel, extra='forbid'):
class AnalysisConfig(BaseModel, extra="forbid"):
"Configuration for static analysis"
github_link: str = Field(
description='HTTPS URL for cloning the operator repo')
description="HTTPS URL for cloning the operator repo")
commit: str = Field(
description='Commit hash to specify the version to conduct static analysis')
type: str = Field(description='Type name of the CR')
description="Commit hash to specify the version to conduct static analysis")
type: str = Field(description="Type name of the CR")
package: str = Field(
description='Package name in which the type of the CR is defined')
description="Package name in which the type of the CR is defined")
entrypoint: Optional[str] = Field(
description='The relative path of the main package for the operator')
description="The relative path of the main package for the operator")


class KubernetesEngineConfig(BaseModel, extra='forbid'):
class KubernetesEngineConfig(BaseModel, extra="forbid"):
feature_gates: Dict[str, bool] = Field(
description='Path to the feature gates file', default=None)
description="Path to the feature gates file", default=None)


class OperatorConfig(BaseModel, extra='forbid'):
class OperatorConfig(BaseModel, extra="forbid"):
"""Configuration for porting operators to Acto"""
deploy: DeployConfig
analysis: Optional[AnalysisConfig] = Field(
default=None,
description='Configuration for static analysis')
description="Configuration for static analysis")

seed_custom_resource: str = Field(description='Path to the seed CR file')
seed_custom_resource: str = Field(description="Path to the seed CR file")
num_nodes: int = Field(
description='Number of workers in the Kubernetes cluster', default=4)
description="Number of workers in the Kubernetes cluster", default=4)
wait_time: int = Field(
description='Timeout duration (seconds) for the resettable timer for system convergence',
description="Timeout duration (seconds) for the resettable timer for system convergence",
default=60)
collect_coverage: bool = False
custom_oracle: Optional[str] = Field(
default=None, description='Path to the custom oracle file')
default=None, description="Path to the custom oracle file")
diff_ignore_fields: List[str] = Field(default_factory=list)
kubernetes_version: str = Field(
default='v1.22.9', description='Kubernetes version')
default="v1.22.9", description="Kubernetes version")
kubernetes_engine: KubernetesEngineConfig = Field(
default=KubernetesEngineConfig(), description='Configuration for the Kubernetes engine'
)
default=KubernetesEngineConfig(),
description="Configuration for the Kubernetes engine")

monkey_patch: Optional[str] = Field(
default=None, description='Path to the monkey patch file')
default=None, description="Path to the monkey patch file")
custom_fields: Optional[str] = Field(
default=None, description='Path to the custom fields file')
default=None, description="Path to the custom fields file")
crd_name: Optional[str] = Field(
default=None, description='Name of the CRD')
default=None, description="Name of the CRD")
blackbox_custom_fields: Optional[str] = Field(
default=None, description='Path to the blackbox custom fields file')
default=None, description="Path to the blackbox custom fields file")
k8s_fields: Optional[str] = Field(
default=None, description='Path to the k8s fields file')
default=None, description="Path to the k8s fields file")
example_dir: Optional[str] = Field(
default=None, description='Path to the example dir')
default=None, description="Path to the example dir")
context: Optional[str] = Field(
default=None, description='Path to the context file')
focus_fields: Optional[List[List[str]]] = Field(default=None,
description='List of focus fields')
default=None, description="Path to the context file")
focus_fields: Optional[List[List[str]]] = Field(
default=None, description="List of focus fields")

1 comment on commit 898e44f

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
acto
   __main__.py88880%1–175
   common.py3549972%97–98, 102–116, 119–126, 129, 132, 136, 138, 140, 142, 144, 148, 153–162, 203, 236, 249, 291, 315–316, 325, 327, 330, 334–343, 363–366, 415, 418, 432, 460–461, 498–503, 505–507, 514–517, 521, 537–538, 544–555, 614–623, 634–639
   deploy.py887119%15–41, 47–55, 59, 66–116, 124–130, 133–138
   engine.py5374919%40–57, 64–80, 94–197, 209–235, 238–296, 312–435, 439–540, 550–575, 579–587, 590–603, 628–746, 749–838, 842–933
   exception.py220%1–2
   oracle_handle.py241346%15–18, 26–27, 39–46, 54
   reproduce.py1228134%30–40, 48–52, 58–59, 72–93, 96, 99, 102, 106–108, 111, 114, 122–123, 126, 130, 134, 138, 142–176, 179–189, 192–225
   serialization.py301647%19–27, 33–39
   snapshot.py622658%32, 48, 58–79, 82–86, 89
acto/checker
   checker.py13285%12, 19
   checker_set.py571279%41–42, 67–78
   test_checker.py1621526%13–246
acto/checker/impl
   crash.py30293%14, 16
   health.py52590%37, 58–61, 76, 92
   kubectl_cli.py29486%32–33, 44–46
   operator_log.py24292%24, 32
   state.py2255177%55, 63–64, 69–93, 104–105, 133, 140, 147–149, 166, 265, 288, 317, 319–323, 329–336
   state_compare.py76396%62, 73, 80
   state_condition.py391367%18, 24, 29–37, 47, 53
acto/input
   get_matched_schemas.py542259%12, 47–51, 55–74
   input.py60948920%30–37, 74, 92, 112, 123–126, 129, 159–171, 174–185, 189, 193, 200, 206, 210–389, 410–424, 432–437, 441–454, 463, 479, 482–489, 501, 503, 505–507, 510–523, 528, 536–547, 550–556, 564–591, 595–899, 922–935, 939–986
   testcase.py552064%40–50, 53, 56, 59, 62–66, 95–96, 100, 103, 109, 116, 119
   testplan.py18313725%14–24, 27–29, 32, 35–42, 45, 48–67, 70–74, 78, 82, 85–91, 99–107, 110–121, 124, 127, 130, 133, 136–138, 141–151, 154–164, 167, 174–185, 188–194, 200, 203–219, 222, 225, 231, 239–244, 247, 250, 253, 259–260, 263–269, 272, 275, 278
   value_with_schema.py33721835%16, 20, 24, 28, 32, 36, 40, 54, 58, 61–67, 70–76, 86–114, 117–124, 128–137, 141–149, 152–156, 159, 162, 166, 180, 183, 186–192, 195–201, 211–230, 233–240, 243, 247–256, 260–272, 275–279, 282, 285, 289, 301, 307, 312, 314, 316, 320, 324, 329–333, 337–340, 349–359, 362–369, 373–376, 380–385, 388–391, 405, 408–412, 416, 421–428, 431–434, 437–439, 442–445, 448–451, 462, 465, 468, 485–498
   valuegenerator.py62038937%20, 24, 28, 32, 43, 47, 51, 55, 76–86, 90–104, 107, 110, 114, 117, 121–130, 133–139, 142, 145, 148, 151, 154, 157–167, 194–199, 202–213, 216, 219, 223, 226–231, 234–237, 240, 243–248, 251–254, 257, 260, 263, 266, 269, 273–282, 285, 288, 291, 294–304, 331–343, 346–347, 350, 353, 357, 360–365, 368–371, 374, 377–382, 385–388, 391, 394, 397, 400, 403, 406, 409–419, 450–482, 485–496, 499–502, 505–508, 512–520, 523, 526, 529, 532, 535, 538–548, 573–589, 592–609, 612, 615, 619–621, 624–628, 631–632, 635–644, 647–653, 656–657, 660–670, 673, 676, 679, 682, 685, 688–698, 710–711, 714–724, 727–730, 733–736, 740, 748, 751–752, 755–765, 768–771, 774–777, 781, 794–797, 800–814, 817, 820, 824, 827–832, 835, 838, 841–846, 849, 852, 855, 858, 861–871, 881, 884, 887, 890, 894, 913, 919, 931, 933, 936–940, 943–946, 951, 955, 957, 961–962
acto/input/known_schemas
   base.py531375%17–18, 28, 37, 46–47, 56–57, 66, 75, 84–85, 93
   cronjob_schemas.py763357%13, 16–19, 22, 25, 36–39, 42–47, 50, 53, 59, 62–65, 68, 71, 82, 85–90, 93, 96, 113, 117–119, 131, 137, 140
   deployment_schemas.py592558%16, 22–27, 30–32, 35, 38, 54–57, 65–67, 70, 78–81, 91, 94
   known_schema.py753948%28, 31–34, 37, 43, 46–48, 51, 54, 81–84, 102–113, 117–135
   pod_disruption_budget_schemas.py562261%14–17, 21, 25–27, 30, 41–44, 48, 54, 57, 68–71, 81, 84
   pod_schemas.py79727166%16–19, 23, 28, 32, 40–43, 47, 51–53, 61–64, 68, 73, 83, 92, 151, 156, 160, 167, 171, 178, 182, 238, 242, 247, 251, 294, 298, 303, 307, 335, 338, 341, 344, 347, 350, 353, 356, 359, 362, 365, 368, 393, 397, 400–405, 408, 414, 417, 420, 428, 431, 434, 437, 445, 453, 481, 488–494, 503, 507, 539, 543, 572, 575, 578, 581, 584, 587, 590, 619, 623, 626–631, 634, 642, 645, 648, 664–667, 670–672, 675, 684, 690, 693–696, 699, 702, 713–714, 717–719, 722, 725, 736, 739, 742, 745–748, 752, 756–758, 761–765, 768, 774, 777, 780, 783, 786, 789, 792, 795, 798, 801, 804, 807, 810, 813, 816, 840, 845, 849–857, 860, 866, 869, 872, 875, 878, 881, 884, 887, 890, 893, 896, 899, 902, 905, 908, 929, 933–935, 938–943, 946, 959, 964, 974, 980, 986, 989, 992, 1032, 1036–1038, 1041, 1054, 1059, 1065, 1068–1071, 1074, 1077, 1088–1091, 1094–1096, 1102, 1108, 1111–1114, 1117, 1120, 1133–1136, 1139–1141, 1147, 1153, 1156–1159, 1162, 1165, 1176–1179, 1182–1184, 1190, 1196, 1199–1202, 1205, 1212, 1215–1217, 1223, 1238, 1243, 1247, 1274, 1278, 1291, 1296, 1302, 1305, 1308, 1314, 1317, 1320–1322, 1325, 1342, 1345, 1348, 1374, 1378–1381, 1384, 1402, 1408, 1411, 1414, 1421, 1427, 1469, 1473
   resource_schemas.py1495066%15–19, 22, 25, 28–32, 35, 38, 45, 49, 54, 57–59, 62, 76, 78, 82, 96, 102–105, 108, 111, 121, 134, 147, 154, 159–162, 165, 173–176, 179, 187, 194, 199, 213, 231, 236
   service_schemas.py1786762%13, 16, 19, 25–30, 33, 36, 42, 45–48, 51, 54, 64–67, 70–73, 79, 85, 88–91, 94, 101–102, 105–108, 111, 114, 127, 142, 180, 184, 208, 214, 217, 220, 228–231, 235, 240, 244–246, 249, 257–258, 261, 264, 277–280, 284, 288–290, 293
   statefulset_schemas.py1866167%15–18, 21, 31–36, 42, 49–53, 56–58, 61–63, 66–70, 73–75, 78–80, 83, 90–93, 99–102, 105, 132, 149, 154, 158, 164, 167–170, 173, 176, 190–193, 196–201, 207, 225, 230, 234, 262, 266, 290
   storage_schemas.py1797956%13, 16, 25–30, 33, 36, 42, 48–53, 59, 67–70, 74, 79–80, 83, 89, 92–95, 98, 104–105, 108–111, 114–116, 122, 130–131, 135, 140, 145–148, 154–155, 158, 164, 181–184, 193, 197, 203–205, 211, 214, 228–231, 244, 250–252, 255, 258, 266–269, 273, 277–279, 282
acto/kubectl_client
   kubectl.py231822%8–14, 23–29, 37–44
acto/kubernetes_engine
   base.py532258%30, 34, 38, 42, 46, 49–67, 79
   k3d.py856820%17, 21–39, 45, 54–78, 81–97, 100–110, 117–133, 137–139
   kind.py993268%51–53, 57–58, 84, 90, 99–102, 108–112, 115–116, 119–131, 139, 144, 147, 158
acto/monkey_patch
   monkey_patch.py792470%9, 18, 36, 39, 41, 45–56, 76, 90–95, 106
acto/parse_log
   parse_log.py781976%71–74, 84–86, 89–91, 96–98, 116–124
acto/post_process
   post_diff_test.py40723542%64–65, 68, 78, 93–94, 133–134, 205, 215, 219, 224–233, 268, 270, 272–276, 281–298, 306–314, 317–342, 349–358, 361–423, 437–441, 461, 469–501, 504–521, 525–574, 586, 590, 596–597, 599–620, 630–666
   post_process.py1042279%51, 55, 67, 77–88, 100, 103, 138–142, 146, 150, 158, 162
   test_post_process.py28196%53
acto/runner
   runner.py32229010%29–73, 76–78, 92–147, 150–159, 162–193, 200–228, 235–258, 261–267, 270–292, 296–301, 312–317, 331–336, 349–441, 446–450, 456–466, 477–507, 512–545
acto/schema
   anyof.py442055%26, 30–38, 41, 44, 47–49, 52, 55–60
   array.py702959%31, 43–52, 57, 59–62, 71–78, 81–83, 86–88, 91, 94, 97, 103
   base.py1025744%13–15, 18–20, 23, 26–37, 40, 43, 46–48, 51–61, 64–74, 77, 80–86, 95, 100, 105, 110, 114, 118, 142, 145–149
   boolean.py271063%16, 20, 23, 26, 29–32, 35, 38
   integer.py26965%17, 21–23, 26, 29, 32, 35, 38
   number.py301163%30–32, 35–37, 40, 43, 46, 49, 52
   object.py1174859%44, 46, 51, 66–75, 80, 83, 94–109, 112–120, 123–126, 129, 132, 148, 151–158, 168
   oneof.py443032%13–19, 22, 25–27, 30–38, 41, 44, 47–49, 52, 55–60
   opaque.py17665%13, 16, 19, 22, 25, 28
   schema.py42881%21, 25, 31–34, 49–50
   string.py27967%25, 29–31, 34, 37, 40, 43, 46
acto/utils
   __init__.py13192%11
   acto_timer.py312229%10–15, 19, 22–33, 38–40, 44–47
   error_handler.py433323%13–30, 38–52, 56–74
   k8s_helper.py645120%21–27, 39–45, 57–61, 73–79, 83–91, 95–102, 106–127
   preprocess.py695816%17–73, 93–141, 147–192
   process_with_except.py990%1–13
   thread_logger.py15380%9, 18, 28
TOTAL8142421348% 

Tests Skipped Failures Errors Time
139 0 💤 0 ❌ 0 🔥 1m 41s ⏱️

Please sign in to comment.