| Giorgi Lekveishvili | 285ab62 | 2023-11-22 13:50:45 +0400 | [diff] [blame^] | 1 | # Copyright (C) 2022 The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | import os.path |
| 16 | |
| 17 | from copy import deepcopy |
| 18 | from pathlib import Path |
| 19 | |
| 20 | import pytest |
| 21 | import yaml |
| 22 | |
| 23 | import pygit2 as git |
| 24 | import chromedriver_autoinstaller |
| 25 | from kubernetes import client |
| 26 | from selenium import webdriver |
| 27 | from selenium.webdriver.common.by import By |
| 28 | |
| 29 | from .abstract_deployment import AbstractDeployment |
| 30 | |
| 31 | |
| 32 | class TimeOutException(Exception): |
| 33 | """Exception to be raised, if some action does not finish in time.""" |
| 34 | |
| 35 | |
| 36 | def dict_to_git_config(config_dict): |
| 37 | config = "" |
| 38 | for section, options in config_dict.items(): |
| 39 | config += f"[{section}]\n" |
| 40 | for key, value in options.items(): |
| 41 | if isinstance(value, bool): |
| 42 | value = "true" if value else "false" |
| 43 | elif isinstance(value, list): |
| 44 | for opt in value: |
| 45 | config += f" {key} = {opt}\n" |
| 46 | continue |
| 47 | config += f" {key} = {value}\n" |
| 48 | return config |
| 49 | |
| 50 | |
| 51 | GERRIT_STARTUP_TIMEOUT = 240 |
| 52 | |
| 53 | DEFAULT_GERRIT_CONFIG = { |
| 54 | "auth": { |
| 55 | "type": "LDAP", |
| 56 | }, |
| 57 | "container": { |
| 58 | "user": "gerrit", |
| 59 | "javaHome": "/usr/lib/jvm/java-11-openjdk", |
| 60 | "javaOptions": [ |
| 61 | "-Djavax.net.ssl.trustStore=/var/gerrit/etc/keystore", |
| 62 | "-Xms200m", |
| 63 | "-Xmx4g", |
| 64 | ], |
| 65 | }, |
| 66 | "gerrit": { |
| 67 | "basePath": "git", |
| 68 | "canonicalWebUrl": "http://example.com/", |
| 69 | "serverId": "gerrit-1", |
| 70 | }, |
| 71 | "httpd": { |
| 72 | "listenUrl": "proxy-https://*:8080/", |
| 73 | "requestLog": True, |
| 74 | "gracefulStopTimeout": "1m", |
| 75 | }, |
| 76 | "index": {"type": "LUCENE", "onlineUpgrade": False}, |
| 77 | "ldap": { |
| 78 | "server": "ldap://openldap.openldap.svc.cluster.local:1389", |
| 79 | "accountbase": "dc=example,dc=org", |
| 80 | "username": "cn=admin,dc=example,dc=org", |
| 81 | }, |
| 82 | "sshd": {"listenAddress": "off"}, |
| 83 | } |
| 84 | |
| 85 | DEFAULT_VALUES = { |
| 86 | "gitRepositoryStorage": {"externalPVC": {"use": True, "name": "repo-storage"}}, |
| 87 | "gitGC": {"logging": {"persistence": {"enabled": False}}}, |
| 88 | "gerrit": { |
| 89 | "etc": {"config": {"gerrit.config": dict_to_git_config(DEFAULT_GERRIT_CONFIG)}} |
| 90 | }, |
| 91 | } |
| 92 | |
| 93 | |
| 94 | # pylint: disable=R0902 |
| 95 | class GerritDeployment(AbstractDeployment): |
| 96 | def __init__( |
| 97 | self, |
| 98 | tmp_dir, |
| 99 | cluster, |
| 100 | storageclass, |
| 101 | container_registry, |
| 102 | container_org, |
| 103 | container_version, |
| 104 | ingress_url, |
| 105 | ldap_admin_credentials, |
| 106 | ldap_credentials, |
| 107 | ): |
| 108 | super().__init__(tmp_dir) |
| 109 | self.cluster = cluster |
| 110 | self.storageclass = storageclass |
| 111 | self.ldap_credentials = ldap_credentials |
| 112 | |
| 113 | self.chart_name = "gerrit-" + self.namespace |
| 114 | self.chart_path = os.path.join( |
| 115 | # pylint: disable=E1101 |
| 116 | Path(git.discover_repository(os.path.realpath(__file__))).parent.absolute(), |
| 117 | "helm-charts", |
| 118 | "gerrit", |
| 119 | ) |
| 120 | |
| 121 | self.gerrit_config = deepcopy(DEFAULT_GERRIT_CONFIG) |
| 122 | self.chart_opts = deepcopy(DEFAULT_VALUES) |
| 123 | |
| 124 | self._configure_container_images( |
| 125 | container_registry, container_org, container_version |
| 126 | ) |
| 127 | self.hostname = f"{self.namespace}.{ingress_url}" |
| 128 | self._configure_ingress() |
| 129 | self.set_gerrit_config_value( |
| 130 | "gerrit", "canonicalWebUrl", f"http://{self.hostname}" |
| 131 | ) |
| 132 | # pylint: disable=W1401 |
| 133 | self.set_helm_value( |
| 134 | "gerrit.etc.secret.secure\.config", |
| 135 | dict_to_git_config({"ldap": {"password": ldap_admin_credentials[1]}}), |
| 136 | ) |
| 137 | |
| 138 | def install(self, wait=True): |
| 139 | if self.cluster.helm.is_installed(self.namespace, self.chart_name): |
| 140 | self.update() |
| 141 | return |
| 142 | |
| 143 | with open(self.values_file, "w", encoding="UTF-8") as f: |
| 144 | yaml.dump(self.chart_opts, f) |
| 145 | |
| 146 | self.cluster.create_namespace(self.namespace) |
| 147 | self._create_pvc() |
| 148 | |
| 149 | self.cluster.helm.install( |
| 150 | self.chart_path, |
| 151 | self.chart_name, |
| 152 | values_file=self.values_file, |
| 153 | fail_on_err=True, |
| 154 | namespace=self.namespace, |
| 155 | wait=wait, |
| 156 | ) |
| 157 | |
| 158 | def create_admin_account(self): |
| 159 | self.wait_until_ready() |
| 160 | chromedriver_autoinstaller.install() |
| 161 | options = webdriver.ChromeOptions() |
| 162 | options.add_argument("--headless") |
| 163 | options.add_argument("--no-sandbox") |
| 164 | options.add_argument("--ignore-certificate-errors") |
| 165 | options.set_capability("acceptInsecureCerts", True) |
| 166 | driver = webdriver.Chrome( |
| 167 | options=options, |
| 168 | ) |
| 169 | driver.get(f"http://{self.hostname}/login") |
| 170 | user_input = driver.find_element(By.ID, "f_user") |
| 171 | user_input.send_keys("gerrit-admin") |
| 172 | |
| 173 | pwd_input = driver.find_element(By.ID, "f_pass") |
| 174 | pwd_input.send_keys(self.ldap_credentials["gerrit-admin"]) |
| 175 | |
| 176 | submit_btn = driver.find_element(By.ID, "b_signin") |
| 177 | submit_btn.click() |
| 178 | |
| 179 | driver.close() |
| 180 | |
| 181 | def update(self): |
| 182 | with open(self.values_file, "w", encoding="UTF-8") as f: |
| 183 | yaml.dump(self.chart_opts, f) |
| 184 | |
| 185 | self.cluster.helm.upgrade( |
| 186 | self.chart_path, |
| 187 | self.chart_name, |
| 188 | values_file=self.values_file, |
| 189 | fail_on_err=True, |
| 190 | namespace=self.namespace, |
| 191 | ) |
| 192 | |
| 193 | def wait_until_ready(self): |
| 194 | pod_labels = f"app=gerrit,release={self.chart_name}" |
| 195 | finished_in_time = self._wait_for_pod_readiness( |
| 196 | pod_labels, timeout=GERRIT_STARTUP_TIMEOUT |
| 197 | ) |
| 198 | |
| 199 | if not finished_in_time: |
| 200 | raise TimeOutException( |
| 201 | f"Gerrit pod was not ready in time ({GERRIT_STARTUP_TIMEOUT} s)." |
| 202 | ) |
| 203 | |
| 204 | def uninstall(self): |
| 205 | self.cluster.helm.delete(self.chart_name, namespace=self.namespace) |
| 206 | self.cluster.delete_namespace(self.namespace) |
| 207 | |
| 208 | def set_gerrit_config_value(self, section, key, value): |
| 209 | if isinstance(self.gerrit_config[section][key], list): |
| 210 | self.gerrit_config[section][key].append(value) |
| 211 | else: |
| 212 | self.gerrit_config[section][key] = value |
| 213 | # pylint: disable=W1401 |
| 214 | self.set_helm_value( |
| 215 | "gerrit.etc.config.gerrit\.config", dict_to_git_config(self.gerrit_config) |
| 216 | ) |
| 217 | |
| 218 | def _set_values_file(self): |
| 219 | return os.path.join(self.tmp_dir, "values.yaml") |
| 220 | |
| 221 | def _configure_container_images( |
| 222 | self, container_registry, container_org, container_version |
| 223 | ): |
| 224 | self.set_helm_value("images.registry.name", container_registry) |
| 225 | self.set_helm_value("gitGC.image", f"{container_org}/git-gc") |
| 226 | self.set_helm_value("gerrit.images.gerritInit", f"{container_org}/gerrit-init") |
| 227 | self.set_helm_value("gerrit.images.gerrit", f"{container_org}/gerrit") |
| 228 | self.set_helm_value("images.version", container_version) |
| 229 | |
| 230 | def _configure_ingress(self): |
| 231 | self.set_helm_value("ingress.enabled", True) |
| 232 | self.set_helm_value("ingress.host", self.hostname) |
| 233 | |
| 234 | def _create_pvc(self): |
| 235 | core_v1 = client.CoreV1Api() |
| 236 | core_v1.create_namespaced_persistent_volume_claim( |
| 237 | self.namespace, |
| 238 | body=client.V1PersistentVolumeClaim( |
| 239 | kind="PersistentVolumeClaim", |
| 240 | api_version="v1", |
| 241 | metadata=client.V1ObjectMeta(name="repo-storage"), |
| 242 | spec=client.V1PersistentVolumeClaimSpec( |
| 243 | access_modes=["ReadWriteMany"], |
| 244 | storage_class_name=self.storageclass, |
| 245 | resources=client.V1ResourceRequirements( |
| 246 | requests={"storage": "1Gi"} |
| 247 | ), |
| 248 | ), |
| 249 | ), |
| 250 | ) |
| 251 | |
| 252 | |
| 253 | @pytest.fixture(scope="class") |
| 254 | def gerrit_deployment( |
| 255 | request, tmp_path_factory, test_cluster, ldap_admin_credentials, ldap_credentials |
| 256 | ): |
| 257 | deployment = GerritDeployment( |
| 258 | tmp_path_factory.mktemp("gerrit_deployment"), |
| 259 | test_cluster, |
| 260 | request.config.getoption("--rwm-storageclass").lower(), |
| 261 | request.config.getoption("--registry"), |
| 262 | request.config.getoption("--org"), |
| 263 | request.config.getoption("--tag"), |
| 264 | request.config.getoption("--ingress-url"), |
| 265 | ldap_admin_credentials, |
| 266 | ldap_credentials, |
| 267 | ) |
| 268 | |
| 269 | yield deployment |
| 270 | |
| 271 | deployment.uninstall() |
| 272 | |
| 273 | |
| 274 | @pytest.fixture(scope="class") |
| 275 | def default_gerrit_deployment(gerrit_deployment): |
| 276 | gerrit_deployment.install() |
| 277 | gerrit_deployment.create_admin_account() |
| 278 | |
| 279 | yield gerrit_deployment |