Skip to content

Commit 5ae1774

Browse files
Roll back a rule's connection settings when the router update fails
When applying an updated rule to the router fails, updateLoadBalancerRule puts back the name, description, algorithm and cidrs, but not the keepalive and timeouts. Those stayed in the database while the router still ran the old values, and the next successful apply of anything on that router would quietly push them. The settings held before the update are now restored along with the rest. They were also written before the provider validated the rule, so a rule it rejected kept the new settings. The other fields only reach the database at _lbDao.update, after validation; the settings are now written at the same point. Signed-off-by: Brad House <bhouse@nexthop.ai>
1 parent 07af938 commit 5ae1774

2 files changed

Lines changed: 86 additions & 3 deletions

File tree

‎server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,15 +2415,18 @@ public LoadBalancer updateLoadBalancerRule(UpdateLoadBalancerRuleCmd cmd) {
24152415
lb.setCidrList(cidrListStr);
24162416
}
24172417

2418-
boolean settingsChanged = updateLoadBalancerConnectionSettings(lbRuleId, cmd.getKeepAlive(), cmd.getIdleTimeout(), cmd.getKeepAliveTimeout(),
2419-
cmd.isCleanupConnectionSettings());
2420-
24212418
// Validate rule in LB provider
24222419
LoadBalancingRule rule = getLoadBalancerRuleToApply(lb);
24232420
if (!validateLbRule(rule)) {
24242421
throw new InvalidParameterValueException(String.format("Modifications in lb rule %s are not supported.", lb));
24252422
}
24262423

2424+
// The settings are written straight to the details table, unlike the fields above that wait
2425+
// for _lbDao.update, so they are stored only once nothing else can reject the update
2426+
Map<String, String> settingsBackup = getConnectionSettings(lbRuleId);
2427+
boolean settingsChanged = updateLoadBalancerConnectionSettings(lbRuleId, cmd.getKeepAlive(), cmd.getIdleTimeout(), cmd.getKeepAliveTimeout(),
2428+
cmd.isCleanupConnectionSettings());
2429+
24272430
LoadBalancerVO tmplbVo = _lbDao.findById(lbRuleId);
24282431
boolean success = _lbDao.update(lbRuleId, lb);
24292432

@@ -2461,6 +2464,9 @@ public LoadBalancer updateLoadBalancerRule(UpdateLoadBalancerRuleCmd cmd) {
24612464
lb.setState(lbBackup.getState());
24622465
_lbDao.update(lb.getId(), lb);
24632466
_lbDao.persist(lb);
2467+
if (settingsChanged) {
2468+
setConnectionSettings(lbRuleId, settingsBackup);
2469+
}
24642470

24652471
logger.debug("LB Rollback rule: {} while updating LB rule.", lb);
24662472
}

‎server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,4 +494,81 @@ public void testUpdateLoadBalancerRuleCleanupReappliesConfig() throws Exception
494494
Mockito.verify(_firewallRuleDetailsDao).removeDetail(lbRuleId, LoadBalancer.IDLE_TIMEOUT);
495495
Mockito.verify(lbr, times(1)).applyLoadBalancerConfig(lbRuleId);
496496
}
497+
498+
/**
499+
* Backs the details dao with a map, so a value written and then rolled back can be seen.
500+
*/
501+
private Map<String, String> fakeConnectionSettings(Map<String, String> initial) {
502+
Map<String, String> store = new HashMap<>(initial);
503+
when(_firewallRuleDetailsDao.findDetail(Mockito.eq(lbRuleId), Mockito.anyString())).thenAnswer(invocation -> {
504+
String key = invocation.getArgument(1);
505+
String value = store.get(key);
506+
return value == null ? null : new FirewallRuleDetailVO(lbRuleId, key, value, true);
507+
});
508+
Mockito.doAnswer(invocation -> store.remove(invocation.<String>getArgument(1)))
509+
.when(_firewallRuleDetailsDao).removeDetail(Mockito.eq(lbRuleId), Mockito.anyString());
510+
Mockito.doAnswer(invocation -> store.put(invocation.getArgument(1), invocation.getArgument(2)))
511+
.when(_firewallRuleDetailsDao).addDetail(Mockito.eq(lbRuleId), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
512+
return store;
513+
}
514+
515+
@Test
516+
public void testUpdateLoadBalancerRuleRollsBackSettingsWhenApplyFails() throws Exception {
517+
setupUpdateLoadBalancerRule();
518+
Map<String, String> before = Map.of(LoadBalancer.KEEPALIVE, "true", LoadBalancer.IDLE_TIMEOUT, "5000");
519+
Map<String, String> store = fakeConnectionSettings(before);
520+
521+
// Drop keepalive, change idletimeout, add keepalivetimeout, then fail to reach the router
522+
UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd();
523+
ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId);
524+
ReflectionTestUtils.setField(cmd, "algorithm", "roundrobin");
525+
ReflectionTestUtils.setField(cmd, "lbProtocol", NetUtils.SSL_PROTO);
526+
ReflectionTestUtils.setField(cmd, "idleTimeout", 2000L);
527+
ReflectionTestUtils.setField(cmd, "keepAliveTimeout", 1000L);
528+
ReflectionTestUtils.setField(cmd, "cleanupConnectionSettings", true);
529+
when(loadBalancerMock.getAlgorithm()).thenReturn("roundrobin");
530+
when(loadBalancerMock.getLbProtocol()).thenReturn(NetUtils.SSL_PROTO);
531+
Mockito.doThrow(ResourceUnavailableException.class).when(lbr).applyLoadBalancerConfig(lbRuleId);
532+
when(_networkMgr.getProvidersForServiceInNetwork(networkMock, Network.Service.Lb))
533+
.thenReturn(Collections.singletonList(Network.Provider.VirtualRouter));
534+
535+
try {
536+
lbr.updateLoadBalancerRule(cmd);
537+
Assert.fail("Expected the update to fail when the router is unavailable");
538+
} catch (CloudRuntimeException e) {
539+
// expected
540+
}
541+
542+
Mockito.verify(lbr, times(1)).applyLoadBalancerConfig(lbRuleId);
543+
Assert.assertEquals(before, store);
544+
}
545+
546+
@Test
547+
public void testUpdateLoadBalancerRuleLeavesSettingsAloneWhenValidationFails() {
548+
AccountVO account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid");
549+
account.setId(accountId);
550+
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone",
551+
UUID.randomUUID().toString(), User.Source.UNKNOWN);
552+
CallContext.register(user, account);
553+
554+
when(_lbDao.findById(lbRuleId)).thenReturn(loadBalancerMock);
555+
when(loadBalancerMock.getNetworkId()).thenReturn(networkId);
556+
when(_networkDao.findById(networkId)).thenReturn(networkMock);
557+
LoadBalancingRule loadBalancingRule = Mockito.mock(LoadBalancingRule.class);
558+
Mockito.doReturn(loadBalancingRule).when(lbr).getLoadBalancerRuleToApply(loadBalancerMock);
559+
Mockito.doReturn(false).when(lbr).validateLbRule(loadBalancingRule);
560+
561+
UpdateLoadBalancerRuleCmd cmd = new UpdateLoadBalancerRuleCmd();
562+
ReflectionTestUtils.setField(cmd, ApiConstants.ID, lbRuleId);
563+
ReflectionTestUtils.setField(cmd, "idleTimeout", 2000L);
564+
565+
try {
566+
lbr.updateLoadBalancerRule(cmd);
567+
Assert.fail("Expected the provider to reject the update");
568+
} catch (InvalidParameterValueException e) {
569+
// expected
570+
}
571+
572+
verifyNoConnectionSettingWrites();
573+
}
497574
}

0 commit comments

Comments
 (0)