44
55import json
66import logging
7+ import subprocess
78from pathlib import Path
89
910import pytest
1011import yaml
1112from pytest_operator .plugin import OpsTest
1213
13- from .helpers import access_all_dashboards , get_app_relation_data
14+ from src .literals import MSG_INCOMPATIBLE_UPGRADE
15+
16+ from ..ha .continuous_writes import ContinuousWrites
17+ from .helpers import (
18+ access_all_dashboards ,
19+ check_full_status ,
20+ get_app_relation_data ,
21+ get_leader_unit ,
22+ get_relations ,
23+ )
1424
1525logger = logging .getLogger (__name__ )
1626
27+
28+ OPENSEARCH_CHARM_NAME = "opensearch"
29+ OPENSEARCH_DASHBOARDS_CHARM_NAME = "opensearch-dashboards"
30+ CHANNEL = "2/edge"
31+
32+ STARTING_VERSION = "2.14.0"
33+
34+
35+ OPENSEARCH_VERSION_TO_REVISION = {
36+ STARTING_VERSION : 143 ,
37+ "2.15.0" : 144 ,
38+ "2.16.0" : 161 ,
39+ }
40+
41+ VERSION_TO_REVISION = {
42+ STARTING_VERSION : 18 ,
43+ "2.15.0" : 19 ,
44+ "2.16.0" : 20 ,
45+ }
46+
47+
48+ FROM_VERSION_PREFIX = "from_v{}_to_local"
49+
50+ UPGRADE_INITIAL_VERSION = [
51+ (
52+ pytest .param (
53+ version ,
54+ id = FROM_VERSION_PREFIX .format (version ),
55+ marks = pytest .mark .group (FROM_VERSION_PREFIX .format (version )),
56+ )
57+ )
58+ for version in VERSION_TO_REVISION .keys ()
59+ ]
60+
61+ charm = None
62+
63+
1764METADATA = yaml .safe_load (Path ("./metadata.yaml" ).read_text ())
1865APP_NAME = METADATA ["name" ]
1966
3077 - [ 'sysctl', '-w', 'net.ipv4.tcp_retries2=5' ]
3178 """ ,
3279}
80+ OPENSEARCH_RELATION_NAME = "opensearch-client"
3381TLS_CERTIFICATES_APP_NAME = "self-signed-certificates"
3482
3583NUM_UNITS_APP = 3
@@ -45,9 +93,11 @@ async def test_build_and_deploy(ops_test: OpsTest):
4593 """Deploying all charms required for the tests, and wait for their complete setup to be done."""
4694
4795 pytest .charm = await ops_test .build_charm ("." )
48- await ops_test .model .deploy (pytest .charm , application_name = APP_NAME , num_units = NUM_UNITS_APP )
4996 await ops_test .model .set_config (OPENSEARCH_CONFIG )
50- await ops_test .model .deploy (OPENSEARCH_APP_NAME , channel = "2/edge" , num_units = NUM_UNITS_DB )
97+ await ops_test .modeGl .deploy (
98+ OPENSEARCH_APP_NAME , channel = "2/edge" , num_units = NUM_UNITS_DB , revision = 161
99+ )
100+ await ops_test .model .deploy (pytest .charm , application_name = APP_NAME , num_units = NUM_UNITS_APP )
51101
52102 config = {"ca-common-name" : "CN_CA" }
53103 await ops_test .model .deploy (TLS_CERTIFICATES_APP_NAME , channel = "stable" , config = config )
@@ -80,12 +130,7 @@ async def test_build_and_deploy(ops_test: OpsTest):
80130@pytest .mark .group (1 )
81131@pytest .mark .abort_on_fail
82132async def test_in_place_upgrade_http (ops_test : OpsTest ):
83- leader_unit = None
84- for unit in ops_test .model .applications [APP_NAME ].units :
85- if await unit .is_leader_from_status ():
86- leader_unit = unit
87- assert leader_unit
88-
133+ leader_unit = get_leader_unit (ops_test )
89134 action = await leader_unit .run_action ("pre-upgrade-check" )
90135 await action .wait ()
91136
@@ -124,12 +169,7 @@ async def test_switch_tls_on(ops_test: OpsTest):
124169@pytest .mark .group (1 )
125170@pytest .mark .abort_on_fail
126171async def test_in_place_upgrade_https (ops_test : OpsTest ):
127- leader_unit = None
128- for unit in ops_test .model .applications [APP_NAME ].units :
129- if await unit .is_leader_from_status ():
130- leader_unit = unit
131- assert leader_unit
132-
172+ leader_unit = get_leader_unit (ops_test )
133173 action = await leader_unit .run_action ("pre-upgrade-check" )
134174 await action .wait ()
135175
@@ -149,3 +189,255 @@ async def test_in_place_upgrade_https(ops_test: OpsTest):
149189 )
150190
151191 assert await access_all_dashboards (ops_test , https = True )
192+
193+
194+ #######################################################################
195+ #
196+ # Auxiliary functions
197+ #
198+ #######################################################################
199+
200+
201+ @pytest .mark .runner (["self-hosted" , "linux" , "X64" , "jammy" , "large" ])
202+ async def _build_env (ops_test : OpsTest , version : str ) -> None :
203+ """Deploy OpenSearch cluster from a given revision."""
204+ await ops_test .model .set_config (OPENSEARCH_CONFIG )
205+
206+ await ops_test .model .deploy (
207+ OPENSEARCH_CHARM_NAME ,
208+ application_name = APP_NAME ,
209+ num_units = 3 ,
210+ channel = CHANNEL ,
211+ revision = OPENSEARCH_VERSION_TO_REVISION [version ],
212+ )
213+
214+ # Deploy TLS Certificates operator.
215+ config = {"ca-common-name" : "CN_CA" }
216+ await ops_test .model .deploy (TLS_CERTIFICATES_APP_NAME , channel = "stable" , config = config )
217+
218+ # Relate it to OpenSearch to set up TLS.
219+ await ops_test .model .integrate (APP_NAME , TLS_CERTIFICATES_APP_NAME )
220+ await ops_test .model .wait_for_idle (
221+ apps = [TLS_CERTIFICATES_APP_NAME , APP_NAME ],
222+ status = "active" ,
223+ timeout = 1400 ,
224+ idle_period = 50 ,
225+ )
226+ assert len (ops_test .model .applications [APP_NAME ].units ) == 3
227+
228+ await ops_test .model .deploy (
229+ OPENSEARCH_DASHBOARDS_CHARM_NAME ,
230+ application_name = APP_NAME ,
231+ num_units = 3 ,
232+ channel = CHANNEL ,
233+ revision = VERSION_TO_REVISION [version ],
234+ )
235+
236+
237+ #######################################################################
238+ #
239+ # Tests
240+ #
241+ #######################################################################
242+
243+
244+ @pytest .mark .runner (["self-hosted" , "linux" , "X64" , "jammy" , "large" ])
245+ @pytest .mark .group ("happy_path_upgrade" )
246+ @pytest .mark .abort_on_fail
247+ @pytest .mark .skip_if_deployed
248+ async def test_deploy_latest_from_channel (ops_test : OpsTest ) -> None :
249+ """Deploy OpenSearch."""
250+ await _build_env (ops_test , STARTING_VERSION )
251+
252+
253+ @pytest .mark .group ("happy_path_upgrade" )
254+ @pytest .mark .abort_on_fail
255+ async def test_upgrade_between_versions (
256+ ops_test : OpsTest , c_writes : ContinuousWrites , c_writes_runner
257+ ) -> None :
258+ """Test upgrade from upstream to currently locally built version."""
259+ for version , rev in VERSION_TO_REVISION .items ():
260+ if version == STARTING_VERSION :
261+ # We're starting in this version
262+ continue
263+
264+ logger .info (f"Upgrading to version { version } " )
265+
266+ leader_unit = get_leader_unit (ops_test )
267+ action = await leader_unit .run_action ("pre-upgrade-check" )
268+ await action .wait ()
269+ assert action .status == "completed"
270+
271+ async with ops_test .fast_forward ():
272+ logger .info ("Refresh the charm" )
273+ # due to: https://github.com/juju/python-libjuju/issues/1057
274+ # application = ops_test.model.applications[APP_NAME]
275+ # await application.refresh(
276+ # revision=rev,
277+ # )
278+ subprocess .check_output (
279+ f"juju refresh { OPENSEARCH_CHARM_NAME } --revision={ rev } " .split ()
280+ )
281+
282+ logger .info ("Refresh is over, waiting for the charm to settle" )
283+
284+ await ops_test .model .wait_for_idle (
285+ apps = [OPENSEARCH_CHARM_NAME ], status = "active" , timeout = 1000 , idle_period = 120
286+ )
287+ logger .info ("Opensearch upgrade finished" )
288+
289+ await ops_test .model .wait_for_idle (
290+ apps = [APP_NAME ], status = "blocked" , timeout = 1000 , idle_period = 120
291+ )
292+
293+ assert await check_full_status (
294+ ops_test , status = "blocked" , status_msg = MSG_INCOMPATIBLE_UPGRADE
295+ )
296+
297+ subprocess .check_output (
298+ f"juju refresh { OPENSEARCH_DASHBOARDS_CHARM_NAME } --revision={ rev } " .split ()
299+ )
300+
301+ logger .info ("Refresh is over, waiting for the charm to settle" )
302+ await ops_test .model .wait_for_idle (
303+ apps = [APP_NAME ], status = "blocked" , timeout = 1000 , idle_period = 120
304+ )
305+ logger .info ("Opensearch Dashboards upgrade finished" )
306+
307+ opensearch_relation = get_relations (ops_test , OPENSEARCH_RELATION_NAME )[0 ]
308+ assert await access_all_dashboards (ops_test , opensearch_relation .id )
309+
310+
311+ # @pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"])
312+ # @pytest.mark.group("happy_path_upgrade")
313+ # @pytest.mark.abort_on_fail
314+ # async def test_upgrade_to_local(
315+ # ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner
316+ # ) -> None:
317+ # """Test upgrade from usptream to currently locally built version."""
318+ # logger.info("Build charm locally")
319+ # charm = await ops_test.build_charm(".")
320+ # await assert_upgrade_to_local(ops_test, c_writes, charm)
321+ #
322+ #
323+ # ##################################################################################
324+ # #
325+ # # test scenarios from each version:
326+ # # Start with each version, moving to local and then rolling back mid-upgrade
327+ # # Once this test passes, the 2nd test will rerun the upgrade, this time to
328+ # # its end.
329+ # #
330+ # ##################################################################################
331+ #
332+ #
333+ # @pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"])
334+ # @pytest.mark.parametrize("version", UPGRADE_INITIAL_VERSION)
335+ # @pytest.mark.abort_on_fail
336+ # @pytest.mark.skip_if_deployed
337+ # async def test_deploy_from_version(ops_test: OpsTest, version) -> None:
338+ # """Deploy OpenSearch."""
339+ # await _build_env(ops_test, version)
340+ #
341+ #
342+ # @pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"])
343+ # @pytest.mark.parametrize("version", UPGRADE_INITIAL_VERSION)
344+ # @pytest.mark.abort_on_fail
345+ # async def test_upgrade_rollback_from_local(
346+ # ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner, version
347+ # ) -> None:
348+ # """Test upgrade and rollback to each version available."""
349+ # app = (await app_name(ops_test)) or APP_NAME
350+ # units = await get_application_units(ops_test, app)
351+ # leader_id = [u.id for u in units if u.is_leader][0]
352+ #
353+ # action = await run_action(
354+ # ops_test,
355+ # leader_id,
356+ # "pre-upgrade-check",
357+ # app=app,
358+ # )
359+ # assert action.status == "completed"
360+ #
361+ # logger.info("Build charm locally")
362+ # global charm
363+ # if not charm:
364+ # charm = await ops_test.build_charm(".")
365+ #
366+ # async with ops_test.fast_forward():
367+ # logger.info("Refresh the charm")
368+ # # due to: https://github.com/juju/python-libjuju/issues/1057
369+ # # application = ops_test.model.applications[APP_NAME]
370+ # # await application.refresh(
371+ # # revision=new_rev,
372+ # # )
373+ # subprocess.check_output(f"juju refresh opensearch --path={charm}".split())
374+ #
375+ # await wait_until(
376+ # ops_test,
377+ # apps=[app],
378+ # apps_statuses=["blocked"],
379+ # units_statuses=["active"],
380+ # wait_for_exact_units={
381+ # APP_NAME: 3,
382+ # },
383+ # timeout=1400,
384+ # idle_period=IDLE_PERIOD,
385+ # )
386+ #
387+ # logger.info(f"Rolling back to {version}")
388+ # # due to: https://github.com/juju/python-libjuju/issues/1057
389+ # # await application.refresh(
390+ # # revision=rev,
391+ # # )
392+ #
393+ # # Rollback operation
394+ # # We must first switch back to the upstream charm, then rollback to the original
395+ # # revision we were using.
396+ # subprocess.check_output(
397+ # f"""juju refresh opensearch
398+ # --switch={OPENSEARCH_DASHBOARDS_CHARM_NAME}
399+ # --channel={CHANNEL}""".split()
400+ # )
401+ #
402+ # # Wait until we are set in an idle state and can rollback the revision.
403+ # await wait_until(
404+ # ops_test,
405+ # apps=[app],
406+ # apps_statuses=["blocked"],
407+ # units_statuses=["active"],
408+ # wait_for_exact_units={
409+ # APP_NAME: 3,
410+ # },
411+ # timeout=1400,
412+ # idle_period=IDLE_PERIOD,
413+ # )
414+ #
415+ # subprocess.check_output(
416+ # f"juju refresh opensearch --revision={VERSION_TO_REVISION[version]}".split()
417+ # )
418+ #
419+ # await wait_until(
420+ # ops_test,
421+ # apps=[app],
422+ # apps_statuses=["active"],
423+ # units_statuses=["active"],
424+ # wait_for_exact_units={
425+ # APP_NAME: 3,
426+ # },
427+ # timeout=1400,
428+ # idle_period=IDLE_PERIOD,
429+ # )
430+ #
431+ #
432+ # @pytest.mark.runner(["self-hosted", "linux", "X64", "jammy", "large"])
433+ # @pytest.mark.parametrize("version", UPGRADE_INITIAL_VERSION)
434+ # @pytest.mark.abort_on_fail
435+ # async def test_upgrade_from_version_to_local(
436+ # ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner, version
437+ # ) -> None:
438+ # """Test upgrade from usptream to currently locally built version."""
439+ # logger.info("Build charm locally")
440+ # global charm
441+ # if not charm:
442+ # charm = await ops_test.build_charm(".")
443+ # await assert_upgrade_to_local(ops_test, c_writes, charm)
0 commit comments