Skip to content

Commit 4310dc9

Browse files
authored
Add test_smoke test re-open (#454)
* Smoke testing. Garbage ignorance. No resources conflicts. * Smoke testing. Garbage ignorance. No resources conflicts. * Smoke testing. Garbage ignorance. No resources conflicts. * Smoke testing. Garbage ignorance. No resources conflicts. * Smoke testing. Garbage ignorance. No resources conflicts. * Smoke testing. Garbage ignorance. No resources conflicts.
1 parent 448a1a6 commit 4310dc9

File tree

2 files changed

+322
-0
lines changed

2 files changed

+322
-0
lines changed

tests/integration/ha_tests/helpers.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright 2022 Canonical Ltd.
22
# See LICENSE file for licensing details.
3+
import json
34
import logging
45
import os
56
import random
@@ -24,6 +25,7 @@
2425
from ..helpers import (
2526
APPLICATION_NAME,
2627
db_connect,
28+
execute_query_on_unit,
2729
get_patroni_cluster,
2830
get_unit_address,
2931
run_command_on_unit,
@@ -862,3 +864,102 @@ async def reused_full_cluster_recovery_storage(ops_test: OpsTest, unit_name) ->
862864
"/var/snap/charmed-postgresql/common/var/log/patroni/patroni.log*",
863865
)
864866
return True
867+
868+
869+
async def is_storage_exists(ops_test: OpsTest, storage_id: str) -> bool:
870+
"""Returns True if storage exists by provided storage ID."""
871+
complete_command = [
872+
"show-storage",
873+
"-m",
874+
f"{ops_test.controller_name}:{ops_test.model.info.name}",
875+
storage_id,
876+
"--format=json",
877+
]
878+
return_code, stdout, _ = await ops_test.juju(*complete_command)
879+
if return_code != 0:
880+
if return_code == 1:
881+
return storage_id in stdout
882+
raise Exception(
883+
"Expected command %s to succeed instead it failed: %s with code: ",
884+
complete_command,
885+
stdout,
886+
return_code,
887+
)
888+
return storage_id in str(stdout)
889+
890+
891+
async def create_db(ops_test: OpsTest, app: str, db: str) -> None:
892+
"""Creates database with specified name."""
893+
unit = ops_test.model.applications[app].units[0]
894+
unit_address = await unit.get_public_address()
895+
password = await get_password(ops_test, app)
896+
897+
conn = db_connect(unit_address, password)
898+
conn.autocommit = True
899+
cursor = conn.cursor()
900+
cursor.execute(f"CREATE DATABASE {db};")
901+
cursor.close()
902+
conn.close()
903+
904+
905+
async def check_db(ops_test: OpsTest, app: str, db: str) -> bool:
906+
"""Returns True if database with specified name already exists."""
907+
unit = ops_test.model.applications[app].units[0]
908+
unit_address = await unit.get_public_address()
909+
password = await get_password(ops_test, app)
910+
911+
assert password is not None
912+
913+
query = await execute_query_on_unit(
914+
unit_address,
915+
password,
916+
f"select datname from pg_catalog.pg_database where datname = '{db}';",
917+
)
918+
919+
if "ERROR" in query:
920+
raise Exception(f"Database check is failed with postgresql err: {query}")
921+
922+
return db in query
923+
924+
925+
async def get_any_deatached_storage(ops_test: OpsTest) -> str:
926+
"""Returns any of the current available deatached storage."""
927+
return_code, storages_list, stderr = await ops_test.juju(
928+
"storage", "-m", f"{ops_test.controller_name}:{ops_test.model.info.name}", "--format=json"
929+
)
930+
if return_code != 0:
931+
raise Exception(f"failed to get storages info with error: {stderr}")
932+
933+
parsed_storages_list = json.loads(storages_list)
934+
for storage_name, storage in parsed_storages_list["storage"].items():
935+
if (str(storage["status"]["current"]) == "detached") and (str(storage["life"] == "alive")):
936+
return storage_name
937+
938+
raise Exception("failed to get deatached storage")
939+
940+
941+
async def check_password_auth(ops_test: OpsTest, unit_name: str) -> bool:
942+
"""Checks if "operator" password is valid for current postgresql db."""
943+
stdout = await run_command_on_unit(
944+
ops_test,
945+
unit_name,
946+
"""grep -E 'password authentication failed for user' /var/snap/charmed-postgresql/common/var/log/postgresql/postgresql*""",
947+
)
948+
return 'password authentication failed for user "operator"' not in stdout
949+
950+
951+
async def remove_unit_force(ops_test: OpsTest, unit_name: str):
952+
"""Removes unit with --force --no-wait."""
953+
app_name = unit_name.split("/")[0]
954+
complete_command = ["remove-unit", f"{unit_name}", "--force", "--no-wait", "--no-prompt"]
955+
return_code, stdout, _ = await ops_test.juju(*complete_command)
956+
if return_code != 0:
957+
raise Exception(
958+
"Expected command %s to succeed instead it failed: %s with code: ",
959+
complete_command,
960+
stdout,
961+
return_code,
962+
)
963+
964+
for unit in ops_test.model.applications[app_name].units:
965+
assert unit != unit_name
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2021 Canonical Ltd.
3+
# See LICENSE file for licensing details.
4+
5+
import logging
6+
from asyncio import TimeoutError
7+
8+
import pytest
9+
from juju import tag
10+
from pytest_operator.plugin import OpsTest
11+
from tenacity import Retrying, stop_after_delay, wait_fixed
12+
13+
from ..helpers import (
14+
APPLICATION_NAME,
15+
CHARM_SERIES,
16+
)
17+
from ..juju_ import juju_major_version
18+
from .helpers import (
19+
add_unit_with_storage,
20+
check_db,
21+
check_password_auth,
22+
create_db,
23+
get_any_deatached_storage,
24+
is_postgresql_ready,
25+
is_storage_exists,
26+
remove_unit_force,
27+
storage_id,
28+
)
29+
30+
TEST_DATABASE_NAME = "test_database"
31+
DUP_APPLICATION_NAME = "postgres-test-dup"
32+
33+
logger = logging.getLogger(__name__)
34+
35+
36+
@pytest.mark.group(1)
37+
@pytest.mark.abort_on_fail
38+
async def test_app_force_removal(ops_test: OpsTest, charm: str):
39+
"""Remove unit with force while storage is alive."""
40+
async with ops_test.fast_forward():
41+
# Deploy the charm.
42+
logger.info("deploying charm")
43+
await ops_test.model.deploy(
44+
charm,
45+
application_name=APPLICATION_NAME,
46+
num_units=1,
47+
series=CHARM_SERIES,
48+
storage={"pgdata": {"pool": "lxd-btrfs", "size": 8046}},
49+
config={"profile": "testing"},
50+
)
51+
52+
logger.info("waiting for idle")
53+
await ops_test.model.wait_for_idle(apps=[APPLICATION_NAME], status="active", timeout=1500)
54+
assert ops_test.model.applications[APPLICATION_NAME].units[0].workload_status == "active"
55+
56+
primary_name = ops_test.model.applications[APPLICATION_NAME].units[0].name
57+
58+
logger.info("waiting for postgresql")
59+
for attempt in Retrying(stop=stop_after_delay(15 * 3), wait=wait_fixed(3), reraise=True):
60+
with attempt:
61+
assert await is_postgresql_ready(ops_test, primary_name)
62+
63+
logger.info("getting storage id")
64+
storage_id_str = storage_id(ops_test, primary_name)
65+
66+
# Check if storage exists after application deployed
67+
logger.info("werifing is storage exists")
68+
for attempt in Retrying(stop=stop_after_delay(15 * 3), wait=wait_fixed(3), reraise=True):
69+
with attempt:
70+
assert await is_storage_exists(ops_test, storage_id_str)
71+
72+
# Create test database to check there is no resources conflicts
73+
logger.info("creating db")
74+
await create_db(ops_test, APPLICATION_NAME, TEST_DATABASE_NAME)
75+
76+
# Check that test database is not exists for new unit
77+
logger.info("checking db")
78+
assert await check_db(ops_test, APPLICATION_NAME, TEST_DATABASE_NAME)
79+
80+
# Destroy charm
81+
logger.info("force removing charm")
82+
if juju_major_version == 2:
83+
await remove_unit_force(ops_test, primary_name)
84+
else:
85+
await ops_test.model.destroy_unit(
86+
primary_name, force=True, destroy_storage=False, max_wait=1500
87+
)
88+
89+
# Storage should remain
90+
logger.info("werifing is storage exists")
91+
for attempt in Retrying(stop=stop_after_delay(15 * 3), wait=wait_fixed(3), reraise=True):
92+
with attempt:
93+
assert await is_storage_exists(ops_test, storage_id_str)
94+
95+
96+
@pytest.mark.group(1)
97+
@pytest.mark.abort_on_fail
98+
async def test_charm_garbage_ignorance(ops_test: OpsTest, charm: str):
99+
"""Test charm deploy in dirty environment with garbage storage."""
100+
async with ops_test.fast_forward():
101+
logger.info("checking garbage storage")
102+
garbage_storage = None
103+
for attempt in Retrying(stop=stop_after_delay(30 * 3), wait=wait_fixed(3), reraise=True):
104+
with attempt:
105+
garbage_storage = await get_any_deatached_storage(ops_test)
106+
107+
logger.info("add unit with attached storage")
108+
await add_unit_with_storage(ops_test, APPLICATION_NAME, garbage_storage)
109+
110+
primary_name = ops_test.model.applications[APPLICATION_NAME].units[0].name
111+
112+
logger.info("waiting for postgresql")
113+
for attempt in Retrying(stop=stop_after_delay(15 * 3), wait=wait_fixed(3), reraise=True):
114+
with attempt:
115+
assert await is_postgresql_ready(ops_test, primary_name)
116+
117+
logger.info("getting storage id")
118+
storage_id_str = storage_id(ops_test, primary_name)
119+
120+
assert storage_id_str == garbage_storage
121+
122+
# Check if storage exists after application deployed
123+
logger.info("werifing is storage exists")
124+
for attempt in Retrying(stop=stop_after_delay(15 * 3), wait=wait_fixed(3), reraise=True):
125+
with attempt:
126+
assert await is_storage_exists(ops_test, storage_id_str)
127+
128+
# Check that test database exists for new unit
129+
logger.info("checking db")
130+
assert await check_db(ops_test, APPLICATION_NAME, TEST_DATABASE_NAME)
131+
132+
logger.info("removing charm")
133+
await ops_test.model.destroy_unit(primary_name)
134+
135+
136+
@pytest.mark.group(1)
137+
@pytest.mark.abort_on_fail
138+
@pytest.mark.skipif(juju_major_version < 3, reason="Requires juju 3 or higher")
139+
async def test_app_resources_conflicts_v3(ops_test: OpsTest, charm: str):
140+
"""Test application deploy in dirty environment with garbage storage from another application."""
141+
async with ops_test.fast_forward():
142+
logger.info("checking garbage storage")
143+
garbage_storage = None
144+
for attempt in Retrying(stop=stop_after_delay(30 * 3), wait=wait_fixed(3), reraise=True):
145+
with attempt:
146+
garbage_storage = await get_any_deatached_storage(ops_test)
147+
148+
logger.info("deploying duplicate application with attached storage")
149+
await ops_test.model.deploy(
150+
charm,
151+
application_name=DUP_APPLICATION_NAME,
152+
num_units=1,
153+
series=CHARM_SERIES,
154+
attach_storage=[tag.storage(garbage_storage)],
155+
config={"profile": "testing"},
156+
)
157+
158+
# Reducing the update status frequency to speed up the triggering of deferred events.
159+
await ops_test.model.set_config({"update-status-hook-interval": "10s"})
160+
161+
logger.info("waiting for duplicate application to be blocked")
162+
try:
163+
await ops_test.model.wait_for_idle(
164+
apps=[DUP_APPLICATION_NAME], timeout=1000, status="blocked"
165+
)
166+
except TimeoutError:
167+
logger.info("Application is not in blocked state. Checking logs...")
168+
169+
# Since application have postgresql db in storage from external application it should not be able to connect due to new password
170+
logger.info("checking operator password auth")
171+
assert not await check_password_auth(
172+
ops_test, ops_test.model.applications[DUP_APPLICATION_NAME].units[0].name
173+
)
174+
175+
176+
@pytest.mark.group(1)
177+
@pytest.mark.abort_on_fail
178+
@pytest.mark.skipif(juju_major_version != 2, reason="Requires juju 2")
179+
async def test_app_resources_conflicts_v2(ops_test: OpsTest, charm: str):
180+
"""Test application deploy in dirty environment with garbage storage from another application."""
181+
async with ops_test.fast_forward():
182+
logger.info("checking garbage storage")
183+
garbage_storage = None
184+
for attempt in Retrying(stop=stop_after_delay(30 * 3), wait=wait_fixed(3), reraise=True):
185+
with attempt:
186+
garbage_storage = await get_any_deatached_storage(ops_test)
187+
188+
# Deploy duplicaate charm
189+
logger.info("deploying duplicate application")
190+
await ops_test.model.deploy(
191+
charm,
192+
application_name=DUP_APPLICATION_NAME,
193+
num_units=1,
194+
series=CHARM_SERIES,
195+
config={"profile": "testing"},
196+
)
197+
198+
logger.info("force removing charm")
199+
await remove_unit_force(
200+
ops_test, ops_test.model.applications[DUP_APPLICATION_NAME].units[0].name
201+
)
202+
203+
# Add unit with garbage storage
204+
logger.info("adding charm with attached storage")
205+
add_unit_cmd = f"add-unit {DUP_APPLICATION_NAME} --model={ops_test.model.info.name} --attach-storage={garbage_storage}".split()
206+
return_code, _, _ = await ops_test.juju(*add_unit_cmd)
207+
assert return_code == 0, "Failed to add unit with storage"
208+
209+
logger.info("waiting for duplicate application to be blocked")
210+
try:
211+
await ops_test.model.wait_for_idle(
212+
apps=[DUP_APPLICATION_NAME], timeout=1000, status="blocked"
213+
)
214+
except TimeoutError:
215+
logger.info("Application is not in blocked state. Checking logs...")
216+
217+
# Since application have postgresql db in storage from external application it should not be able to connect due to new password
218+
logger.info("checking operator password auth")
219+
assert not await check_password_auth(
220+
ops_test, ops_test.model.applications[DUP_APPLICATION_NAME].units[0].name
221+
)

0 commit comments

Comments
 (0)