From 8dbb0b3a63ee1247d4754f18e6af3d82ced5d1d5 Mon Sep 17 00:00:00 2001 From: "Petr \"Stone\" Hracek" Date: Tue, 30 Jul 2024 16:01:11 +0200 Subject: [PATCH] Some refactoring Signed-off-by: Petr "Stone" Hracek --- container_ci_suite/helm.py | 154 +++--------- container_ci_suite/openshift.py | 349 ++++------------------------ container_ci_suite/openshift_ops.py | 289 +++++++++++++++++++++++ container_ci_suite/utils.py | 23 ++ tests/test_helm.py | 6 +- tests/test_openshift.py | 47 +--- tests/test_openshift_ops.py | 74 ++++++ 7 files changed, 468 insertions(+), 474 deletions(-) create mode 100644 container_ci_suite/openshift_ops.py create mode 100644 tests/test_openshift_ops.py diff --git a/container_ci_suite/helm.py b/container_ci_suite/helm.py index d4ead8c..8bd7244 100644 --- a/container_ci_suite/helm.py +++ b/container_ci_suite/helm.py @@ -22,7 +22,6 @@ import json import yaml import logging -import random import time import requests import subprocess @@ -31,7 +30,10 @@ from pathlib import Path import container_ci_suite.utils as utils + from container_ci_suite.openshift import OpenShiftAPI +from container_ci_suite.openshift_ops import OpenShiftOperations +from container_ci_suite.utils import run_oc_command logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -40,8 +42,7 @@ class HelmChartsAPI: def __init__( - self, path: Path, package_name: str, tarball_dir: Path, - namespace: str = "helm-default", delete_prj: bool = True + self, path: Path, package_name: str, tarball_dir: Path, delete_prj: bool = True ): self.path: Path = path self.version: str = "" @@ -49,12 +50,7 @@ def __init__( self.tarball_dir = tarball_dir self.delete_prj: bool = delete_prj self.create_prj: bool = True - if namespace == "helm-default": - self.namespace = f"helm-sclorg-{random.randrange(10000, 100000)}" - else: - self.namespace = namespace - self.create_prj = False - self.oc_api = OpenShiftAPI(namespace=self.namespace, create_prj=self.create_prj, delete_prj=self.delete_prj) + self.oc_api = OpenShiftAPI(create_prj=self.create_prj, delete_prj=self.delete_prj) self.oc_api.create_prj = self.create_prj self.oc_api.create_project() self.pod_json_data: dict = {} @@ -111,6 +107,16 @@ def is_chart_yaml_present(self): return True return False + def is_s2i_pod_running(self, pod_name_prefix: str): + oc_ops = OpenShiftOperations() + oc_ops.set_namespace(namespace=self.oc_api.namespace) + return oc_ops.is_s2i_pod_running(pod_name_prefix=pod_name_prefix) + + def is_pod_running(self, pod_name_prefix: str): + oc_ops = OpenShiftOperations() + oc_ops.set_namespace(namespace=self.oc_api.namespace) + return oc_ops.is_pod_running(pod_name_prefix=pod_name_prefix) + def helm_package(self) -> bool: """ Package source to Helm Chart package @@ -130,7 +136,6 @@ def helm_package(self) -> bool: def get_helm_json_output(self, command: str) -> Dict: output = HelmChartsAPI.run_helm_command(cmd=command) - print(output) # Remove debug wrong output new_output = [] for line in output.split('\n'): @@ -142,115 +147,9 @@ def get_helm_json_output(self, command: str) -> Dict: # print(output) return json.loads(''.join(new_output)) - def is_pod_finished(self, pod_suffix_name: str = "deploy") -> bool: - if not self.pod_json_data: - self.pod_json_data = self.oc_api.get_pod_status() - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - if self.pod_name_prefix not in pod_name: - continue - status = item["status"]["phase"] - print(f"is_pod_finished for {pod_suffix_name}: {pod_name} and status: {status}.") - if pod_suffix_name in pod_name and status != "Succeeded": - continue - if pod_suffix_name in pod_name and status == "Succeeded": - print(f"Pod with suffix {pod_suffix_name} is finished") - return True - return False - - def is_build_pod_present(self) -> bool: - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - print(f"is_build_pod_present: {pod_name}.") - if "build" in pod_name: - return True - return False - - def is_s2i_pod_running(self, pod_name_prefix: str = "") -> bool: - self.pod_name_prefix = pod_name_prefix - build_pod_finished = False - for count in range(180): - print(f"Cycle for checking s2i build pod status: {count}.") - self.pod_json_data = self.oc_api.get_pod_status() - if len(self.pod_json_data["items"]) == 0: - time.sleep(3) - continue - if not self.is_build_pod_present(): - print("Build pod is not present.") - time.sleep(3) - continue - print("Build pod is present.") - if not self.is_pod_finished(pod_suffix_name="build"): - print("Build pod is not yet finished") - time.sleep(3) - continue - print("Build pod is finished") - build_pod_finished = True - if not build_pod_finished: - print("Build pod was not finished.") - return False - for count in range(60): - if not self.is_pod_running(): - print("Running pod is not yet finished") - time.sleep(3) - continue - print("Pod is running") - return True - return False - - def get_pod_count(self) -> int: - count: int = 0 - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - print(f"get_pod_count: {pod_name} and {self.pod_name_prefix}.") - if self.pod_name_prefix not in pod_name: - continue - if "deploy" in pod_name: - continue - if "build" in pod_name: - continue - count += 1 - print(f"get_pod_count: {count}") - return count - - def is_pod_running(self, pod_name_prefix: str = "") -> bool: - for count in range(60): - print(f"Cycle for checking pod status: {count}.") - self.pod_json_data = self.oc_api.get_pod_status() - if pod_name_prefix == "" and self.pod_name_prefix == "": - print("Application pod name is not specified. Call: is_pod_running(pod_name_prefix=\"something\").") - return False - if pod_name_prefix != "": - self.pod_name_prefix = pod_name_prefix - # Only one running pod is allowed - if self.get_pod_count() != 1: - time.sleep(3) - continue - - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - if self.pod_name_prefix not in pod_name: - continue - status = item["status"]["phase"] - print(f"Pod Name: {pod_name} and status: {status}.") - if "deploy" in pod_name: - continue - if item["status"]["phase"] == "Running": - print(f"Pod with name {pod_name} is running {status}.") - output = OpenShiftAPI.run_oc_command( - f"logs {pod_name}", namespace=self.namespace, json_output=False - ) - print(output) - # Wait couple seconds for sure - time.sleep(3) - return True - time.sleep(3) - - return False - def check_helm_installation(self): # Let's check that pod is really running - output = OpenShiftAPI.run_oc_command("get all", json_output=False) + output = run_oc_command("get all", json_output=False) print(output) json_output = self.get_helm_json_output(command="list") for out in json_output: @@ -270,7 +169,9 @@ def is_helm_package_installed(self): return False def helm_uninstallation(self): - output = HelmChartsAPI.run_helm_command(f"uninstall {self.package_name} -n {self.namespace}", json_output=False) + output = HelmChartsAPI.run_helm_command( + f"uninstall {self.package_name} -n {self.oc_api.namespace}", json_output=False + ) print(output) def helm_installation(self, values: Dict = None): @@ -285,6 +186,8 @@ def helm_installation(self, values: Dict = None): json_output = self.get_helm_json_output( f"install {self.package_name} {self.get_full_tarball_path} {command_values}" ) + # Let's wait couple seconds, till it is not really imported + time.sleep(3) assert json_output["name"] == self.package_name assert json_output["chart"]["metadata"]["version"] == self.version assert json_output["info"]["status"] == "deployed" @@ -336,22 +239,23 @@ def test_helm_chart(self, expected_str: List[str]) -> bool: continue if self.check_test_output(output, expected_str=expected_str): return True - output = OpenShiftAPI.run_oc_command("status", json_output=False) + output = run_oc_command("status", json_output=False) print(output) - output = OpenShiftAPI.run_oc_command("get all", json_output=False) + output = run_oc_command("get all", json_output=False) print(output) return False def get_is_json(self): - output = OpenShiftAPI.run_oc_command( - "get is", namespace=self.namespace, return_output=True, ignore_error=True, shell=True + output = run_oc_command( + "get is", namespace=self.oc_api.namespace, return_output=True, ignore_error=True, shell=True ) + print(f"OC GET IS: {output}") return json.loads(output) def get_routes(self): - output = OpenShiftAPI.run_oc_command( + output = run_oc_command( "get route", - namespace=self.namespace, return_output=True, ignore_error=True, shell=True + namespace=self.oc_api.namespace, return_output=True, ignore_error=True, shell=True ) return json.loads(output) @@ -360,7 +264,7 @@ def get_route_name(self, route_name: str): if len(json_data["items"]) == 0: return None for item in json_data["items"]: - if item["metadata"]["namespace"] != self.namespace: + if item["metadata"]["namespace"] != self.oc_api.namespace: continue if item["metadata"]["name"] != route_name: continue diff --git a/container_ci_suite/openshift.py b/container_ci_suite/openshift.py index ace8945..0b59902 100644 --- a/container_ci_suite/openshift.py +++ b/container_ci_suite/openshift.py @@ -28,10 +28,12 @@ import subprocess from pathlib import Path -from subprocess import CalledProcessError from typing import Dict, Any, List from container_ci_suite.container import DockerCLIWrapper +from container_ci_suite.openshift_ops import OpenShiftOperations +from container_ci_suite.utils import run_oc_command + import container_ci_suite.utils as utils @@ -47,62 +49,45 @@ def __init__( delete_prj: bool = True, version: str = "" ): - self.namespace = namespace self.create_prj = create_prj self.delete_prj = delete_prj self.shared_cluster = utils.is_share_cluster() self.pod_name_prefix = pod_name_prefix self.pod_json_data: Dict = {} self.version = version - self.build_failed: bool = False self.shared_random_name = "" self.config_tenant_name = "core-services-ocp--config" - if self.namespace == "default": + self.openshift_ops = OpenShiftOperations(pod_name_prefix=pod_name_prefix) + if namespace == "default": self.create_project() else: self.namespace = namespace self.create_prj = False - @staticmethod - def run_oc_command( - cmd, json_output: bool = True, return_output: bool = True, ignore_error: bool = False, shell: bool = True, - namespace: str = "" - ): - """ - Run docker command: - """ - json_cmd = "-o json" if json_output else "" - namespace_cmd = f"-n {namespace}" if namespace != "" else "" - - return utils.run_command( - f"oc {cmd} {namespace_cmd} {json_cmd}", - return_output=return_output, - ignore_error=ignore_error, - shell=shell, - ) - def create_project(self): if self.create_prj: if self.shared_cluster: self.shared_random_name = f"{random.randrange(10000, 100000)}" self.namespace = f"core-services-ocp--{self.shared_random_name}" + self.openshift_ops.set_namespace(self.namespace) self.prepare_tenant_namespace() else: self.namespace = f"sclorg-{random.randrange(10000, 100000)}" - OpenShiftAPI.run_oc_command(f"new-project {self.namespace}", json_output=False, return_output=True) + self.openshift_ops.set_namespace(self.namespace) + run_oc_command(f"new-project {self.namespace}", json_output=False, return_output=True) else: - OpenShiftAPI.run_oc_command(f"project {self.namespace}", json_output=False) - return self.is_project_exits() + run_oc_command(f"project {self.namespace}", json_output=False) + return self.openshift_ops.is_project_exits() def prepare_tenant_namespace(self): json_flag = False self.login_to_shared_cluster() tenant_yaml_file = utils.save_tenant_namespace_yaml(project_name=self.shared_random_name) - OpenShiftAPI.run_oc_command(cmd=f"create -f {tenant_yaml_file}", json_output=json_flag, return_output=True) + run_oc_command(cmd=f"create -f {tenant_yaml_file}", json_output=json_flag, return_output=True) tenant_egress_file = utils.save_tenant_egress_yaml(project_name=self.shared_random_name) - OpenShiftAPI.run_oc_command(cmd=f"apply -f {tenant_egress_file}", json_output=False, return_output=True) + run_oc_command(cmd=f"apply -f {tenant_egress_file}", json_output=False, return_output=True) time.sleep(3) - OpenShiftAPI.run_oc_command( + run_oc_command( cmd=f"project {self.namespace}", json_output=json_flag, return_output=True @@ -110,12 +95,12 @@ def prepare_tenant_namespace(self): def delete_tenant_namespace(self): json_flag = False - namespace = OpenShiftAPI.run_oc_command(cmd="project -q", json_output=json_flag) + namespace = run_oc_command(cmd="project -q", json_output=json_flag) if namespace == self.config_tenant_name: print(f"Deleting tenant '{self.config_tenant_name}' is not allowed.") return - OpenShiftAPI.run_oc_command(f"project {self.config_tenant_name}", json_output=json_flag) - if OpenShiftAPI.run_oc_command( + run_oc_command(f"project {self.config_tenant_name}", json_output=json_flag) + if run_oc_command( f"delete tenantnamespace {self.shared_random_name}", json_output=json_flag ): @@ -131,231 +116,35 @@ def delete_project(self): if self.shared_cluster: self.delete_tenant_namespace() else: - OpenShiftAPI.run_oc_command("project default", json_output=False) - OpenShiftAPI.run_oc_command( + run_oc_command("project default", json_output=False) + run_oc_command( f"delete project {self.namespace} --grace-period=0 --force", json_output=False ) - def is_project_exits(self) -> bool: - output = OpenShiftAPI.run_oc_command("projects", json_output=False) - if self.namespace in output: - return True - return False - - def get_pod_count(self) -> int: - count: int = 0 - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - if self.pod_name_prefix not in pod_name: - continue - if "deploy" in pod_name: - continue - if "build" in pod_name: - continue - count += 1 - return count - - def is_pod_running(self, pod_name_prefix: str = "", loops: int = 180) -> bool: - print(f"Check for POD is running {pod_name_prefix}") - for count in range(loops): - print(".", sep="", end="") - self.pod_json_data = self.get_pod_status() - if pod_name_prefix == "" and self.pod_name_prefix == "": - print("\nApplication pod name is not specified. Call: is_pod_running(pod_name_prefix=\"something\").") - return False - if pod_name_prefix != "": - self.pod_name_prefix = pod_name_prefix - # Only one running pod is allowed - if self.get_pod_count() != 1: - time.sleep(3) - continue - - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - if self.pod_name_prefix not in pod_name: - continue - status = item["status"]["phase"] - if "deploy" in pod_name: - print(".", sep="", end="") - continue - if item["status"]["phase"] == "Running": - print(f"\nPod with name {pod_name} is running {status}.") - output = OpenShiftAPI.run_oc_command( - f"logs {pod_name}", namespace=self.namespace, json_output=False - ) - print(output) - # Wait couple seconds for sure - time.sleep(3) - return True - time.sleep(3) - return False - - def is_build_pod_finished(self, cycle_count: int = 180) -> bool: - """ - Function return information if build pod is finished. - The function waits for 180*3 seconds - """ - print("Check if build pod is finished") - for count in range(cycle_count): - print(".", sep="", end="") - self.pod_json_data = self.get_pod_status() - if len(self.pod_json_data["items"]) == 0: - time.sleep(3) - continue - if not self.is_build_pod_present(): - print(".", sep="", end="") - time.sleep(3) - continue - if not self.is_pod_finished(pod_suffix_name="build"): - print(".", sep="", end="") - if self.build_failed: - return False - time.sleep(3) - continue - print("\nBuild pod is finished") - return True - return False - - def is_s2i_pod_running(self, pod_name_prefix: str = "", cycle_count: int = 180) -> bool: - self.pod_name_prefix = pod_name_prefix - build_pod_finished = False - print("Check if S2I build pod is running") - for count in range(cycle_count): - print(".", sep="", end="") - self.pod_json_data = self.get_pod_status() - if len(self.pod_json_data["items"]) == 0: - time.sleep(3) - continue - if not self.is_build_pod_present(): - time.sleep(3) - continue - if not self.is_pod_finished(pod_suffix_name="build"): - time.sleep(3) - continue - build_pod_finished = True - print(f"\nBuild pod with name {pod_name_prefix} is finished.") - if not build_pod_finished: - print(f"\nBuild pod with name {pod_name_prefix} was not finished.") - return False - print("Check if S2I pod is running.") - for count in range(cycle_count): - print(".", sep="", end="") - if not self.is_pod_running(): - time.sleep(3) - continue - print("\nPod is running") - return True - return False - - @staticmethod - def get_raw_url_for_json(container: str, dir: str, filename: str, branch: str = "master") -> str: - RAW_SCL_JSON_URL: str = "https://raw.githubusercontent.com/sclorg/{container}/{branch}/{dir}/{filename}" - return RAW_SCL_JSON_URL.format(container=container, branch=branch, dir=dir, filename=filename) - - def get_pod_status(self) -> Dict: - # output = OpenShiftAPI.run_oc_command("get all", json_output=False) - # print(f"oc get all: {output}") - output = OpenShiftAPI.run_oc_command("get pods", json_output=True, namespace=self.namespace) - # print(f" oc get pods: {output}") - return json.loads(output) - - def is_build_pod_present(self) -> bool: - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - if "build" in pod_name: - return True - return False - - def print_get_status(self): - print("Print get all and status:") - print(OpenShiftAPI.run_oc_command("get all", namespace=self.namespace, json_output=False)) - print(OpenShiftAPI.run_oc_command("status", namespace=self.namespace, json_output=False)) - print(OpenShiftAPI.run_oc_command("status --suggest", namespace=self.namespace, json_output=False)) - - def print_pod_logs(self): - self.pod_json_data = self.get_pod_status() - print("Print all pod logs") - for item in self.pod_json_data["items"]: - pod_name = item["metadata"]["name"] - print(f"Logs from pod name {pod_name}:") - oc_logs = OpenShiftAPI.run_oc_command(f"logs pod/{pod_name}", json_output=False) - print(oc_logs) - - def is_pod_finished(self, pod_suffix_name: str = "deploy") -> bool: - if not self.pod_json_data: - self.pod_json_data = self.get_pod_status() - for item in self.pod_json_data["items"]: - print(".", sep="", end="") - pod_name = item["metadata"]["name"] - if self.pod_name_prefix not in pod_name: - print(".", sep="", end="") - continue - status = item["status"]["phase"] - if pod_suffix_name in pod_name and status == "Failed": - print(f"\nPod with {pod_suffix_name} finished with {status}. See logs.") - self.build_failed = True - self.print_pod_logs() - self.print_get_status() - return False - if pod_suffix_name in pod_name and status != "Succeeded": - print(".", sep="", end="") - continue - if pod_suffix_name in pod_name and status == "Succeeded": - print(f"\nPod with suffix {pod_suffix_name} is finished") - return True - return False - def run_command_in_pod(self, pod_name, command: str = "") -> str: - output = OpenShiftAPI.run_oc_command(f"exec {pod_name} -- \"{command}\"") + output = run_oc_command(f"exec {pod_name} -- \"{command}\"") print(output) return output - def oc_get_services(self, service_name): - output = OpenShiftAPI.run_oc_command(f"get svc/{service_name}", json_output=True, namespace=self.namespace) - json_output = json.loads(output) - print(json_output) - return json_output - - def get_service_ip(self, service_name) -> Any: - json_output = self.oc_get_services(service_name=service_name) - if "clusterIP" not in json_output["spec"]: - return None - if not json_output["spec"]["clusterIP"]: - return None - return json_output["spec"]["clusterIP"] - - def is_imagestream_exist(self, name: str): - try: - json_output = self.oc_get_is(name=name) - if json_output["kind"] == "ImageStream" and json_output["metadata"]["name"] == name: - return json_output - except CalledProcessError: - pass - return None - def import_is(self, path: str, name: str, skip_check=False): if not skip_check: - is_exists = self.is_imagestream_exist(name=name) + is_exists = self.openshift_ops.is_imagestream_exist(name=name) if is_exists: return is_exists - output = OpenShiftAPI.run_oc_command(f"create -f {path}", namespace=self.namespace) + output = run_oc_command(f"create -f {path}", namespace=self.namespace) # Let's wait 3 seconds till imagestreams are not uploaded time.sleep(3) return json.loads(output) def process_file(self, path: str): - output = OpenShiftAPI.run_oc_command(f"process -f {path}", namespace=self.namespace) + output = run_oc_command(f"process -f {path}", namespace=self.namespace) json_output = json.loads(output) print(json_output) return json_output - def oc_get_is(self, name: str): - output = OpenShiftAPI.run_oc_command(f"get is/{name}", namespace=self.namespace) - return json.loads(output) - def start_build(self, service_name: str, app_name: str = "") -> str: from_dir = utils.download_template(template_name=app_name) - output = OpenShiftAPI.run_oc_command(f"start-build {service_name} --from-dir={from_dir}", json_output=False) + output = run_oc_command(f"start-build {service_name} --from-dir={from_dir}", json_output=False) return output def login_to_shared_cluster(self): @@ -364,11 +153,11 @@ def login_to_shared_cluster(self): if not all([token, url]): print("Important variables 'SHARED_CLUSTER_TOKEN,SHARED_CLUSTER_URL' are missing.") return None - output = OpenShiftAPI.run_oc_command(f"login --token={token} --server={url}", json_output=False) + output = run_oc_command(f"login --token={token} --server={url}", json_output=False) print(output) - output = OpenShiftAPI.run_oc_command("version", json_output=False) + output = run_oc_command("version", json_output=False) print(output) - output = OpenShiftAPI.run_oc_command(f"project {self.config_tenant_name}", json_output=False) + output = run_oc_command(f"project {self.config_tenant_name}", json_output=False) print(output) @staticmethod @@ -401,7 +190,7 @@ def upload_image_to_external_registry(self, source_image: str, tagged_image: str cmd = f"podman push {output_name}" output = utils.run_command(cmd, ignore_error=False, return_output=True) print(f"Output from podman push command {output}") - ret = OpenShiftAPI.run_oc_command( + ret = run_oc_command( f"import-image {tagged_image} --from={output_name} --confirm", json_output=False, return_output=True ) print(ret) @@ -410,14 +199,14 @@ def upload_image_to_external_registry(self, source_image: str, tagged_image: str return True def docker_login_to_openshift(self) -> Any: - output = OpenShiftAPI.run_oc_command(cmd="get route default-route -n openshift-image-registry") + output = run_oc_command(cmd="get route default-route -n openshift-image-registry") jsou_output = json.loads(output) print(jsou_output["spec"]["host"]) if not jsou_output["spec"]["host"]: print("Default route does not exist. Install OpenShift 4 cluster properly and expose default route.") return None ocp4_register = jsou_output["spec"]["host"] - token_output = OpenShiftAPI.run_oc_command(cmd="whoami -t", json_output=False).strip() + token_output = run_oc_command(cmd="whoami -t", json_output=False).strip() cmd = f"docker login -u kubeadmin -p {token_output} {ocp4_register}" output = utils.run_command( cmd=cmd, @@ -459,25 +248,6 @@ def upload_image(self, source_image: str, tagged_image: str) -> bool: print(f"Upload_image push {output}") return True - def check_is_exists(self, is_name, version_to_check: str) -> bool: - """ - Function checks if it exists in OpenShift 4 environment - Exact version has to be the same - :return: True if tag was found - False if tag does not exist - """ - json_output = self.oc_get_is(name=is_name) - if "tags" not in json_output["spec"]: - return False - tag_found: bool = False - for tag in json_output["spec"]["tags"]: - if "name" not in tag: - continue - if version_to_check not in tag["name"]: - continue - tag_found = True - return tag_found - def create_new_app_with_template(self, name: str, template_json: str, template_args: Dict = None): """ Function creates a new application in OpenShift 4 environment @@ -492,7 +262,7 @@ def create_new_app_with_template(self, name: str, template_json: str, template_a args = [""] if template_args: args = [f"-p {key}={val}" for key, val in template_args] - output = OpenShiftAPI.run_oc_command( + output = run_oc_command( f"new-app {template_json} --name {name} -p NAMESPACE={self.namespace} {args}", json_output=False ) @@ -500,7 +270,7 @@ def create_new_app_with_template(self, name: str, template_json: str, template_a return output def get_route_url(self, routes_name: str) -> Any: - output = OpenShiftAPI.run_oc_command(f"get routes/{routes_name}") + output = run_oc_command(f"get routes/{routes_name}") json_output = json.loads(output) if not json_output["spec"]["host"]: return None @@ -508,65 +278,34 @@ def get_route_url(self, routes_name: str) -> Any: return None return json_output["spec"]["host"] - def is_pod_ready(self, cycle_count: int = 60) -> bool: - """ - Function checks if pod with specific name is really ready - """ - print("Check if pod is ready.") - for count in range(cycle_count): - print(".", end="") - json_data = self.get_pod_status() - if len(json_data["items"]) == 0: - time.sleep(3) - continue - if not self.is_pod_finished(pod_suffix_name=self.pod_name_prefix): - time.sleep(3) - continue - for item in json_data["items"]: - pod_name = item["metadata"]["name"] - status = item["status"]["phase"] - if "deploy" in pod_name: - continue - if item["status"]["phase"] == "Running": - print(f"\nPod with name {pod_name} is running {status}.") - output = OpenShiftAPI.run_oc_command( - f"logs {pod_name}", namespace=self.namespace, json_output=False - ) - print(output) - # Wait couple seconds for sure - time.sleep(10) - return True - time.sleep(3) - return False - def get_openshift_args(self, oc_args: List[str]) -> str: return " -p ".join(oc_args) def template_deployed(self, name_in_template: str = "") -> bool: - if not self.is_build_pod_finished(): + if not self.openshift_ops.is_build_pod_finished(): print("\nBuild pod does not finished in proper time") - self.print_get_status() + self.openshift_ops.print_get_status() return False - if not self.is_pod_running(pod_name_prefix=name_in_template): + if not self.openshift_ops.is_pod_running(pod_name_prefix=name_in_template): print("Pod is not running after time.") - self.print_get_status() + self.openshift_ops.print_get_status() return False return True def command_app_run(self, cmd: str, return_output: bool = True) -> str: cmd = f"exec command-app -- bash -c \"{cmd}\"" print(f"command_app_run: {cmd}") - cmd_out = self.run_oc_command( + cmd_out = run_oc_command( cmd=cmd, ignore_error=True, return_output=return_output, json_output=False ) return cmd_out def create_deploy_command_app(self, image_name: str = "registry.access.redhat.com/ubi8/ubi") -> bool: cmd_file = utils.save_command_yaml(image_name=image_name) - self.run_oc_command(f"create -f {cmd_file}") - if not self.is_pod_running(pod_name_prefix="command-app"): + run_oc_command(f"create -f {cmd_file}") + if not self.openshift_ops.is_pod_running(pod_name_prefix="command-app"): print("create_deploy_command_app: command-app pod is not running after time.") - self.print_get_status() + self.openshift_ops.print_get_status() return False output_cmd = self.command_app_run("echo $((11*11))") if "121" not in output_cmd: @@ -609,7 +348,7 @@ def deploy_s2i_app(self, image_name: str, app: str, context: str, service_name: app_param = utils.download_template(template_name=app) oc_cmd = f"new-app {tagged_image}~{app_param} --strategy=source --context-dir={context} --name={service_name}" try: - output = self.run_oc_command(f"{oc_cmd}", json_output=False) + output = run_oc_command(f"{oc_cmd}", json_output=False) print(output) except subprocess.CalledProcessError as cpe: print(cpe.output) @@ -642,7 +381,7 @@ def deploy_image_stream_template( oc_cmd = f"new-app -f {local_template} --name={app_name} -p NAMESPACE={self.namespace} {openshift_args}" print(f"Deploy template by command: oc {oc_cmd}") try: - output = self.run_oc_command(f"{oc_cmd}", json_output=False) + output = run_oc_command(f"{oc_cmd}", json_output=False) print(output) except subprocess.CalledProcessError: return False @@ -712,7 +451,7 @@ def deploy_template( oc_cmd = f"new-app {local_template} --name {name_in_template} -p NAMESPACE={self.namespace} {openshift_args}" print(f"Deploy template by command: oc {oc_cmd}") try: - output = self.run_oc_command(f"{oc_cmd}", json_output=False) + output = run_oc_command(f"{oc_cmd}", json_output=False) print(output) except subprocess.CalledProcessError: return False @@ -730,7 +469,7 @@ def check_command_internal( ) -> bool: if not self.create_deploy_command_app(image_name=image_name): return False - ip_address = self.get_service_ip(service_name=service_name) + ip_address = self.openshift_ops.get_service_ip(service_name=service_name) cmd = cmd.replace("", ip_address) for count in range(timeout): output = self.command_app_run(cmd=cmd, return_output=True) @@ -748,7 +487,7 @@ def check_response_inside_cluster( response_code: int = 200, max_tests: int = 20 ) -> bool: - ip_address = self.get_service_ip(service_name=name_in_template) + ip_address = self.openshift_ops.get_service_ip(service_name=name_in_template) url = f"{protocol}://{ip_address}:{port}/" print(f"URL address to get internal response is: {url}") if not self.create_deploy_command_app(): diff --git a/container_ci_suite/openshift_ops.py b/container_ci_suite/openshift_ops.py new file mode 100644 index 0000000..8b36a74 --- /dev/null +++ b/container_ci_suite/openshift_ops.py @@ -0,0 +1,289 @@ +# MIT License +# +# Copyright (c) 2018-2019 Red Hat, Inc. + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import json +import logging +import time + +from subprocess import CalledProcessError +from typing import Dict, Any + +from container_ci_suite.utils import run_oc_command + + +logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +class OpenShiftOperations: + namespace: str = "" + pod_json_data: Dict = {} + + def __init__(self, pod_name_prefix: str = ""): + self.pod_name_prefix = pod_name_prefix + self.build_failed: bool = False + + def set_namespace(self, namespace: str): + self.namespace = namespace + + def get_pod_status(self) -> Dict: + # output = OpenShiftAPI.run_oc_command("get all", json_output=False) + # print(f"oc get all: {output}") + output = run_oc_command("get pods", json_output=True, namespace=self.namespace) + # print(f" oc get pods: {output}") + return json.loads(output) + + def print_get_status(self): + print("Print get all and status:") + print(run_oc_command("get all", namespace=self.namespace, json_output=False)) + print(run_oc_command("status", namespace=self.namespace, json_output=False)) + print(run_oc_command("status --suggest", namespace=self.namespace, json_output=False)) + + def print_pod_logs(self): + self.pod_json_data = self.get_pod_status() + print("Print all pod logs") + for item in self.pod_json_data["items"]: + pod_name = item["metadata"]["name"] + print(f"Logs from pod name {pod_name}:") + oc_logs = run_oc_command(f"logs pod/{pod_name}", json_output=False) + print(oc_logs) + + def is_project_exits(self) -> bool: + output = run_oc_command("projects", json_output=False) + if self.namespace in output: + return True + return False + + def get_pod_count(self) -> int: + count: int = 0 + for item in self.pod_json_data["items"]: + pod_name = item["metadata"]["name"] + if self.pod_name_prefix not in pod_name: + continue + if "deploy" in pod_name: + continue + if "build" in pod_name: + continue + count += 1 + return count + + def get_logs(self, pod_name) -> str: + return run_oc_command( + f"logs {pod_name}", namespace=self.namespace, json_output=False + ) + + def is_pod_running(self, pod_name_prefix: str = "", loops: int = 180) -> bool: + print(f"Check for POD is running {pod_name_prefix}") + for count in range(loops): + print(".", sep="", end="") + self.pod_json_data = self.get_pod_status() + if pod_name_prefix == "" and self.pod_name_prefix == "": + print("\nApplication pod name is not specified. Call: is_pod_running(pod_name_prefix=\"something\").") + return False + if pod_name_prefix != "": + self.pod_name_prefix = pod_name_prefix + # Only one running pod is allowed + if self.get_pod_count() != 1: + time.sleep(3) + continue + + for item in self.pod_json_data["items"]: + pod_name = item["metadata"]["name"] + if self.pod_name_prefix not in pod_name: + continue + status = item["status"]["phase"] + if "deploy" in pod_name: + print(".", sep="", end="") + continue + if item["status"]["phase"] == "Running": + print(f"\nPod with name {pod_name} is running {status}.") + output = self.get_logs(pod_name=pod_name) + print(output) + # Wait couple seconds for sure + time.sleep(3) + return True + time.sleep(3) + return False + + def is_build_pod_present(self) -> bool: + for item in self.pod_json_data["items"]: + pod_name = item["metadata"]["name"] + if "build" in pod_name: + return True + return False + + def is_pod_finished(self, pod_suffix_name: str = "deploy") -> bool: + if not self.pod_json_data: + self.pod_json_data = self.get_pod_status() + for item in self.pod_json_data["items"]: + print(".", sep="", end="") + pod_name = item["metadata"]["name"] + if self.pod_name_prefix not in pod_name: + print(".", sep="", end="") + continue + status = item["status"]["phase"] + if pod_suffix_name in pod_name and status == "Failed": + print(f"\nPod with {pod_suffix_name} finished with {status}. See logs.") + self.build_failed = True + self.print_pod_logs() + self.print_get_status() + return False + if pod_suffix_name in pod_name and status != "Succeeded": + print(".", sep="", end="") + continue + if pod_suffix_name in pod_name and status == "Succeeded": + print(f"\nPod with suffix {pod_suffix_name} is finished") + return True + return False + + def is_build_pod_finished(self, cycle_count: int = 180) -> bool: + """ + Function return information if build pod is finished. + The function waits for 180*3 seconds + """ + print("Check if build pod is finished") + for count in range(cycle_count): + print(".", sep="", end="") + self.pod_json_data = self.get_pod_status() + if len(self.pod_json_data["items"]) == 0: + time.sleep(3) + continue + if not self.is_build_pod_present(): + print(".", sep="", end="") + time.sleep(3) + continue + if not self.is_pod_finished(pod_suffix_name="build"): + print(".", sep="", end="") + if self.build_failed: + return False + time.sleep(3) + continue + print("\nBuild pod is finished") + return True + return False + + def is_s2i_pod_running(self, pod_name_prefix: str = "", cycle_count: int = 180) -> bool: + self.pod_name_prefix = pod_name_prefix + build_pod_finished = False + print("Check if S2I build pod is running") + for count in range(cycle_count): + print(".", sep="", end="") + self.pod_json_data = self.get_pod_status() + if len(self.pod_json_data["items"]) == 0: + time.sleep(3) + continue + if not self.is_build_pod_present(): + time.sleep(3) + continue + if not self.is_pod_finished(pod_suffix_name="build"): + time.sleep(3) + continue + build_pod_finished = True + print(f"\nBuild pod with name {pod_name_prefix} is finished.") + if not build_pod_finished: + print(f"\nBuild pod with name {pod_name_prefix} was not finished.") + return False + print("Check if S2I pod is running.") + for count in range(cycle_count): + print(".", sep="", end="") + if not self.is_pod_running(): + time.sleep(3) + continue + print("\nPod is running") + return True + return False + + def oc_get_services(self, service_name): + output = run_oc_command(f"get svc/{service_name}", json_output=True, namespace=self.namespace) + json_output = json.loads(output) + print(json_output) + return json_output + + def get_service_ip(self, service_name) -> Any: + json_output = self.oc_get_services(service_name=service_name) + if "clusterIP" not in json_output["spec"]: + return None + if not json_output["spec"]["clusterIP"]: + return None + return json_output["spec"]["clusterIP"] + + def is_imagestream_exist(self, name: str): + try: + json_output = self.oc_get_is(name=name) + if json_output["kind"] == "ImageStream" and json_output["metadata"]["name"] == name: + return json_output + except CalledProcessError: + pass + return None + + def oc_get_is(self, name: str): + output = run_oc_command(f"get is/{name}", namespace=self.namespace) + return json.loads(output) + + def check_is_exists(self, is_name, version_to_check: str) -> bool: + """ + Function checks if it exists in OpenShift 4 environment + Exact version has to be the same + :return: True if tag was found + False if tag does not exist + """ + json_output = self.oc_get_is(name=is_name) + if "tags" not in json_output["spec"]: + return False + tag_found: bool = False + for tag in json_output["spec"]["tags"]: + if "name" not in tag: + continue + if version_to_check not in tag["name"]: + continue + tag_found = True + return tag_found + + def is_pod_ready(self, cycle_count: int = 60) -> bool: + """ + Function checks if pod with specific name is really ready + """ + print("Check if pod is ready.") + for count in range(cycle_count): + print(".", end="") + json_data = self.get_pod_status() + if len(json_data["items"]) == 0: + time.sleep(3) + continue + if not self.is_pod_finished(pod_suffix_name=self.pod_name_prefix): + time.sleep(3) + continue + for item in json_data["items"]: + pod_name = item["metadata"]["name"] + status = item["status"]["phase"] + if "deploy" in pod_name: + continue + if item["status"]["phase"] == "Running": + print(f"\nPod with name {pod_name} is running {status}.") + output = run_oc_command( + f"logs {pod_name}", namespace=self.namespace, json_output=False + ) + print(output) + # Wait couple seconds for sure + time.sleep(10) + return True + time.sleep(3) + return False diff --git a/container_ci_suite/utils.py b/container_ci_suite/utils.py index fb0868d..196c488 100644 --- a/container_ci_suite/utils.py +++ b/container_ci_suite/utils.py @@ -176,6 +176,24 @@ def run_command( raise cpe +def run_oc_command( + cmd, json_output: bool = True, return_output: bool = True, ignore_error: bool = False, shell: bool = True, + namespace: str = "" +): + """ + Run docker command: + """ + json_cmd = "-o json" if json_output else "" + namespace_cmd = f"-n {namespace}" if namespace != "" else "" + + return run_command( + f"oc {cmd} {namespace_cmd} {json_cmd}", + return_output=return_output, + ignore_error=ignore_error, + shell=shell + ) + + def get_response_request(url_address: str, expected_str: str, response_code: int = 200, max_tests: int = 3) -> bool: for count in range(max_tests): try: @@ -363,3 +381,8 @@ def is_share_cluster() -> bool: if file_shared_cluster in ["True", "true", "1", "yes", "Yes", "y", "Y"]: return True return False + + +def get_raw_url_for_json(container: str, dir: str, filename: str, branch: str = "master") -> str: + RAW_SCL_JSON_URL: str = "https://raw.githubusercontent.com/sclorg/{container}/{branch}/{dir}/{filename}" + return RAW_SCL_JSON_URL.format(container=container, branch=branch, dir=dir, filename=filename) diff --git a/tests/test_helm.py b/tests/test_helm.py index 7eb3208..f8445ea 100644 --- a/tests/test_helm.py +++ b/tests/test_helm.py @@ -38,15 +38,17 @@ class TestContainerCISuiteHelmCharts: def setup_method(self): flexmock(OpenShiftAPI).should_receive("create_project").and_return(True) self.helm_chart = HelmChartsAPI( - Path("foo_path"), package_name="postgresql-imagestreams", tarball_dir=test_dir, namespace="pgsql-13" + Path("foo_path"), package_name="postgresql-imagestreams", tarball_dir=test_dir ) + + self.helm_chart.oc_api.namespace = "something" + self.helm_chart.namespace = "something" self.helm_chart.set_version("0.0.1") def test_helm_api(self): assert self.helm_chart.path == Path("foo_path") assert self.helm_chart.package_name == "postgresql-imagestreams" assert self.helm_chart.version == "0.0.1" - assert self.helm_chart.namespace == "pgsql-13" @pytest.mark.parametrize( "tag,registry,expected_value", diff --git a/tests/test_openshift.py b/tests/test_openshift.py index 1a651ca..09a705d 100644 --- a/tests/test_openshift.py +++ b/tests/test_openshift.py @@ -27,6 +27,7 @@ from flexmock import flexmock from container_ci_suite.openshift import OpenShiftAPI +from container_ci_suite.openshift_ops import OpenShiftOperations from container_ci_suite.container import DockerCLIWrapper from container_ci_suite import utils @@ -34,34 +35,8 @@ class TestOpenShiftCISuite(object): def setup_method(self): self.oc_api = OpenShiftAPI(namespace="container-ci-suite-test") - - def test_check_is_version(self, oc_get_is_ruby_json): - flexmock(OpenShiftAPI).should_receive("oc_get_is").and_return(oc_get_is_ruby_json) - assert self.oc_api.check_is_exists("ruby", "2.5-ubi8") - assert not self.oc_api.check_is_exists("ruby", "333-ubi9") - - # TODO variant with outputs - def test_get_pod_count(self, oc_build_pod_finished_json): - self.oc_api.pod_json_data = oc_build_pod_finished_json - self.oc_api.pod_name_prefix = "python-311-testing" - assert self.oc_api.get_pod_count() == 1 - - def test_is_pod_running(self, oc_is_pod_running): - flexmock(OpenShiftAPI).should_receive("get_pod_status").and_return(oc_is_pod_running) - flexmock(OpenShiftAPI).should_receive("run_oc_command").once() - assert self.oc_api.is_pod_running(pod_name_prefix="python-311", loops=1) - - def test_build_pod_not_finished(self, oc_build_pod_not_finished_json): - flexmock(OpenShiftAPI).should_receive("get_pod_status").and_return(oc_build_pod_not_finished_json) - assert not self.oc_api.is_build_pod_finished(cycle_count=2) - - def test_build_pod_finished(self, oc_build_pod_finished_json): - flexmock(OpenShiftAPI).should_receive("get_pod_status").and_return(oc_build_pod_finished_json) - assert self.oc_api.is_build_pod_finished(cycle_count=2) - - def test_is_s2i_pod_running(self): - # self.oc_api.is_s2i_pod_running() - pass + self.oc_ops = OpenShiftOperations() + self.oc_ops.set_namespace(namespace="container-ci-suite-test") @pytest.mark.parametrize( "container,dir,filename,branch", @@ -72,7 +47,7 @@ def test_is_s2i_pod_running(self): ) def test_get_raw_url_for_json(self, container, dir, filename, branch): expected_output = f"https://raw.githubusercontent.com/sclorg/{container}/{branch}/{dir}/{filename}" - assert self.oc_api.get_raw_url_for_json( + assert utils.get_raw_url_for_json( container=container, dir=dir, filename=filename, branch=branch ) == expected_output @@ -87,18 +62,6 @@ def test_upload_image_login_failed(self): def test_upload_image_success(self): flexmock(DockerCLIWrapper).should_receive("docker_pull_image").and_return(True) - flexmock(OpenShiftAPI).should_receive("docker_login_to_openshift").and_return("defualt_registry") + flexmock(OpenShiftAPI).should_receive("docker_login_to_openshift").and_return("default_registry") flexmock(utils).should_receive("run_command").twice() assert self.oc_api.upload_image(source_image="foobar", tagged_image="foobar:latest") - - def test_get_pod_status(self): - # self.oc_api.get_pod_status() - pass - - def test_get_service_ip(self, get_svc_ip): - flexmock(OpenShiftAPI).should_receive("oc_get_services").and_return(get_svc_ip) - assert self.oc_api.get_service_ip("python-testing") == "172.30.224.217" - - def test_get_service_ip_not_available(self, get_svc_ip_empty): - flexmock(OpenShiftAPI).should_receive("oc_get_services").and_return(get_svc_ip_empty) - assert self.oc_api.get_service_ip("python-testing") is None diff --git a/tests/test_openshift_ops.py b/tests/test_openshift_ops.py new file mode 100644 index 0000000..63c0a04 --- /dev/null +++ b/tests/test_openshift_ops.py @@ -0,0 +1,74 @@ +#!/bin/env python3 + +# MIT License +# +# Copyright (c) 2018-2019 Red Hat, Inc. + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from flexmock import flexmock + +from container_ci_suite.openshift_ops import OpenShiftOperations + + +class TestOpenShiftOpsSuite(object): + def setup_method(self): + self.oc_ops = OpenShiftOperations() + self.oc_ops.set_namespace(namespace="container-ci-suite-test") + + def test_check_is_version(self, oc_get_is_ruby_json): + flexmock(OpenShiftOperations).should_receive("oc_get_is").and_return(oc_get_is_ruby_json) + assert self.oc_ops.check_is_exists("ruby", "2.5-ubi8") + assert not self.oc_ops.check_is_exists("ruby", "333-ubi9") + + # TODO variant with outputs + def test_get_pod_count(self, oc_build_pod_finished_json): + self.oc_ops.pod_json_data = oc_build_pod_finished_json + self.oc_ops.pod_name_prefix = "python-311-testing" + assert self.oc_ops.get_pod_count() == 1 + + def test_is_pod_running(self, oc_is_pod_running): + flexmock(OpenShiftOperations).should_receive("get_pod_status").and_return(oc_is_pod_running) + flexmock(OpenShiftOperations).should_receive("get_pod_count").and_return(1) + flexmock(OpenShiftOperations).should_receive("get_logs").and_return("something") + assert self.oc_ops.is_pod_running(pod_name_prefix="python-311", loops=2) + + def test_build_pod_not_finished(self, oc_build_pod_not_finished_json): + flexmock(OpenShiftOperations).should_receive("get_pod_status").and_return(oc_build_pod_not_finished_json) + assert not self.oc_ops.is_build_pod_finished(cycle_count=2) + + def test_build_pod_finished(self, oc_build_pod_finished_json): + flexmock(OpenShiftOperations).should_receive("get_pod_status").and_return(oc_build_pod_finished_json) + assert self.oc_ops.is_build_pod_finished(cycle_count=2) + + def test_get_service_ip(self, get_svc_ip): + flexmock(OpenShiftOperations).should_receive("oc_get_services").and_return(get_svc_ip) + assert self.oc_ops.get_service_ip("python-testing") == "172.30.224.217" + + def test_get_service_ip_not_available(self, get_svc_ip_empty): + flexmock(OpenShiftOperations).should_receive("oc_get_services").and_return(get_svc_ip_empty) + assert self.oc_ops.get_service_ip("python-testing") is None + + def test_get_pod_status(self): + # self.oc_api.get_pod_status() + pass + + def test_is_s2i_pod_running(self): + # self.oc_api.is_s2i_pod_running() + pass