Skip to content

Commit 2db6524

Browse files
no-wait option in tests.yaml
To run multiple bundles in different models, zaza need to wait for first bundle applications to acheive status as specified in target_deploy_status before deploying second bundle using configure. (Deploying second bundle using configure is actually a hacky way). This increases the time of deployment as both bundles are deployed sequentially. sunbeam-charms project has total timeout of 2 hours for its functional test. Add option no-wait in tests.yaml so that zaza did not wait for target_deploy_status for the applications and proceed with configure. tests_options: no_wait_deploy: smoke The option --no-wait already exists for functest-deploy cli command.
1 parent 8db3638 commit 2db6524

5 files changed

Lines changed: 171 additions & 24 deletions

File tree

doc/source/addingcharmtests.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,38 @@ In the above case, focal-ussuri will be deployed using the --force parameter.
173173
i.e. the `tests_options.force_deploy['focal-ussuri']` option applies to the
174174
`focal-ussuri` bundle whether it appears in any of the bundle sections.
175175

176+
Skipping the post-deploy wait
177+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
178+
179+
By default, after deploying a bundle zaza waits for all applications to reach
180+
the states specified in ``target_deploy_status`` (or active/idle if not
181+
configured). This wait can be skipped on a per-bundle basis using the
182+
``tests_options.no_wait_deploy`` option.
183+
184+
This is useful when a charm requires post-deploy configuration before it can
185+
reach its target state, or when you want to proceed immediately to the
186+
configure step without waiting.
187+
188+
In the ``tests.yaml`` the option is added as a list item::
189+
190+
charm_name: vault
191+
gate_bundles:
192+
- focal-ussuri
193+
194+
target_deploy_status:
195+
vault:
196+
workload-status: blocked
197+
workload-status-message: Vault needs to be initialized
198+
199+
tests_options:
200+
no_wait_deploy:
201+
- focal-ussuri
202+
203+
In the above case, zaza will deploy the ``focal-ussuri`` bundle and immediately
204+
proceed to the configure step without waiting for vault to enter the blocked
205+
state or for the model to settle (the additional post-deploy
206+
``block_until_all_units_idle`` wait is also skipped).
207+
176208
Augmenting behaviour of configure steps
177209
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
178210

unit_tests/test_zaza_charm_lifecycle_func_test_runner.py

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ def test_func_test_runner(self):
8585
deploy_calls = [
8686
mock.call(cwd + '/tests/bundles/bundle1.yaml', 'newmodel',
8787
model_ctxt={'default_alias': 'newmodel'},
88-
force=True, test_directory=None, trust=False,
88+
wait=True, force=True, test_directory=None, trust=False,
8989
ignore_hard_deploy_errors=False),
9090
mock.call(cwd + '/tests/bundles/bundle2.yaml', 'newmodel',
9191
model_ctxt={'default_alias': 'newmodel'},
92-
force=True, test_directory=None, trust=False,
92+
wait=True, force=True, test_directory=None, trust=False,
9393
ignore_hard_deploy_errors=False)]
9494
configure_calls = [
9595
mock.call('newmodel', [
@@ -164,24 +164,24 @@ def test_func_test_runner_cmr(self):
164164
cwd = os.getcwd()
165165
deploy_calls = [
166166
mock.call(cwd + '/tests/bundles/bundle1.yaml', 'm1',
167-
model_ctxt={'default_alias': 'm1'}, force=False,
168-
test_directory=None, trust=False,
167+
model_ctxt={'default_alias': 'm1'}, wait=True,
168+
force=False, test_directory=None, trust=False,
169169
ignore_hard_deploy_errors=False),
170170
mock.call(cwd + '/tests/bundles/bundle2.yaml', 'm2',
171-
model_ctxt={'default_alias': 'm2'}, force=False,
172-
test_directory=None, trust=False,
171+
model_ctxt={'default_alias': 'm2'}, wait=True,
172+
force=False, test_directory=None, trust=False,
173173
ignore_hard_deploy_errors=False),
174174
mock.call(
175175
cwd + '/tests/bundles/bundle5.yaml',
176176
'm3',
177177
model_ctxt={'model_alias_5': 'm3', 'model_alias_6': 'm4'},
178-
force=False, test_directory=None, trust=False,
178+
wait=True, force=False, test_directory=None, trust=False,
179179
ignore_hard_deploy_errors=False),
180180
mock.call(
181181
cwd + '/tests/bundles/bundle6.yaml',
182182
'm4',
183183
model_ctxt={'model_alias_5': 'm3', 'model_alias_6': 'm4'},
184-
force=False, test_directory=None, trust=False,
184+
wait=True, force=False, test_directory=None, trust=False,
185185
ignore_hard_deploy_errors=False)]
186186
configure_calls = [
187187
mock.call('m1', [
@@ -256,12 +256,12 @@ def test_func_test_runner_with_before_script(self):
256256
mock.call('newmodel', 'default_alias', test_directory=None)]
257257
deploy_calls = [
258258
mock.call(cwd + '/tests/bundles/bundle1.yaml', 'newmodel',
259-
model_ctxt={'default_alias': 'newmodel'}, force=False,
260-
test_directory=None, trust=False,
259+
model_ctxt={'default_alias': 'newmodel'}, wait=True,
260+
force=False, test_directory=None, trust=False,
261261
ignore_hard_deploy_errors=False),
262262
mock.call(cwd + '/tests/bundles/bundle2.yaml', 'newmodel',
263-
model_ctxt={'default_alias': 'newmodel'}, force=False,
264-
test_directory=None, trust=False,
263+
model_ctxt={'default_alias': 'newmodel'}, wait=True,
264+
force=False, test_directory=None, trust=False,
265265
ignore_hard_deploy_errors=False)]
266266
before_deploy_calls = [
267267
mock.call('newmodel', [
@@ -319,7 +319,7 @@ def test_func_test_runner_smoke(self):
319319
deploy_calls = [
320320
mock.call(cwd + '/tests/bundles/bundle2.yaml', 'newmodel',
321321
model_ctxt={'default_alias': 'newmodel'},
322-
force=False,
322+
wait=True, force=False,
323323
test_directory=None, trust=False,
324324
ignore_hard_deploy_errors=False)]
325325
self.deploy.assert_has_calls(deploy_calls)
@@ -352,12 +352,12 @@ def test_func_test_runner_dev(self):
352352
cwd = os.getcwd()
353353
deploy_calls = [
354354
mock.call(cwd + '/tests/bundles/bundle3.yaml', 'newmodel',
355-
model_ctxt={'default_alias': 'newmodel'}, force=False,
356-
test_directory=None, trust=False,
355+
model_ctxt={'default_alias': 'newmodel'}, wait=True,
356+
force=False, test_directory=None, trust=False,
357357
ignore_hard_deploy_errors=False),
358358
mock.call(cwd + '/tests/bundles/bundle4.yaml', 'newmodel',
359-
model_ctxt={'default_alias': 'newmodel'}, force=False,
360-
test_directory=None, trust=False,
359+
model_ctxt={'default_alias': 'newmodel'}, wait=True,
360+
force=False, test_directory=None, trust=False,
361361
ignore_hard_deploy_errors=False)]
362362
self.deploy.assert_has_calls(deploy_calls)
363363

@@ -392,7 +392,7 @@ def test_func_test_runner_specify_bundle(self):
392392
cwd + '/tests/bundles/maveric-filebeat.yaml',
393393
'newmodel',
394394
model_ctxt={'default_alias': 'newmodel'},
395-
force=False,
395+
wait=True, force=False,
396396
test_directory=None, trust=False,
397397
ignore_hard_deploy_errors=False)]
398398
self.deploy.assert_has_calls(deploy_calls)
@@ -429,7 +429,7 @@ def test_func_test_runner_specify_bundle_with_alias(self):
429429
cwd + '/tests/bundles/maveric-filebeat.yaml',
430430
'newmodel',
431431
model_ctxt={'alias': 'newmodel'},
432-
force=False,
432+
wait=True, force=False,
433433
test_directory=None, trust=False,
434434
ignore_hard_deploy_errors=False)]
435435
self.deploy.assert_has_calls(deploy_calls)
@@ -459,7 +459,7 @@ def test_func_test_runner_specify_bundle_with_implicit_alias(self):
459459
cwd + '/tests/bundles/maveric-filebeat.yaml',
460460
'newmodel',
461461
model_ctxt={'alias': 'newmodel'},
462-
force=False,
462+
wait=True, force=False,
463463
test_directory=None, trust=False,
464464
ignore_hard_deploy_errors=False)]
465465
self.deploy.assert_has_calls(deploy_calls)
@@ -498,15 +498,15 @@ def test_func_test_runner_cmr_specify_bundle_with_alias(self):
498498
'newmodel1',
499499
model_ctxt={'alias': 'newmodel1',
500500
'another_alias': 'newmodel2'},
501-
force=False,
501+
wait=True, force=False,
502502
test_directory=None, trust=False,
503503
ignore_hard_deploy_errors=False),
504504
mock.call(
505505
cwd + '/tests/bundles/maverick-things.yaml',
506506
'newmodel2',
507507
model_ctxt={'alias': 'newmodel1',
508508
'another_alias': 'newmodel2'},
509-
force=False,
509+
wait=True, force=False,
510510
test_directory=None, trust=False,
511511
ignore_hard_deploy_errors=False)]
512512
self.deploy.assert_has_calls(deploy_calls)
@@ -604,3 +604,41 @@ def test_main_bundle_keep_model_ambiguous_case2(self):
604604

605605
def test_main_bundle_keep_model_ambiguous_case3(self):
606606
self.__keep_model_ambiguous(False, True, True)
607+
608+
def test_func_test_runner_no_wait_deploy(self):
609+
self.patch_object(lc_func_test_runner.utils, 'get_charm_config')
610+
self.patch_object(lc_func_test_runner.utils, 'generate_model_name')
611+
self.patch_object(lc_func_test_runner.prepare, 'prepare')
612+
self.patch_object(lc_func_test_runner.before_deploy, 'before_deploy')
613+
self.patch_object(lc_func_test_runner.deploy, 'deploy')
614+
self.patch_object(lc_func_test_runner.configure, 'configure')
615+
self.patch_object(lc_func_test_runner.test, 'test')
616+
self.patch_object(lc_func_test_runner.destroy, 'destroy')
617+
self.patch_object(
618+
lc_func_test_runner.zaza.model,
619+
'block_until_all_units_idle')
620+
self.generate_model_name.return_value = 'newmodel'
621+
self.get_charm_config.return_value = {
622+
'charm_name': 'mycharm',
623+
'gate_bundles': ['bundle1', 'bundle2'],
624+
'tests': [
625+
'zaza.charm_tests.mycharm.tests.SmokeTest'],
626+
'tests_options': {
627+
'no_wait_deploy': ['bundle1']
628+
}}
629+
lc_func_test_runner.func_test_runner()
630+
cwd = os.getcwd()
631+
deploy_calls = [
632+
mock.call(cwd + '/tests/bundles/bundle1.yaml', 'newmodel',
633+
model_ctxt={'default_alias': 'newmodel'},
634+
wait=False, force=False, test_directory=None,
635+
trust=False, ignore_hard_deploy_errors=False),
636+
mock.call(cwd + '/tests/bundles/bundle2.yaml', 'newmodel',
637+
model_ctxt={'default_alias': 'newmodel'},
638+
wait=True, force=False, test_directory=None,
639+
trust=False, ignore_hard_deploy_errors=False)]
640+
self.deploy.assert_has_calls(deploy_calls)
641+
# block_until_all_units_idle should only be called for bundle2
642+
# (bundle1 has no_wait_deploy set)
643+
self.block_until_all_units_idle.assert_called_once_with(
644+
ignore_hard_errors=False, model_name='newmodel')

unit_tests/test_zaza_charm_lifecycle_utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,40 @@ def test_is_config_deploy_trusted_for_bundle(self):
304304
}
305305
self.assertTrue(lc_utils.is_config_deploy_trusted_for_bundle('x'))
306306

307+
def test_no_wait_deploy(self):
308+
self.patch_object(lc_utils, 'get_charm_config')
309+
# test that no options at all returns False
310+
self.get_charm_config.return_value = {}
311+
self.assertFalse(lc_utils.no_wait_deploy('x'))
312+
# test that if options exist but no bundle
313+
self.get_charm_config.return_value = {
314+
'tests_options': {}
315+
}
316+
self.assertFalse(lc_utils.no_wait_deploy('x'))
317+
self.get_charm_config.return_value = {
318+
'tests_options': {
319+
'no_wait_deploy': []
320+
}
321+
}
322+
self.assertFalse(lc_utils.no_wait_deploy('x'))
323+
# verify that it returns True if the bundle is mentioned
324+
self.get_charm_config.return_value = {
325+
'tests_options': {
326+
'no_wait_deploy': ['x']
327+
}
328+
}
329+
self.assertTrue(lc_utils.no_wait_deploy('x'))
330+
# verify that a prefixed bundle 'security:x' does not match 'x'
331+
self.assertFalse(lc_utils.no_wait_deploy('security:x'))
332+
# verify that listing 'security:x' does not match plain 'x'
333+
self.get_charm_config.return_value = {
334+
'tests_options': {
335+
'no_wait_deploy': ['security:x']
336+
}
337+
}
338+
self.assertFalse(lc_utils.no_wait_deploy('x'))
339+
self.assertTrue(lc_utils.no_wait_deploy('security:x'))
340+
307341
def test_get_class(self):
308342
self.assertEqual(
309343
type(lc_utils.get_class('unit_tests.'

zaza/charm_lifecycle/func_test_runner.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,21 +142,30 @@ def run_env_deployment(env_deployment, keep_model=DESTROY_MODEL, force=False,
142142
deployment.bundle)
143143
errors_ = (ignore_hard_deploy_errors or
144144
utils.ignore_hard_deploy_errors(deployment.bundle))
145+
no_wait_ = utils.no_wait_deploy(deployment.bundle)
145146
deploy.deploy(
146147
os.path.join(
147148
utils.get_bundle_dir(),
148149
'{}.yaml'.format(deployment.bundle)),
149150
deployment.model_name,
150151
model_ctxt=model_aliases,
152+
wait=not no_wait_,
151153
force=force_,
152154
trust=trust_,
153155
test_directory=test_directory,
154156
ignore_hard_deploy_errors=errors_)
155157

156158
# When deploying bundles with cross model relations, hooks may be
157-
# triggered in already deployedi models so wait for all models to
158-
# settle.
159+
# triggered in already deployed models so wait for all models to
160+
# settle. Bundles with no_wait_deploy set intentionally skip this
161+
# settle wait to allow immediate progression to the configure step.
159162
for deployment in env_deployment.model_deploys:
163+
if utils.no_wait_deploy(deployment.bundle):
164+
logging.info(
165+
"Skipping post-deploy wait for {} "
166+
"(no_wait_deploy is set)".format(
167+
deployment.model_name))
168+
continue
160169
logging.info("Waiting for {} to settle".format(
161170
deployment.model_name))
162171
errors_ = (ignore_hard_deploy_errors or

zaza/charm_lifecycle/utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,40 @@ def ignore_hard_deploy_errors(
526526
return False
527527

528528

529+
def no_wait_deploy(
530+
bundle_name, yaml_file=None, fatal=True):
531+
"""Ask if the deploy wait/settle steps should be skipped for a bundle.
532+
533+
Setting no_wait_deploy for a bundle means that zaza will not:
534+
* wait for the applications to reach the states defined in
535+
target_deploy_status (or active/idle) after deploying the bundle; or
536+
* perform the post-deploy block_until_all_units_idle "settle" wait.
537+
538+
The tests_options section needs to look like:
539+
540+
tests_options:
541+
no_wait_deploy:
542+
- focal-ussuri
543+
544+
:param bundle_name: bundle to check in the no_wait_deploy list
545+
:type bundle_name: str
546+
:param yaml_file: the YAML file that contains the tests specification
547+
:type yaml_file: Optional[str]
548+
:param fatal: whether any errors cause an exception or are just logged.
549+
:type fatal: bool
550+
:returns: True if the config option is set for the bundle
551+
:rtype: bool
552+
:raises: OSError if the YAML file doesn't exist and fatal=True
553+
"""
554+
config = get_charm_config(yaml_file, fatal)
555+
try:
556+
return bundle_name in config['tests_options']['no_wait_deploy']
557+
# Type error is if no_wait_deploy is present, but with no list
558+
except (KeyError, TypeError):
559+
pass
560+
return False
561+
562+
529563
def get_class(class_str):
530564
"""Get the class represented by the given string.
531565

0 commit comments

Comments
 (0)