Skip to content

Commit c8cf0fd

Browse files
Check the per rule settings in the VR health check
haproxy_check.py compares the router's haproxy.cfg against what the management server thinks it configured. It knew about maxconn and the global idle timeout, so a rule's own keepalive and timeouts could drift without anything noticing. The health check data now carries the three per rule values, empty when the rule inherits, and the check validates them in that rule's listen section: ruleKeepAlive option http-keep-alive, or option httpclose ruleIdleTimeout timeout client, timeout server ruleKeepAliveTimeout timeout http-keep-alive The http mode test needed widening too. It read the offering flag alone, so a rule that sets keepalive itself stopped being checked at all: before port 80 and the offering has keepalive off after port 80 and (the rule sets keepalive, or the offering has it off) An older management server sends none of these keys, and a rule that sets nothing sends them empty. Both read as nothing to check. Signed-off-by: Brad House <bhouse@nexthop.ai>
1 parent 08327da commit c8cf0fd

2 files changed

Lines changed: 79 additions & 3 deletions

File tree

server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
import org.apache.cloudstack.utils.CloudStackVersion;
7979
import org.apache.cloudstack.utils.identity.ManagementServerNode;
8080
import org.apache.cloudstack.utils.usage.UsageUtils;
81+
import org.apache.cloudstack.resourcedetail.FirewallRuleDetailVO;
82+
import org.apache.cloudstack.resourcedetail.dao.FirewallRuleDetailsDao;
8183
import org.apache.commons.collections4.CollectionUtils;
8284
import org.apache.commons.lang3.ObjectUtils;
8385
import org.apache.commons.lang3.StringUtils;
@@ -199,6 +201,7 @@
199201
import com.cloud.network.router.VirtualRouter.RedundantState;
200202
import com.cloud.network.router.VirtualRouter.Role;
201203
import com.cloud.network.rules.FirewallRule;
204+
import com.cloud.network.rules.LoadBalancer;
202205
import com.cloud.network.rules.FirewallRule.Purpose;
203206
import com.cloud.network.rules.FirewallRuleVO;
204207
import com.cloud.network.rules.LoadBalancerContainer.Scheme;
@@ -352,6 +355,8 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V
352355
@Inject protected CommandSetupHelper _commandSetupHelper;
353356
@Inject ManagementServer mgr;
354357
@Inject
358+
private FirewallRuleDetailsDao _firewallRuleDetailsDao;
359+
@Inject
355360
RoutedIpv4Manager routedIpv4Manager;
356361
@Inject
357362
BGPService bgpService;
@@ -1711,7 +1716,9 @@ private void updateWithLbRules(final DomainRouterJoinVO routerJoinVO, final Stri
17111716
.append(",protocol=").append(appLoadBalancerVO.getLbProtocol());
17121717
}
17131718
loadBalancingData.append(",stickiness=").append(getStickinessPolicies(firewallRuleVO.getId()));
1714-
loadBalancingData.append(",keepAliveEnabled=").append(offering.isKeepAliveEnabled()).append(",vmIps=");
1719+
loadBalancingData.append(",keepAliveEnabled=").append(offering.isKeepAliveEnabled());
1720+
appendLbRuleConnectionSettings(loadBalancingData, firewallRuleVO.getId());
1721+
loadBalancingData.append(",vmIps=");
17151722
for (LoadBalancerVMMapVO vmMapVO : vmMapVOs) {
17161723
loadBalancingData.append(vmMapVO.getInstanceIp()).append(" ");
17171724
}
@@ -1720,6 +1727,21 @@ private void updateWithLbRules(final DomainRouterJoinVO routerJoinVO, final Stri
17201727
}
17211728
}
17221729

1730+
/**
1731+
* The per rule haproxy settings, so the health check can tell what the rule's own listen
1732+
* section should hold. Empty when the rule inherits.
1733+
*/
1734+
protected void appendLbRuleConnectionSettings(final StringBuilder loadBalancingData, long lbRuleId) {
1735+
loadBalancingData.append(",ruleKeepAlive=").append(lbRuleDetail(lbRuleId, LoadBalancer.KEEPALIVE))
1736+
.append(",ruleIdleTimeout=").append(lbRuleDetail(lbRuleId, LoadBalancer.IDLE_TIMEOUT))
1737+
.append(",ruleKeepAliveTimeout=").append(lbRuleDetail(lbRuleId, LoadBalancer.KEEPALIVE_TIMEOUT));
1738+
}
1739+
1740+
private String lbRuleDetail(long lbRuleId, String key) {
1741+
FirewallRuleDetailVO detail = _firewallRuleDetailsDao.findDetail(lbRuleId, key);
1742+
return detail == null ? "" : detail.getValue();
1743+
}
1744+
17231745
protected void updateWithLbRuleSslCertificates(final StringBuilder loadBalancingData, LoadBalancerVO loadBalancerVO, String sourceIp) {
17241746
if (NetUtils.SSL_PROTO.equals(loadBalancerVO.getLbProtocol())) {
17251747
final LbSslCert sslCert = _lbMgr.getLbSslCert(loadBalancerVO.getId());

systemvm/debian/root/health_checks/haproxy_check.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,52 @@ def checkIdletimeout(haproxyData, haCfgSections):
6969
return False
7070
return True
7171

72+
def hasOption(cfgSection, option):
73+
return option in cfgSection.get("option", [])
74+
75+
76+
def timeoutValue(cfgSection, kind):
77+
for tline in cfgSection.get("timeout", []):
78+
parts = tline.strip().split(None, 1)
79+
if len(parts) == 2 and parts[0].strip() == kind:
80+
return parts[1].strip()
81+
return None
82+
83+
84+
def checkRuleConnectionSettings(lbSec, cfgSection, secName, httpModeExpected):
85+
"""
86+
Per rule keepalive and timeouts, which live in the rule's own listen section rather than in
87+
defaults. An empty value means the rule inherits, so there is nothing to check.
88+
"""
89+
correct = True
90+
91+
keepAlive = lbSec.get("ruleKeepAlive", "")
92+
if keepAlive != "" and httpModeExpected:
93+
expected = "http-keep-alive" if keepAlive == "true" else "httpclose"
94+
if not hasOption(cfgSection, expected):
95+
print("Expected 'option " + expected + "' in " + secName)
96+
correct = False
97+
98+
idleTimeout = lbSec.get("ruleIdleTimeout", "")
99+
if idleTimeout != "":
100+
for kind in ("client", "server"):
101+
found = timeoutValue(cfgSection, kind)
102+
if found != idleTimeout:
103+
print("Expected 'timeout " + kind + " " + idleTimeout + "' in " + secName +
104+
" but found " + str(found))
105+
correct = False
106+
107+
keepAliveTimeout = lbSec.get("ruleKeepAliveTimeout", "")
108+
if keepAliveTimeout != "" and keepAlive == "true" and httpModeExpected:
109+
found = timeoutValue(cfgSection, "http-keep-alive")
110+
if found != keepAliveTimeout:
111+
print("Expected 'timeout http-keep-alive " + keepAliveTimeout + "' in " + secName +
112+
" but found " + str(found))
113+
correct = False
114+
115+
return correct
116+
117+
72118
def checkLoadBalance(haproxyData, haCfgSections):
73119
correct = True
74120
for lbSec in haproxyData:
@@ -94,12 +140,20 @@ def checkLoadBalance(haproxyData, haCfgSections):
94140
print("Incorrect bind string found. Expected " + bindStr + " but found " + cfgSection["bind"][0] + ".")
95141
correct = False
96142

97-
if (lbSec["sourcePortStart"] == "80" and lbSec["sourcePortEnd"] == "80" and lbSec["keepAliveEnabled"] == "false") \
98-
or (lbSec["stickiness"].find("AppCookie") != -1 or lbSec["stickiness"].find("LbCookie") != -1):
143+
# A rule that sets keepalive itself stays in http mode on port 80. Without it, the
144+
# network offering decides, and keepalive there drops the rule to tcp mode.
145+
onHttpPort = lbSec["sourcePortStart"] == "80" and lbSec["sourcePortEnd"] == "80"
146+
ruleKeepAlive = lbSec.get("ruleKeepAlive", "")
147+
httpModeExpected = (onHttpPort and (ruleKeepAlive != "" or lbSec["keepAliveEnabled"] == "false")) \
148+
or lbSec["stickiness"].find("AppCookie") != -1 or lbSec["stickiness"].find("LbCookie") != -1
149+
if httpModeExpected:
99150
if not ("mode" in cfgSection and cfgSection["mode"][0] == "http"):
100151
print("Expected HTTP mode but not found")
101152
correct = False
102153

154+
if not checkRuleConnectionSettings(lbSec, cfgSection, secName, httpModeExpected):
155+
correct = False
156+
103157
expectedServerIps = lbSec["vmIps"].split(" ")
104158
for expectedServerIp in expectedServerIps:
105159
pattern = expectedServerIp + ":" + \

0 commit comments

Comments
 (0)