Skip to content

Commit 0b11268

Browse files
authored
[DPE-3644] Landscape client (#388)
* Landscape client * Pass the cert * Juju 2 compatible * Update libs
1 parent ab05e36 commit 0b11268

File tree

3 files changed

+100
-20
lines changed

3 files changed

+100
-20
lines changed

lib/charms/tls_certificates_interface/v2/tls_certificates.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
277277
import logging
278278
import uuid
279279
from contextlib import suppress
280-
from datetime import datetime, timedelta
280+
from datetime import datetime, timedelta, timezone
281281
from ipaddress import IPv4Address
282282
from typing import Any, Dict, List, Literal, Optional, Union
283283

@@ -286,7 +286,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
286286
from cryptography.hazmat.primitives import hashes, serialization
287287
from cryptography.hazmat.primitives.asymmetric import rsa
288288
from cryptography.hazmat.primitives.serialization import pkcs12
289-
from jsonschema import exceptions, validate # type: ignore[import-untyped]
289+
from jsonschema import exceptions, validate
290290
from ops.charm import (
291291
CharmBase,
292292
CharmEvents,
@@ -307,7 +307,7 @@ def _on_all_certificates_invalidated(self, event: AllCertificatesInvalidatedEven
307307

308308
# Increment this PATCH version before using `charmcraft publish-lib` or reset
309309
# to 0 if you are raising the major API version
310-
LIBPATCH = 27
310+
LIBPATCH = 28
311311

312312
PYDEPS = ["cryptography", "jsonschema"]
313313

@@ -635,7 +635,9 @@ def _get_closest_future_time(
635635
datetime: expiry_notification_time if not in the past, expiry_time otherwise
636636
"""
637637
return (
638-
expiry_notification_time if datetime.utcnow() < expiry_notification_time else expiry_time
638+
expiry_notification_time
639+
if datetime.now(timezone.utc) < expiry_notification_time
640+
else expiry_time
639641
)
640642

641643

@@ -650,7 +652,7 @@ def _get_certificate_expiry_time(certificate: str) -> Optional[datetime]:
650652
"""
651653
try:
652654
certificate_object = x509.load_pem_x509_certificate(data=certificate.encode())
653-
return certificate_object.not_valid_after
655+
return certificate_object.not_valid_after_utc
654656
except ValueError:
655657
logger.warning("Could not load certificate.")
656658
return None
@@ -705,8 +707,8 @@ def generate_ca(
705707
.issuer_name(subject_name)
706708
.public_key(private_key_object.public_key()) # type: ignore[arg-type]
707709
.serial_number(x509.random_serial_number())
708-
.not_valid_before(datetime.utcnow())
709-
.not_valid_after(datetime.utcnow() + timedelta(days=validity))
710+
.not_valid_before(datetime.now(timezone.utc))
711+
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=validity))
710712
.add_extension(x509.SubjectKeyIdentifier(digest=subject_identifier), critical=False)
711713
.add_extension(
712714
x509.AuthorityKeyIdentifier(
@@ -860,8 +862,8 @@ def generate_certificate(
860862
.issuer_name(issuer)
861863
.public_key(csr_object.public_key())
862864
.serial_number(x509.random_serial_number())
863-
.not_valid_before(datetime.utcnow())
864-
.not_valid_after(datetime.utcnow() + timedelta(days=validity))
865+
.not_valid_before(datetime.now(timezone.utc))
866+
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=validity))
865867
)
866868
extensions = get_certificate_extensions(
867869
authority_key_identifier=ca_pem.extensions.get_extension_for_class(
@@ -1070,7 +1072,7 @@ class CertificatesRequirerCharmEvents(CharmEvents):
10701072
class TLSCertificatesProvidesV2(Object):
10711073
"""TLS certificates provider class to be instantiated by TLS certificates providers."""
10721074

1073-
on = CertificatesProviderCharmEvents()
1075+
on = CertificatesProviderCharmEvents() # type: ignore[reportAssignmentType]
10741076

10751077
def __init__(self, charm: CharmBase, relationship_name: str):
10761078
super().__init__(charm, relationship_name)
@@ -1481,7 +1483,7 @@ def certificate_issued_for_csr(
14811483
class TLSCertificatesRequiresV2(Object):
14821484
"""TLS certificates requirer class to be instantiated by TLS certificates requirers."""
14831485

1484-
on = CertificatesRequirerCharmEvents()
1486+
on = CertificatesRequirerCharmEvents() # type: ignore[reportAssignmentType]
14851487

14861488
def __init__(
14871489
self,
@@ -1708,7 +1710,7 @@ def get_expiring_certificates(self) -> List[Dict[str, str]]:
17081710
expiry_notification_time = expiry_time - timedelta(
17091711
hours=self.expiry_notification_time
17101712
)
1711-
if datetime.utcnow() > expiry_notification_time:
1713+
if datetime.now(timezone.utc) > expiry_notification_time:
17121714
final_list.append(cert)
17131715
return final_list
17141716

@@ -1891,7 +1893,7 @@ def _on_secret_expired(self, event: SecretExpiredEvent) -> None:
18911893
event.secret.remove_all_revisions()
18921894
return
18931895

1894-
if datetime.utcnow() < expiry_time:
1896+
if datetime.now(timezone.utc) < expiry_time:
18951897
logger.warning("Certificate almost expired")
18961898
self.on.certificate_expiring.emit(
18971899
certificate=certificate_dict["certificate"],
@@ -1937,7 +1939,7 @@ def _on_update_status(self, event: UpdateStatusEvent) -> None:
19371939
expiry_time = _get_certificate_expiry_time(certificate_dict["certificate"])
19381940
if not expiry_time:
19391941
continue
1940-
time_difference = expiry_time - datetime.utcnow()
1942+
time_difference = expiry_time - datetime.now(timezone.utc)
19411943
if time_difference.total_seconds() < 0:
19421944
logger.warning("Certificate is expired")
19431945
self.on.certificate_invalidated.emit(

tests/integration/test_backups.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,6 @@ async def cloud_configs(ops_test: OpsTest, github_secrets) -> None:
9090
bucket_object.delete()
9191

9292

93-
@pytest.mark.group(1)
94-
async def test_none() -> None:
95-
"""Empty test so that the suite will not fail if all tests are skippedi."""
96-
pass
97-
98-
9993
@pytest.mark.group(1)
10094
@pytest.mark.abort_on_fail
10195
async def test_backup(ops_test: OpsTest, cloud_configs: Tuple[Dict, Dict]) -> None:
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2021 Canonical Ltd.
3+
# See LICENSE file for licensing details.
4+
5+
import logging
6+
import subprocess
7+
from asyncio import gather
8+
from base64 import b64encode
9+
10+
import pytest
11+
from pytest_operator.plugin import OpsTest
12+
13+
from .helpers import (
14+
CHARM_SERIES,
15+
get_unit_address,
16+
scale_application,
17+
)
18+
19+
DATABASE_APP_NAME = "pg"
20+
LS_CLIENT = "landscape-client"
21+
22+
logger = logging.getLogger(__name__)
23+
24+
25+
@pytest.mark.group(1)
26+
@pytest.mark.abort_on_fail
27+
async def test_deploy(ops_test: OpsTest, charm: str, github_secrets):
28+
landscape_config = {
29+
"admin_email": "[email protected]",
30+
"admin_name": "Admin",
31+
"admin_password": "qweqwepoipoi",
32+
}
33+
await gather(
34+
ops_test.model.deploy(
35+
charm,
36+
application_name=DATABASE_APP_NAME,
37+
num_units=3,
38+
series=CHARM_SERIES,
39+
config={"profile": "testing"},
40+
),
41+
ops_test.model.deploy("landscape-scalable"),
42+
ops_test.model.deploy(LS_CLIENT, num_units=0),
43+
)
44+
await ops_test.model.applications["landscape-server"].set_config(landscape_config)
45+
46+
await ops_test.model.wait_for_idle(
47+
apps=["landscape-server", "haproxy", DATABASE_APP_NAME], status="active", timeout=3000
48+
)
49+
haproxy_unit = ops_test.model.applications["haproxy"].units[0]
50+
haproxy_addr = get_unit_address(ops_test, haproxy_unit.name)
51+
haproxy_host = haproxy_unit.machine.hostname
52+
cert = subprocess.check_output(
53+
["lxc", "exec", haproxy_host, "cat", "/var/lib/haproxy/selfsigned_ca.crt"]
54+
)
55+
ssl_public_key = f"base64:{b64encode(cert).decode()}"
56+
57+
await ops_test.model.applications[LS_CLIENT].set_config(
58+
{
59+
"account-name": "standalone",
60+
"ping-url": f"http://{haproxy_addr}/ping",
61+
"url": f"https://{haproxy_addr}/message-system",
62+
"ssl-public-key": ssl_public_key,
63+
}
64+
)
65+
await ops_test.model.relate(f"{DATABASE_APP_NAME}:juju-info", f"{LS_CLIENT}:container")
66+
await ops_test.model.wait_for_idle(apps=[LS_CLIENT, DATABASE_APP_NAME], status="active")
67+
68+
69+
@pytest.mark.group(1)
70+
async def test_scale_up(ops_test: OpsTest, github_secrets):
71+
await scale_application(ops_test, DATABASE_APP_NAME, 4)
72+
73+
await ops_test.model.wait_for_idle(
74+
apps=[LS_CLIENT, DATABASE_APP_NAME], status="active", timeout=1500
75+
)
76+
77+
78+
@pytest.mark.group(1)
79+
async def test_scale_down(ops_test: OpsTest, github_secrets):
80+
await scale_application(ops_test, DATABASE_APP_NAME, 3)
81+
82+
await ops_test.model.wait_for_idle(
83+
apps=[LS_CLIENT, DATABASE_APP_NAME], status="active", timeout=1500
84+
)

0 commit comments

Comments
 (0)