Skip to content

Commit a5785cd

Browse files
authored
Fix schedule_all_controllers to skip INVALID controllers when enabling (#400)
When enabling controller services, NiFi server correctly skips INVALID controllers (those that cannot be enabled due to validation errors). The client was incorrectly waiting for ALL controllers to reach ENABLED state, causing a 30-second timeout when any controller was INVALID. Now the polling logic only waits for VALID controllers when enabling. When disabling, all controllers are still checked since any controller can be disabled regardless of validation status. Adds test for mixed VALID/INVALID controller scenario.
1 parent 0716aee commit a5785cd

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

nipyapi/canvas.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2153,6 +2153,12 @@ def schedule_all_controllers(pg_id, scheduled):
21532153
all descendant controller services automatically. Waits for all controllers
21542154
to reach the target state before returning.
21552155
2156+
Note:
2157+
When enabling, INVALID controllers (those with validation errors) are
2158+
skipped since NiFi cannot enable them. The function waits only for
2159+
VALID controllers to reach ENABLED state. When disabling, all
2160+
controllers are included since any controller can be disabled.
2161+
21562162
Args:
21572163
pg_id (str): The UUID of the Process Group
21582164
scheduled (bool or str): True/False for ENABLED/DISABLED, or one of
@@ -2176,7 +2182,15 @@ def _all_controllers_in_state():
21762182
controllers = list_all_controllers(pg_id)
21772183
if not controllers:
21782184
return True # No controllers to wait for
2179-
return all(c.component.state == target_state for c in controllers)
2185+
# When enabling, only wait for VALID controllers (server skips INVALID ones)
2186+
# When disabling, all controllers can be disabled regardless of validation
2187+
if target_state == "ENABLED":
2188+
eligible = [c for c in controllers if c.component.validation_status == "VALID"]
2189+
else:
2190+
eligible = controllers
2191+
if not eligible:
2192+
return True # No eligible controllers to wait for
2193+
return all(c.component.state == target_state for c in eligible)
21802194

21812195
with nipyapi.utils.rest_exceptions():
21822196
result = nipyapi.nifi.FlowApi().activate_controller_services(

tests/test_canvas.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,41 @@ def test_schedule_all_controllers(fix_pg, fix_cont):
920920
assert c2.component.state == 'DISABLED'
921921

922922

923+
def test_schedule_all_controllers_with_invalid(fix_pg, fix_cont):
924+
"""Test schedule_all_controllers skips INVALID controllers when enabling.
925+
926+
NiFi server correctly skips INVALID controllers (they can't be enabled).
927+
The client should only wait for VALID controllers to reach ENABLED state.
928+
"""
929+
f_pg = fix_pg.generate()
930+
# Create valid controller via fixture
931+
valid_ctrl = fix_cont(parent_pg=f_pg, kind='CSVReader')
932+
# Create invalid controller (SSLContextService requires keystore config)
933+
invalid_ctrl = fix_cont(parent_pg=f_pg, kind='StandardSSLContextService')
934+
935+
# Verify preconditions
936+
valid_ctrl = canvas.get_controller(valid_ctrl.id, 'id')
937+
invalid_ctrl = canvas.get_controller(invalid_ctrl.id, 'id')
938+
assert valid_ctrl.component.validation_status == 'VALID'
939+
assert invalid_ctrl.component.validation_status == 'INVALID'
940+
941+
# Enable all - should complete without timeout
942+
result = canvas.schedule_all_controllers(f_pg.id, True)
943+
assert result.state == 'ENABLED'
944+
945+
# Valid controller should be ENABLED, invalid stays DISABLED
946+
valid_ctrl = canvas.get_controller(valid_ctrl.id, 'id')
947+
invalid_ctrl = canvas.get_controller(invalid_ctrl.id, 'id')
948+
assert valid_ctrl.component.state == 'ENABLED'
949+
assert invalid_ctrl.component.state == 'DISABLED'
950+
951+
# Disable all - should also work
952+
result = canvas.schedule_all_controllers(f_pg.id, False)
953+
assert result.state == 'DISABLED'
954+
valid_ctrl = canvas.get_controller(valid_ctrl.id, 'id')
955+
assert valid_ctrl.component.state == 'DISABLED'
956+
957+
923958
def test_delete_controller(fix_pg, fix_cont):
924959
f_pg = fix_pg.generate()
925960
f_c1 = fix_cont(parent_pg=f_pg)

0 commit comments

Comments
 (0)