|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright 2024 Canonical Ltd. |
| 3 | +# See LICENSE file for licensing details. |
| 4 | +import dataclasses |
| 5 | +import json |
| 6 | +import logging |
| 7 | +import os |
| 8 | +import socket |
| 9 | +import subprocess |
| 10 | +import time |
| 11 | + |
| 12 | +import boto3 |
| 13 | +import botocore.exceptions |
| 14 | +import pytest |
| 15 | +from pytest_operator.plugin import OpsTest |
| 16 | + |
| 17 | +from . import architecture, markers |
| 18 | +from .helpers import ( |
| 19 | + backup_operations, |
| 20 | +) |
| 21 | +from .juju_ import juju_major_version |
| 22 | + |
| 23 | +logger = logging.getLogger(__name__) |
| 24 | + |
| 25 | +S3_INTEGRATOR_APP_NAME = "s3-integrator" |
| 26 | +if juju_major_version < 3: |
| 27 | + tls_certificates_app_name = "tls-certificates-operator" |
| 28 | + if architecture.architecture == "arm64": |
| 29 | + tls_channel = "legacy/edge" |
| 30 | + else: |
| 31 | + tls_channel = "legacy/stable" |
| 32 | + tls_config = {"generate-self-signed-certificates": "true", "ca-common-name": "Test CA"} |
| 33 | +else: |
| 34 | + tls_certificates_app_name = "self-signed-certificates" |
| 35 | + if architecture.architecture == "arm64": |
| 36 | + tls_channel = "latest/edge" |
| 37 | + else: |
| 38 | + tls_channel = "latest/stable" |
| 39 | + tls_config = {"ca-common-name": "Test CA"} |
| 40 | + |
| 41 | +backup_id, value_before_backup, value_after_backup = "", None, None |
| 42 | + |
| 43 | + |
| 44 | +@dataclasses.dataclass(frozen=True) |
| 45 | +class ConnectionInformation: |
| 46 | + access_key_id: str |
| 47 | + secret_access_key: str |
| 48 | + bucket: str |
| 49 | + |
| 50 | + |
| 51 | +@pytest.fixture(scope="session") |
| 52 | +def microceph(): |
| 53 | + if not os.environ.get("CI") == "true": |
| 54 | + raise Exception("Not running on CI. Skipping microceph installation") |
| 55 | + logger.info("Setting up TLS certificates") |
| 56 | + subprocess.run(["openssl", "genrsa", "-out", "./ca.key", "2048"], check=True) |
| 57 | + subprocess.run( |
| 58 | + [ |
| 59 | + "openssl", |
| 60 | + "req", |
| 61 | + "-x509", |
| 62 | + "-new", |
| 63 | + "-nodes", |
| 64 | + "-key", |
| 65 | + "./ca.key", |
| 66 | + "-days", |
| 67 | + "1024", |
| 68 | + "-out", |
| 69 | + "./ca.crt", |
| 70 | + "-outform", |
| 71 | + "PEM", |
| 72 | + "-subj", |
| 73 | + "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com", |
| 74 | + ], |
| 75 | + check=True, |
| 76 | + ) |
| 77 | + subprocess.run(["openssl", "genrsa", "-out", "./server.key", "2048"], check=True) |
| 78 | + subprocess.run( |
| 79 | + [ |
| 80 | + "openssl", |
| 81 | + "req", |
| 82 | + "-new", |
| 83 | + "-key", |
| 84 | + "./server.key", |
| 85 | + "-out", |
| 86 | + "./server.csr", |
| 87 | + "-subj", |
| 88 | + "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com", |
| 89 | + ], |
| 90 | + check=True, |
| 91 | + ) |
| 92 | + host_ip = socket.gethostbyname(socket.gethostname()) |
| 93 | + subprocess.run( |
| 94 | + f'echo "subjectAltName = IP:{host_ip}" > ./extfile.cnf', |
| 95 | + shell=True, |
| 96 | + check=True, |
| 97 | + ) |
| 98 | + subprocess.run( |
| 99 | + [ |
| 100 | + "openssl", |
| 101 | + "x509", |
| 102 | + "-req", |
| 103 | + "-in", |
| 104 | + "./server.csr", |
| 105 | + "-CA", |
| 106 | + "./ca.crt", |
| 107 | + "-CAkey", |
| 108 | + "./ca.key", |
| 109 | + "-CAcreateserial", |
| 110 | + "-out", |
| 111 | + "./server.crt", |
| 112 | + "-days", |
| 113 | + "365", |
| 114 | + "-extfile", |
| 115 | + "./extfile.cnf", |
| 116 | + ], |
| 117 | + check=True, |
| 118 | + ) |
| 119 | + |
| 120 | + logger.info("Setting up microceph") |
| 121 | + subprocess.run(["sudo", "snap", "install", "microceph", "--revision", "1169"], check=True) |
| 122 | + subprocess.run(["sudo", "microceph", "cluster", "bootstrap"], check=True) |
| 123 | + subprocess.run(["sudo", "microceph", "disk", "add", "loop,1G,3"], check=True) |
| 124 | + subprocess.run( |
| 125 | + 'sudo microceph enable rgw --ssl-certificate="$(sudo base64 -w0 ./server.crt)" --ssl-private-key="$(sudo base64 -w0 ./server.key)"', |
| 126 | + shell=True, |
| 127 | + check=True, |
| 128 | + ) |
| 129 | + output = subprocess.run( |
| 130 | + [ |
| 131 | + "sudo", |
| 132 | + "microceph.radosgw-admin", |
| 133 | + "user", |
| 134 | + "create", |
| 135 | + "--uid", |
| 136 | + "test", |
| 137 | + "--display-name", |
| 138 | + "test", |
| 139 | + ], |
| 140 | + capture_output=True, |
| 141 | + check=True, |
| 142 | + encoding="utf-8", |
| 143 | + ).stdout |
| 144 | + key = json.loads(output)["keys"][0] |
| 145 | + key_id = key["access_key"] |
| 146 | + secret_key = key["secret_key"] |
| 147 | + logger.info("Creating microceph bucket") |
| 148 | + for attempt in range(3): |
| 149 | + try: |
| 150 | + boto3.client( |
| 151 | + "s3", |
| 152 | + endpoint_url=f"https://{host_ip}", |
| 153 | + aws_access_key_id=key_id, |
| 154 | + aws_secret_access_key=secret_key, |
| 155 | + verify="./ca.crt", |
| 156 | + ).create_bucket(Bucket=_BUCKET) |
| 157 | + except botocore.exceptions.EndpointConnectionError: |
| 158 | + if attempt == 2: |
| 159 | + raise |
| 160 | + # microceph is not ready yet |
| 161 | + logger.info("Unable to connect to microceph via S3. Retrying") |
| 162 | + time.sleep(1) |
| 163 | + else: |
| 164 | + break |
| 165 | + logger.info("Set up microceph") |
| 166 | + return ConnectionInformation(key_id, secret_key, _BUCKET) |
| 167 | + |
| 168 | + |
| 169 | +_BUCKET = "testbucket" |
| 170 | +logger = logging.getLogger(__name__) |
| 171 | + |
| 172 | + |
| 173 | +@pytest.fixture(scope="session") |
| 174 | +def cloud_credentials(microceph: ConnectionInformation) -> dict[str, str]: |
| 175 | + """Read cloud credentials.""" |
| 176 | + return { |
| 177 | + "access-key": microceph.access_key_id, |
| 178 | + "secret-key": microceph.secret_access_key, |
| 179 | + } |
| 180 | + |
| 181 | + |
| 182 | +@pytest.fixture(scope="session") |
| 183 | +def cloud_configs(microceph: ConnectionInformation): |
| 184 | + host_ip = socket.gethostbyname(socket.gethostname()) |
| 185 | + result = subprocess.run( |
| 186 | + "sudo base64 -w0 ./ca.crt", shell=True, check=True, stdout=subprocess.PIPE, text=True |
| 187 | + ) |
| 188 | + base64_output = result.stdout |
| 189 | + return { |
| 190 | + "endpoint": f"https://{host_ip}", |
| 191 | + "bucket": microceph.bucket, |
| 192 | + "path": "/pg", |
| 193 | + "region": "", |
| 194 | + "s3-uri-style": "path", |
| 195 | + "tls-ca-chain": f"{base64_output}", |
| 196 | + } |
| 197 | + |
| 198 | + |
| 199 | +@pytest.mark.group("ceph") |
| 200 | +@markers.amd64_only |
| 201 | +async def test_backup_ceph(ops_test: OpsTest, cloud_configs, cloud_credentials, charm) -> None: |
| 202 | + """Build and deploy two units of PostgreSQL in microceph, test backup and restore actions.""" |
| 203 | + await backup_operations( |
| 204 | + ops_test, |
| 205 | + S3_INTEGRATOR_APP_NAME, |
| 206 | + tls_certificates_app_name, |
| 207 | + tls_config, |
| 208 | + tls_channel, |
| 209 | + cloud_credentials, |
| 210 | + "ceph", |
| 211 | + cloud_configs, |
| 212 | + charm, |
| 213 | + ) |
0 commit comments