haproxy.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "text/template"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. computeapi "yunion.io/x/onecloud/pkg/apis/compute"
  26. compute_models "yunion.io/x/onecloud/pkg/compute/models"
  27. agentutils "yunion.io/x/onecloud/pkg/lbagent/utils"
  28. )
  29. var haproxyConfigErrNop = errors.Error("nop haproxy config snippet")
  30. type GenHaproxyConfigsResult struct {
  31. LoadbalancersEnabled []*Loadbalancer
  32. }
  33. func (b *LoadbalancerCorpus) GenHaproxyToplevelConfig(dir string, opts *AgentParams) error {
  34. buf := bytes.NewBufferString("# yunion lb auto-generated 00-haproxy.cfg\n")
  35. haproxyConfigTmpl := opts.HaproxyConfigTmpl
  36. err := haproxyConfigTmpl.Execute(buf, opts.Data)
  37. if err != nil {
  38. return err
  39. }
  40. data := buf.Bytes()
  41. p := filepath.Join(dir, "00-haproxy.cfg")
  42. err = ioutil.WriteFile(p, data, agentutils.FileModeFile)
  43. if err != nil {
  44. return err
  45. }
  46. return nil
  47. }
  48. func (b *LoadbalancerCorpus) GenHaproxyConfigs(dir string, opts *AgentParams) (*GenHaproxyConfigsResult, error) {
  49. if len(b.LoadbalancerCertificates) > 0 {
  50. certsBase := filepath.Join(dir, "certs")
  51. certsBaseFinal := filepath.Join(agentutils.DirStagingToFinal(dir), "certs")
  52. err := os.MkdirAll(certsBase, agentutils.FileModeDirSensitive)
  53. if err != nil {
  54. return nil, fmt.Errorf("mkdir %s: %s", certsBase, err)
  55. }
  56. {
  57. p := filepath.Join(dir, "01-haproxy.cfg")
  58. lines := []string{
  59. "global",
  60. fmt.Sprintf(" crt-base %s", certsBaseFinal),
  61. "",
  62. }
  63. s := strings.Join(lines, "\n")
  64. err := os.WriteFile(p, []byte(s), agentutils.FileModeFile)
  65. if err != nil {
  66. return nil, fmt.Errorf("write 01-haproxy.cfg: %s", err)
  67. }
  68. }
  69. for _, lbcert := range b.LoadbalancerCertificates {
  70. d := []byte(lbcert.Certificate)
  71. if len(d) > 0 && d[len(d)-1] != '\n' {
  72. d = append(d, '\n')
  73. }
  74. d = append(d, []byte(lbcert.PrivateKey)...)
  75. fn := fmt.Sprintf("%s.pem", lbcert.Id)
  76. p := filepath.Join(certsBase, fn)
  77. err := os.WriteFile(p, d, agentutils.FileModeFileSensitive)
  78. if err != nil {
  79. return nil, fmt.Errorf("write cert %s: %s", lbcert.Id, err)
  80. }
  81. }
  82. }
  83. for _, lbacl := range b.LoadbalancerAcls {
  84. cidrs := []string{}
  85. if lbacl.AclEntries != nil {
  86. for _, aclEntry := range *lbacl.AclEntries {
  87. cidrs = append(cidrs, aclEntry.Cidr)
  88. }
  89. }
  90. if len(cidrs) > 0 {
  91. s := fmt.Sprintf("## loadbalancer acl %s(%s)\n", lbacl.Name, lbacl.Id)
  92. s += strings.Join(cidrs, "\n")
  93. s += "\n"
  94. p := filepath.Join(dir, "acl-"+lbacl.Id)
  95. err := os.WriteFile(p, []byte(s), agentutils.FileModeFile)
  96. if err != nil {
  97. return nil, err
  98. }
  99. }
  100. }
  101. r := &GenHaproxyConfigsResult{
  102. LoadbalancersEnabled: []*Loadbalancer{},
  103. }
  104. for _, lb := range b.Loadbalancers {
  105. if lb.ClusterId != opts.AgentModel.ClusterId {
  106. continue
  107. }
  108. if lb.Status != "enabled" {
  109. continue
  110. }
  111. if lb.Address == "" {
  112. continue
  113. }
  114. if len(lb.Listeners) == 0 {
  115. continue
  116. }
  117. buf := bytes.NewBufferString(fmt.Sprintf("## loadbalancer %s(%s)\n\n", lb.Name, lb.Id))
  118. hasActiveListener := false
  119. for _, listener := range lb.Listeners {
  120. if listener.Status != "enabled" {
  121. continue
  122. }
  123. var err error
  124. switch listener.ListenerType {
  125. case "http", "https":
  126. err = b.genHaproxyConfigHttp(buf, listener, opts)
  127. case "tcp":
  128. err = b.genHaproxyConfigTcp(buf, listener, opts)
  129. case "udp":
  130. // we record it for use in keepalived, gobetween conf gen
  131. r.LoadbalancersEnabled = append(r.LoadbalancersEnabled, lb)
  132. continue
  133. default:
  134. log.Infof("haproxy: ignore listener type %s", listener.ListenerType)
  135. continue
  136. }
  137. if err == haproxyConfigErrNop {
  138. continue
  139. }
  140. if err != nil {
  141. return nil, err
  142. }
  143. hasActiveListener = true
  144. buf.WriteString("\n\n") // listeners sep lines
  145. }
  146. if hasActiveListener {
  147. r.LoadbalancersEnabled = append(r.LoadbalancersEnabled, lb)
  148. d := buf.Bytes()
  149. if len(d) > 1 { // this is for sure because of "\n\n"
  150. d = d[:len(d)-2]
  151. }
  152. fn := fmt.Sprintf("%s.%s", lb.Id, agentutils.HaproxyCfgExt)
  153. p := filepath.Join(dir, fn)
  154. err := os.WriteFile(p, d, agentutils.FileModeFile)
  155. if err != nil {
  156. return nil, err
  157. }
  158. }
  159. }
  160. return r, nil
  161. }
  162. func (b *LoadbalancerCorpus) genHaproxyConfigCommon(lb *Loadbalancer, listener *LoadbalancerListener, opts *AgentParams) (map[string]interface{}, error) {
  163. data := map[string]interface{}{
  164. "comment": fmt.Sprintf("%s(%s)", listener.Name, listener.Id),
  165. "id": listener.Id,
  166. "listener_type": listener.ListenerType,
  167. }
  168. {
  169. address := lb.GetAddress()
  170. bind := fmt.Sprintf("%s:%d", address, listener.ListenerPort)
  171. if listener.ListenerType == "https" && listener.certificate != nil {
  172. bind += fmt.Sprintf(" ssl crt %s.pem", listener.certificate.Id)
  173. if listener.TLSCipherPolicy != "" {
  174. policy := agentutils.HaproxySslPolicy(listener.TLSCipherPolicy)
  175. if policy != nil {
  176. bind += fmt.Sprintf(" ssl-min-ver %s", policy.SslMinVer)
  177. }
  178. }
  179. if listener.EnableHttp2 {
  180. bind += " alpn h2,http/1.1"
  181. }
  182. }
  183. data["bind"] = bind
  184. }
  185. {
  186. agentHaproxyParams := opts.AgentModel.Params.Haproxy
  187. if agentHaproxyParams.GlobalLog != "" {
  188. switch listener.ListenerType {
  189. case "http", "https":
  190. if agentHaproxyParams.LogHttp {
  191. data["log"] = true
  192. }
  193. case "tcp":
  194. if agentHaproxyParams.LogTcp {
  195. data["log"] = true
  196. }
  197. }
  198. }
  199. }
  200. if listener.AclStatus == "on" {
  201. lbacl, ok := b.LoadbalancerAcls[listener.AclId]
  202. if ok && lbacl.AclEntries != nil && len(*lbacl.AclEntries) > 0 {
  203. var action, cond string
  204. switch listener.ListenerType {
  205. case "tcp":
  206. action = "tcp-request connection reject"
  207. case "http", "https":
  208. action = "http-request deny"
  209. }
  210. switch listener.AclType {
  211. case "black":
  212. cond = "if"
  213. case "white":
  214. cond = "unless"
  215. }
  216. if action != "" && cond != "" {
  217. acl := fmt.Sprintf("%s %s { src -f acl-%s }", action, cond, listener.AclId)
  218. data["acl"] = acl
  219. }
  220. }
  221. }
  222. {
  223. // NOTE timeout tunnel is not set. We may need to prepare a
  224. // default section for each frontend/listener
  225. timeoutsMap := map[string]int{
  226. "client_request_timeout": listener.ClientRequestTimeout,
  227. "client_idle_timeout": listener.ClientIdleTimeout,
  228. }
  229. for k, v := range timeoutsMap {
  230. if v > 0 {
  231. data[k] = fmt.Sprintf("%ds", v)
  232. }
  233. }
  234. }
  235. return data, nil
  236. }
  237. func (b *LoadbalancerCorpus) genHaproxyConfigBackend(data map[string]interface{}, lb *Loadbalancer, listener *LoadbalancerListener, backendGroup *LoadbalancerBackendGroup) error {
  238. var mode string
  239. var balanceAlgorithm string
  240. var httpCheck, httpCheckExpect string
  241. var checkEnable, httpCheckEnable bool
  242. var err error
  243. { // mode
  244. switch listener.ListenerType {
  245. case "tcp":
  246. mode = "tcp"
  247. case "http", "https":
  248. mode = "http"
  249. default:
  250. return fmt.Errorf("haproxy: unsupported listener type %s", listener.ListenerType)
  251. }
  252. }
  253. { // balance algorithm
  254. balanceAlgorithm, err = agentutils.HaproxyBalanceAlgorithm(listener.Scheduler)
  255. if err != nil {
  256. return err
  257. }
  258. }
  259. { // (http) check enabled?
  260. if listener.HealthCheck == "on" {
  261. checkEnable = true
  262. if listener.HealthCheckType == "http" {
  263. httpCheckEnable = true
  264. httpCheck = agentutils.HaproxyConfigHttpCheck(
  265. listener.HealthCheckURI, listener.HealthCheckDomain)
  266. httpCheckExpect = agentutils.HaproxyConfigHttpCheckExpect(
  267. listener.HealthCheckHttpCode)
  268. }
  269. }
  270. }
  271. var stickySessionEnable bool
  272. if mode == "http" && listener.StickySession == "on" {
  273. // sticky session
  274. stickyCookie := ""
  275. switch listener.StickySessionType {
  276. case "insert":
  277. cookie := listener.StickySessionCookie
  278. if cookie == "" {
  279. cookie = "SERVERID"
  280. }
  281. stickyCookie = fmt.Sprintf("cookie %s insert indirect nocache", cookie)
  282. if maxIdle := listener.StickySessionCookieTimeout; maxIdle > 0 {
  283. stickyCookie += fmt.Sprintf(" maxidle %ds", maxIdle)
  284. }
  285. case "server":
  286. cookie := listener.StickySessionCookie
  287. if cookie != "" {
  288. stickyCookie = fmt.Sprintf("cookie %q rewrite", cookie)
  289. }
  290. }
  291. if stickyCookie != "" {
  292. data["stickyCookie"] = stickyCookie
  293. stickySessionEnable = true
  294. }
  295. }
  296. {
  297. listenerSendProxy, err := agentutils.HaproxySendProxy(listener.SendProxy)
  298. if err != nil {
  299. return fmt.Errorf("listener %s(%s): %v", listener.Name, listener.Id, err)
  300. }
  301. serverLines := []string{}
  302. for _, backend := range backendGroup.Backends {
  303. address, port := backend.GetAddressPort()
  304. serverLine := fmt.Sprintf("server %s %s:%d", backend.Id, address, port)
  305. if listener.Scheduler == "rr" {
  306. serverLine += " weight 1"
  307. } else {
  308. weight := backend.Weight
  309. if weight <= 0 {
  310. weight = 1
  311. }
  312. serverLine += fmt.Sprintf(" weight %d", weight)
  313. }
  314. if checkEnable {
  315. /*
  316. * The inter parameter changes the interval between checks; it defaults to two seconds.
  317. * The fall parameter sets how many failed checks are allowed; it defaults to three.
  318. * The rise parameter sets how many passing checks there must be before returning a previously
  319. * failed server to the rotation; it defaults to two.
  320. */
  321. rise := listener.HealthCheckRise
  322. fall := listener.HealthCheckFall
  323. intv := listener.HealthCheckInterval
  324. if rise == 0 {
  325. rise = 2
  326. }
  327. if fall == 0 {
  328. fall = 3
  329. }
  330. if intv == 0 {
  331. intv = 2
  332. }
  333. serverLine += fmt.Sprintf(" check rise %d fall %d inter %ds", rise, fall, intv)
  334. }
  335. if stickySessionEnable {
  336. serverLine += fmt.Sprintf(" cookie %q", backend.Id)
  337. }
  338. if listenerSendProxy != "" {
  339. serverLine += " " + listenerSendProxy
  340. } else if sendProxy, err := agentutils.HaproxySendProxy(backend.SendProxy); err != nil {
  341. return fmt.Errorf("backend %s(%s): %v", backend.Name, backend.Id, err)
  342. } else if sendProxy != "" {
  343. serverLine += " " + sendProxy
  344. } else {
  345. // nothing to do
  346. }
  347. if backend.Ssl == "on" {
  348. serverLine += " ssl"
  349. serverLine += " verify none"
  350. serverLine += " check-ssl"
  351. }
  352. serverLines = append(serverLines, serverLine)
  353. }
  354. data["servers"] = serverLines
  355. }
  356. if listener.HealthCheckTimeout > 0 {
  357. data["timeout_check"] = fmt.Sprintf("timeout check %ds", listener.HealthCheckTimeout)
  358. }
  359. {
  360. timeoutsMap := map[string]int{
  361. "backend_connect_timeout": listener.BackendConnectTimeout,
  362. "backend_idle_timeout": listener.BackendIdleTimeout,
  363. "backend_tunnel_timeout": listener.BackendIdleTimeout,
  364. }
  365. for k, v := range timeoutsMap {
  366. if v > 0 {
  367. data[k] = fmt.Sprintf("%ds", v)
  368. }
  369. }
  370. }
  371. data["mode"] = mode
  372. data["balanceAlgorithm"] = balanceAlgorithm
  373. if httpCheckEnable {
  374. data["httpCheck"] = httpCheck
  375. data["httpCheckExpect"] = httpCheckExpect
  376. }
  377. return nil
  378. }
  379. func (b *LoadbalancerCorpus) genHaproxyConfigHttpRate(data map[string]interface{}, requestRate, requestRatePerSrc int) error {
  380. periodSecond := 10
  381. id := data["id"].(string)
  382. dummyBackends := []map[string]string{}
  383. rateRules := []string{}
  384. // order matters here: every src uses up his own quota before touching
  385. // the shared one
  386. if requestRatePerSrc > 0 {
  387. idPerSrc := id + "_persrc"
  388. dummyBackends = append(dummyBackends, map[string]string{
  389. "id": idPerSrc,
  390. "stick_table": fmt.Sprintf("stick-table type ip size 1m expire 1m store http_req_rate(%ds)", periodSecond),
  391. })
  392. rateRules = append(rateRules,
  393. fmt.Sprintf("http-request deny deny_status 429 if { src_http_req_rate(%s) gt %d }",
  394. idPerSrc, requestRatePerSrc*periodSecond),
  395. fmt.Sprintf("http-request track-sc0 src table %s",
  396. idPerSrc))
  397. }
  398. if requestRate > 0 {
  399. idTotal := id + "_total"
  400. dummyBackends = append(dummyBackends, map[string]string{
  401. "id": idTotal,
  402. "stick_table": fmt.Sprintf("stick-table type integer size 1 expire 1m store http_req_rate(%ds)", periodSecond),
  403. })
  404. rateRules = append(rateRules,
  405. fmt.Sprintf("http-request deny deny_status 429 if { int(1),table_http_req_rate(%s) gt %d }",
  406. idTotal, requestRate*periodSecond),
  407. fmt.Sprintf("http-request track-sc1 int(1) table %s",
  408. idTotal))
  409. }
  410. data["rate_rules"] = rateRules
  411. data["dummy_backends"] = dummyBackends
  412. return nil
  413. }
  414. func (b *LoadbalancerCorpus) haproxyRedirectLine(r *compute_models.SLoadbalancerHTTPRedirect, listenerType string) string {
  415. var (
  416. code = r.RedirectCode
  417. scheme = r.RedirectScheme
  418. host = r.RedirectHost
  419. path = r.RedirectPath
  420. )
  421. if scheme == "" {
  422. scheme = strings.ToLower(listenerType)
  423. }
  424. if host == "" {
  425. host = "%[req.hdr(host)]"
  426. }
  427. if path == "" {
  428. path = "%[capture.req.uri]"
  429. }
  430. line := fmt.Sprintf("http-request redirect code %d location %s://%s%s", code, scheme, host, path)
  431. return line
  432. }
  433. func (b *LoadbalancerCorpus) genHaproxyConfigHttp(buf *bytes.Buffer, listener *LoadbalancerListener, opts *AgentParams) error {
  434. var (
  435. lb = listener.loadbalancer
  436. )
  437. data, err := b.genHaproxyConfigCommon(lb, listener, opts)
  438. if err != nil {
  439. return errors.Wrap(err, "genHaproxyConfigCommon for http listener")
  440. }
  441. {
  442. // NOTE add X-Real-IP if needed
  443. //
  444. // http-request set-header X-Client-IP %[src]
  445. //
  446. data["xforwardedfor"] = listener.XForwardedFor
  447. data["gzip"] = listener.Gzip
  448. }
  449. var (
  450. rules = listener.rules.OrderedEnabledList()
  451. ruleLines = []string{}
  452. backends = []interface{}{}
  453. ruleBackendIdGen = func(id string) string {
  454. return fmt.Sprintf("backends_rule-%s", id)
  455. }
  456. )
  457. { // dispatch
  458. for _, rule := range rules {
  459. sufCond := ""
  460. if rule.Domain != "" || rule.Path != "" {
  461. sufCond += " if"
  462. if rule.Domain != "" {
  463. sufCond += fmt.Sprintf(" { hdr_dom(host) %q }", rule.Domain)
  464. }
  465. if rule.Path != "" {
  466. sufCond += fmt.Sprintf(" { path_beg %q }", rule.Path)
  467. }
  468. }
  469. if rule.Redirect == computeapi.LB_REDIRECT_OFF {
  470. // use_backend rule.Id if xx
  471. ruleLine := fmt.Sprintf("use_backend %s", ruleBackendIdGen(rule.Id))
  472. ruleLines = append(ruleLines, ruleLine+sufCond)
  473. continue
  474. } else if rule.Redirect == computeapi.LB_REDIRECT_RAW {
  475. // http-request redirect ... if xx
  476. ruleLine := b.haproxyRedirectLine(&rule.SLoadbalancerHTTPRedirect, listener.ListenerType)
  477. ruleLines = append(ruleLines, ruleLine+sufCond)
  478. } else {
  479. return haproxyConfigErrNop
  480. }
  481. }
  482. // default is a raw redirect
  483. if listener.Redirect == computeapi.LB_REDIRECT_RAW {
  484. ruleLines = append(ruleLines,
  485. b.haproxyRedirectLine(&listener.SLoadbalancerHTTPRedirect, listener.ListenerType),
  486. )
  487. }
  488. data["rules"] = ruleLines
  489. }
  490. { // those with backend group
  491. // rules backend group
  492. for _, rule := range rules {
  493. // NOTE dup is ok
  494. if rule.BackendGroupId == "" {
  495. // just in case
  496. continue
  497. }
  498. if rule.Redirect != computeapi.LB_REDIRECT_OFF {
  499. continue
  500. }
  501. backendGroup := lb.BackendGroups[rule.BackendGroupId]
  502. backendData := map[string]interface{}{
  503. "comment": fmt.Sprintf("rule %s(%s) backendGroup %s(%s)",
  504. rule.Name, rule.Id,
  505. backendGroup.Name, backendGroup.Id),
  506. "id": ruleBackendIdGen(rule.Id),
  507. }
  508. if err := b.genHaproxyConfigBackend(backendData, lb, listener, backendGroup); err != nil {
  509. return err
  510. }
  511. if err := b.genHaproxyConfigHttpRate(backendData, rule.HTTPRequestRate, rule.HTTPRequestRatePerSrc); err != nil {
  512. return err
  513. }
  514. backends = append(backends, backendData)
  515. }
  516. // default backend group
  517. if listener.Redirect == computeapi.LB_REDIRECT_OFF && listener.BackendGroupId != "" {
  518. backendGroup := lb.BackendGroups[listener.BackendGroupId]
  519. backendData := map[string]interface{}{
  520. "comment": fmt.Sprintf("listener %s(%s) default backendGroup %s(%s)",
  521. listener.Name, listener.Id,
  522. backendGroup.Name, backendGroup.Id),
  523. "id": fmt.Sprintf("backends_listener_default-%s", listener.Id),
  524. }
  525. if err := b.genHaproxyConfigBackend(backendData, lb, listener, backendGroup); err != nil {
  526. return err
  527. }
  528. if err := b.genHaproxyConfigHttpRate(backendData, listener.HTTPRequestRate, listener.HTTPRequestRatePerSrc); err != nil {
  529. return err
  530. }
  531. backends = append(backends, backendData)
  532. data["default_backend"] = backendData
  533. }
  534. data["backends"] = backends
  535. }
  536. if len(ruleLines) == 0 && len(backends) == 0 {
  537. // nothing to serve
  538. return haproxyConfigErrNop
  539. }
  540. err = haproxyConfigTmpl.ExecuteTemplate(buf, "httpListen", data)
  541. return err
  542. }
  543. func (b *LoadbalancerCorpus) genHaproxyConfigTcp(buf *bytes.Buffer, listener *LoadbalancerListener, opts *AgentParams) error {
  544. lb := listener.loadbalancer
  545. data, err := b.genHaproxyConfigCommon(lb, listener, opts)
  546. if err != nil {
  547. return errors.Wrap(err, "genHaproxyConfigCommon for tcp listener")
  548. }
  549. if listener.BackendGroupId != "" {
  550. backendGroup := lb.BackendGroups[listener.BackendGroupId]
  551. backendData := map[string]interface{}{
  552. "comment": fmt.Sprintf("listener %s(%s) backendGroup %s(%s)",
  553. listener.Name, listener.Id,
  554. backendGroup.Name, backendGroup.Id),
  555. "id": fmt.Sprintf("backends_listener-%s", listener.Id),
  556. }
  557. err := b.genHaproxyConfigBackend(backendData, lb, listener, backendGroup)
  558. if err != nil {
  559. return err
  560. }
  561. data["backend"] = backendData
  562. err = haproxyConfigTmpl.ExecuteTemplate(buf, "tcpListen", data)
  563. return err
  564. }
  565. return haproxyConfigErrNop
  566. }
  567. var haproxyConfigTmpl = template.Must(template.New("").Parse(`
  568. {{ define "tcpListen" -}}
  569. # {{ .listener_type }} listener: {{ .comment }}
  570. listen {{ .id }}
  571. bind {{ .bind }}
  572. mode tcp
  573. {{- println }}
  574. {{- if .log }} {{ println "option tcplog" }} {{- end }}
  575. {{- if .acl }} {{ println .acl }} {{- end}}
  576. {{- if .client_idle_timeout }} timeout client {{ println .client_idle_timeout }} {{- end}}
  577. default_backend {{ .backend.id }}
  578. {{ template "backend" .backend }}
  579. {{- end }}
  580. {{ define "httpListen" -}}
  581. # {{ .listener_type }} listener: {{ .comment }}
  582. {{- range .dummy_backends }}
  583. backend {{ .id }}
  584. {{ println .stick_table }}
  585. {{- end }}
  586. frontend {{ .id }}
  587. bind {{ .bind }}
  588. mode http
  589. {{- println }}
  590. {{- if .log }} {{ println "option httplog clf" }} {{- end }}
  591. {{- if .acl }} {{ println .acl }} {{- end}}
  592. {{- if .client_request_timeout }} timeout http-request {{ println .client_request_timeout }} {{- end}}
  593. {{- if .client_idle_timeout }} timeout http-keep-alive {{ println .client_idle_timeout }} {{- end}}
  594. {{- if .xforwardedfor }} {{ println "option forwardfor" }} {{- end}}
  595. {{- if .gzip }} {{ println "compression algo gzip" }} {{- end}}
  596. {{- range .rules }} {{ println . }} {{- end }}
  597. {{- if .default_backend.id }} default_backend {{ println .default_backend.id }} {{- end }}
  598. {{- range .backends }}
  599. {{- template "backend" . }}
  600. {{- end }}
  601. {{- end }}
  602. {{ define "backend" -}}
  603. # {{ .comment }}
  604. {{- range .dummy_backends }}
  605. backend {{ .id }}
  606. {{ println .stick_table }}
  607. {{- end }}
  608. backend {{ .id }}
  609. mode {{ .mode }}
  610. balance {{ .balanceAlgorithm }}
  611. {{- println }}
  612. {{- range .rate_rules }} {{ println . }} {{- end }}
  613. {{- if .backend_connect_timeout }} timeout connect {{ println .backend_connect_timeout }} {{- end}}
  614. {{- if .backend_idle_timeout }} timeout server {{ println .backend_idle_timeout }} {{- end}}
  615. {{- if .timeout_check }} {{ println .timeout_check }} {{- end }}
  616. {{- if .stickyCookie }} {{ println .stickyCookie }} {{- end }}
  617. {{- if .httpCheck }} {{ println .httpCheck }} {{- end }}
  618. {{- if .httpCheckExpect }} {{ println .httpCheckExpect }} {{- end }}
  619. {{- range .servers }} {{ println . }} {{- end }}
  620. {{- end }}
  621. `))