33
44import glob
55import logging
6+ import subprocess
67from pathlib import Path
78
89import lightkube
@@ -55,22 +56,25 @@ def lightkube_client() -> Client:
5556
5657
5758@pytest .mark .abort_on_fail
58- async def test_build_and_deploy (ops_test : OpsTest ):
59+ async def test_build_and_deploy (ops_test : OpsTest , request : pytest . FixtureRequest ):
5960 """Build the charm and deploy it with trust=True.
6061
6162 Assert on the unit status.
6263 """
63- charm_under_test = await ops_test .build_charm ("." )
64-
65- await ops_test .model .deploy (charm_under_test , application_name = APP_NAME , trust = True )
64+ entity_url = (
65+ await ops_test .build_charm ("." )
66+ if not (entity_url := request .config .getoption ("--charm-path" ))
67+ else Path (entity_url ).resolve ()
68+ )
69+ await ops_test .model .deploy (entity_url , application_name = APP_NAME , trust = True )
6670 await ops_test .model .wait_for_idle (
6771 apps = [APP_NAME ], status = "active" , raise_on_blocked = True , timeout = 60 * 10
6872 )
6973 assert ops_test .model .applications [APP_NAME ].units [0 ].workload_status == "active"
7074
7175 # store charm location in global to be used in other tests
7276 global CHARM_LOCATION
73- CHARM_LOCATION = charm_under_test
77+ CHARM_LOCATION = entity_url
7478
7579 # Deploy grafana-agent for COS integration tests
7680 await deploy_and_assert_grafana_agent (ops_test .model , APP_NAME , metrics = True )
@@ -93,7 +97,7 @@ async def ensure_training_operator_is_running(ops_test: OpsTest) -> None:
9397 "wait" ,
9498 "--for=condition=ready" ,
9599 "pod" ,
96- "-lapp.kubernetes.io/name=training-operator " ,
100+ f"-l control-plane= { ops_test . model_name } - { APP_NAME } " ,
97101 f"-n{ ops_test .model_name } " ,
98102 "--timeout=10m" ,
99103 check = True ,
@@ -108,7 +112,7 @@ async def ensure_training_operator_is_running(ops_test: OpsTest) -> None:
108112 "status.phase!=Running" ,
109113 check = True ,
110114 )
111- assert "training-operator" not in out
115+ assert APP_NAME not in out
112116
113117
114118def lightkube_create_global_resources () -> dict :
@@ -228,6 +232,40 @@ async def test_metrics_endpoint(ops_test: OpsTest):
228232 await assert_metrics_endpoint (app , metrics_port = METRICS_PORT , metrics_path = METRICS_PATH )
229233
230234
235+ def get_deployment_pod_names (model : str , application_name : str ) -> list [str ]:
236+ """Retrieve names of all pods belonging to a specific Juju application.
237+
238+ This function uses kubectl to query the Kubernetes cluster for pods that match
239+ the given application name within the specified Juju model namespace. It filters
240+ pods by the label "control-plane".
241+
242+ Args:
243+ model (str): The name of the Juju model, which corresponds to the Kubernetes
244+ namespace where the pods are deployed.
245+ application_name (str): The name of the Juju application whose pods should
246+ be retrieved. This matches the "control-plane" label.
247+
248+ Returns:
249+ list[str]: A list of pod names matching the application. Returns an empty
250+ list if no pods are found or if the kubectl command fails.
251+ """
252+ cmd = [
253+ "kubectl" ,
254+ "get" ,
255+ "pods" ,
256+ f"-n{ model } " ,
257+ f"-l control-plane={ model } -{ application_name } " ,
258+ "--no-headers" ,
259+ "-o=custom-columns=NAME:.metadata.name" ,
260+ ]
261+ proc = subprocess .run (
262+ cmd ,
263+ stdout = subprocess .PIPE ,
264+ )
265+ stdout = proc .stdout .decode ("utf8" )
266+ return stdout .split ()
267+
268+
231269def build_pod_container_map (model_name : str , deployment_template : dict ) -> dict [str , dict ]:
232270 """Build full map of pods:containers belonging to this charm.
233271
@@ -236,7 +274,7 @@ def build_pod_container_map(model_name: str, deployment_template: dict) -> dict[
236274 `src/templates/deployment.yaml.j2`.
237275 """
238276 charm_pods : list = get_pod_names (model_name , APP_NAME )
239- deployment_pods : list = get_pod_names (model_name , f" { APP_NAME } -manager" )
277+ deployment_pods : list = get_deployment_pod_names (model_name , APP_NAME )
240278 deployment_container_name = deployment_template ["spec" ]["template" ]["spec" ]["containers" ][0 ][
241279 "name"
242280 ]
@@ -295,7 +333,7 @@ async def test_remove_with_resources_present(ops_test: OpsTest):
295333 lightkube_client = lightkube .Client ()
296334 crd_list = lightkube_client .list (
297335 CustomResourceDefinition ,
298- labels = [("app.juju.is/created-by" , "training-operator" )],
336+ labels = [("app.juju.is/created-by" , APP_NAME )],
299337 namespace = ops_test .model_name ,
300338 )
301339 # testing for empty list (iterator)
@@ -336,7 +374,7 @@ async def test_upgrade(ops_test: OpsTest):
336374 lightkube_client = lightkube .Client ()
337375 crd_list = lightkube_client .list (
338376 CustomResourceDefinition ,
339- labels = [("app.juju.is/created-by" , "training-operator" )],
377+ labels = [("app.juju.is/created-by" , APP_NAME )],
340378 namespace = ops_test .model_name ,
341379 )
342380 # testing for non empty list (iterator)
@@ -383,7 +421,7 @@ async def test_remove_without_resources(ops_test: OpsTest):
383421 lightkube_client = lightkube .Client ()
384422 crd_list = lightkube_client .list (
385423 CustomResourceDefinition ,
386- labels = [("app.juju.is/created-by" , "training-operator" )],
424+ labels = [("app.juju.is/created-by" , APP_NAME )],
387425 namespace = ops_test .model_name ,
388426 )
389427 for crd in crd_list :
0 commit comments