From 82b7bea6622210e19a1a4dbf961c236ba07f316a Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 02:55:34 +0530 Subject: [PATCH 01/13] fix: haproxy #369 perf issue --- haproxy_manager/backend_switch.go | 100 ++++ haproxy_manager/custom_link.go | 32 ++ haproxy_manager/frontend.go | 150 +++++ haproxy_manager/link.go | 524 ------------------ haproxy_manager/predefined_link.go | 199 +++++++ haproxy_manager/redirect_rule.go | 205 +++++++ .../process_ingress_rule_delete_request.go | 5 +- 7 files changed, 689 insertions(+), 526 deletions(-) create mode 100644 haproxy_manager/backend_switch.go create mode 100644 haproxy_manager/custom_link.go create mode 100644 haproxy_manager/frontend.go delete mode 100644 haproxy_manager/link.go create mode 100644 haproxy_manager/predefined_link.go create mode 100644 haproxy_manager/redirect_rule.go diff --git a/haproxy_manager/backend_switch.go b/haproxy_manager/backend_switch.go new file mode 100644 index 0000000000..b31f0c237d --- /dev/null +++ b/haproxy_manager/backend_switch.go @@ -0,0 +1,100 @@ +package haproxymanager + +import ( + "bytes" + "encoding/json" + "io" + "strconv" + "strings" +) + +func (s Manager) CreateBackendSwitch(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) error { + params := QueryParameters{} + params.add("transaction_id", transactionId) + params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) + var reqBody map[string]interface{} + // for tcp mode, just add use_backend rule without ACL + if listenerMode == TCPMode { + reqBody = map[string]interface{}{ + "index": 0, + "name": backendName, + } + } else { + reqBody = map[string]interface{}{ + "cond": "if", + "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(domainName) + `:` + strconv.Itoa(bindPort) + ` }`, + "index": 0, + "name": backendName, + } + } + reqBodyBytes, err := json.Marshal(reqBody) + if err != nil { + return err + } + // Send request + addBackendSwitchRes, addBackendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", params, bytes.NewReader(reqBodyBytes)) + if addBackendSwitchErr != nil || !isValidStatusCode(addBackendSwitchRes.StatusCode) { + return addBackendSwitchErr + } + defer func(body io.ReadCloser) { + _ = body.Close() + }(addBackendSwitchRes.Body) + return nil +} + +func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) (int, error) { + params := QueryParameters{} + params.add("transaction_id", transactionId) + params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) + // Send request + getBackendSwitchRes, getBackendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", params) + if getBackendSwitchErr != nil || !isValidStatusCode(getBackendSwitchRes.StatusCode) { + return -1, getBackendSwitchErr + } + defer func(body io.ReadCloser) { + _ = body.Close() + }(getBackendSwitchRes.Body) + // Parse response + var backendSwitchRules []map[string]interface{} + err := json.NewDecoder(getBackendSwitchRes.Body).Decode(&backendSwitchRules) + if err != nil { + return -1, err + } + for _, rule := range backendSwitchRules { + if rule["name"] == backendName && + rule["cond"] == "if" && + rule["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(domainName)+`:`+strconv.Itoa(bindPort)+` }` { + return int(rule["index"].(float64)), nil + } + } + return -1, nil +} + +func (s Manager) DeleteBackendSwitch(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) error { + // check if frontend already exists + isFrontendExist, _ := s.IsFrontendExist(transactionId, listenerMode, bindPort) + if !isFrontendExist { + return nil + } + // fetch backend switch index + index, err := s.FetchBackendSwitchIndex(transactionId, listenerMode, bindPort, backendName, domainName) + if err != nil { + return err + } + // not found + if index == -1 { + return nil + } + // Build query parameters + params := QueryParameters{} + params.add("transaction_id", transactionId) + params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) + deleteReq, err := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(index), params) + if err != nil || !isValidStatusCode(deleteReq.StatusCode) { + return err + } + defer func(body io.ReadCloser) { + _ = body.Close() + }(deleteReq.Body) + return nil +} diff --git a/haproxy_manager/custom_link.go b/haproxy_manager/custom_link.go new file mode 100644 index 0000000000..de989efe98 --- /dev/null +++ b/haproxy_manager/custom_link.go @@ -0,0 +1,32 @@ +package haproxymanager + +import "errors" + +// AddTCPLink Add TCP Frontend to HAProxy configuration +// -- Manage ACLs with frontend [port{required} and domain_name{optional}] +// -- Manage rules with frontend and backend switch +func (s Manager) AddTCPLink(transactionId string, backendName string, port int, domainName string, listenerMode ListenerMode, restrictedPorts []int) error { + if IsPortRestrictedForManualConfig(port, restrictedPorts) { + return errors.New("port is restricted for manual configuration") + } + // Add Frontend + err := s.AddFrontend(transactionId, listenerMode, port, restrictedPorts) + if err != nil { + return err + } + // Add Backend Switch + err = s.CreateBackendSwitch(transactionId, listenerMode, port, backendName, domainName) + return err +} + +// DeleteTCPLink Delete TCP Frontend from HAProxy configuration +func (s Manager) DeleteTCPLink(transactionId string, backendName string, port int, domainName string, listenerMode ListenerMode) error { + // Delete Backend Switch + err := s.DeleteBackendSwitch(transactionId, listenerMode, port, backendName, domainName) + if err != nil { + return err + } + // Delete Frontend + err = s.DeleteFrontend(transactionId, listenerMode, port) + return err +} diff --git a/haproxy_manager/frontend.go b/haproxy_manager/frontend.go new file mode 100644 index 0000000000..b79261bbe9 --- /dev/null +++ b/haproxy_manager/frontend.go @@ -0,0 +1,150 @@ +package haproxymanager + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "strconv" +) + +var defaultBackend = "error_backend" + +func GenerateFrontendName(listenerMode ListenerMode, port int) string { + return "fe_" + string(listenerMode) + "_" + strconv.Itoa(port) +} + +func (s Manager) AddFrontend(transactionId string, listenerMode ListenerMode, bindPort int, restrictedPorts []int) error { + frontendName := GenerateFrontendName(listenerMode, bindPort) + if IsPortRestrictedForManualConfig(bindPort, restrictedPorts) { + return errors.New("port is restricted for manual configuration") + } + // check if frontend already exists + isFrontendExist, _ := s.IsFrontendExist(transactionId, listenerMode, bindPort) + if isFrontendExist { + return nil + } + // create frontend + params := QueryParameters{} + params.add("transaction_id", transactionId) + body := map[string]interface{}{ + "maxconn": 6000, + "mode": listenerMode, + "name": frontendName, + } + body["default_backend"] = defaultBackend + bodyBytes, err := json.Marshal(body) + if err != nil { + return errors.New("failed to marshal frontend create request body") + } + // Send request + addTcpFrontendRes, addTcpFrontendErr := s.postRequest("/services/haproxy/configuration/frontends", params, bytes.NewReader(bodyBytes)) + if addTcpFrontendErr != nil || !isValidStatusCode(addTcpFrontendRes.StatusCode) { + // 409 status code means that frontend already exists + if addTcpFrontendRes.StatusCode == 409 { + return nil + } + return errors.New("failed to add tcp frontend") + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(addTcpFrontendRes.Body) + // create bind + bindParams := QueryParameters{} + bindParams.add("transaction_id", transactionId) + bindParams.add("frontend", frontendName) + bindBody := map[string]interface{}{ + "ssl": false, + "port": bindPort, + } + bindBodyBytes, err := json.Marshal(bindBody) + if err != nil { + return errors.New("failed to marshal bind create request body") + } + // Send request + addBindRes, addBindErr := s.postRequest("/services/haproxy/configuration/binds", bindParams, bytes.NewReader(bindBodyBytes)) + if addBindErr != nil || !isValidStatusCode(addBindRes.StatusCode) { + return errors.New("failed to add port to frontend") + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(addBindRes.Body) + return nil +} + +func (s Manager) IsFrontendExist(transactionId string, listenerMode ListenerMode, bindPort int) (bool, error) { + frontendName := GenerateFrontendName(listenerMode, bindPort) + params := QueryParameters{} + params.add("transaction_id", transactionId) + // Send request to check if frontend exist + isFrontendExistRes, isFrontendExistErr := s.getRequest("/services/haproxy/configuration/frontends/"+frontendName, params) + if isFrontendExistErr != nil { + return false, errors.New("failed to check if frontend exist") + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(isFrontendExistRes.Body) + if isFrontendExistRes.StatusCode == 404 { + return false, nil + } + if isFrontendExistRes.StatusCode == 200 { + return true, nil + } + return false, errors.New("failed to check if frontend exist for unknown reason") +} + +func (s Manager) IsOtherSwitchingRuleExist(transactionId string, listenerMode ListenerMode, bindPort int) (bool, error) { + frontendName := GenerateFrontendName(listenerMode, bindPort) + params := QueryParameters{} + params.add("transaction_id", transactionId) + params.add("frontend", frontendName) + // Send request to fetch switching rules + switchingRulesRes, switchingRulesErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", params) + if switchingRulesErr != nil { + return false, errors.New("failed to check if switching rule exist") + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(switchingRulesRes.Body) + if switchingRulesRes.StatusCode == 200 { + switchingRules := map[string]interface{}{} + if err := json.NewDecoder(switchingRulesRes.Body).Decode(&switchingRules); err != nil { + return false, errors.New("failed to decode switching rules response") + } + switchingRulesArray := switchingRules["data"].([]interface{}) + return len(switchingRulesArray) > 0, nil + } + return false, errors.New("failed to check if switching rule exist for unknown reason") +} + +func (s Manager) DeleteFrontend(transactionId string, listenerMode ListenerMode, bindPort int) error { + // check if frontend exists + isFrontendExist, err := s.IsFrontendExist(transactionId, listenerMode, bindPort) + if err != nil { + return err + } + if !isFrontendExist { + return nil + } + // don't delete frontend if there are switching rules + isSwitchingRuleExist, err := s.IsOtherSwitchingRuleExist(transactionId, listenerMode, bindPort) + if err != nil { + return err + } + if isSwitchingRuleExist { + return nil + } + // delete frontend + frontendName := GenerateFrontendName(listenerMode, bindPort) + params := QueryParameters{} + params.add("transaction_id", transactionId) + // Send request + deleteFrontendRes, deleteFrontendErr := s.deleteRequest("/services/haproxy/configuration/frontends/"+frontendName, params) + if deleteFrontendErr != nil || !isValidStatusCode(deleteFrontendRes.StatusCode) { + return errors.New("failed to delete frontend") + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(deleteFrontendRes.Body) + return nil +} diff --git a/haproxy_manager/link.go b/haproxy_manager/link.go deleted file mode 100644 index d42b6dd535..0000000000 --- a/haproxy_manager/link.go +++ /dev/null @@ -1,524 +0,0 @@ -package haproxymanager - -import ( - "bytes" - "encoding/json" - "errors" - "io" - "log" - "strconv" - "strings" -) - -// AddHTTPLink Add HTTP Link [Backend Switch] to HAProxy configuration -// -- Manage ACLs with frontend [only domain_name] -// -- Manage rules with frontend and backend switch -func (s Manager) AddHTTPLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_http" - // Build query parameters - addBackendSwitchRequestQueryParams := QueryParameters{} - addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - addBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Add backend switch request body - addBackendSwitchRequestBody := map[string]interface{}{ - "cond": "if", - "cond_test": `{ hdr(host) -i ` + domainName + ` }`, - "index": 1, - "name": backendName, - } - addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) - if err != nil { - return errors.New("failed to marshal add_backend_switch_request_body") - } - // Send add backend switch request - backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to add backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddHTTPLink: ", err) - } - }(backendSwitchRes.Body) - return nil -} - -// DeleteHTTPLink Delete HTTP Link from HAProxy configuration -func (s Manager) DeleteHTTPLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_http" - // Build query parameters - fetchBackendSwitchRequestQueryParams := QueryParameters{} - fetchBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - fetchBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Fetch backend switch - backendSwitchRes, backendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", fetchBackendSwitchRequestQueryParams) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to fetch backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPLink: ", err) - } - }(backendSwitchRes.Body) - // Parse backend switch - backendSwitchData := map[string]interface{}{} - bodyBytes, err := io.ReadAll(backendSwitchRes.Body) - if err != nil { - return errors.New("failed to read backend switch response body") - } - err = json.Unmarshal(bodyBytes, &backendSwitchData) - if err != nil { - return errors.New("failed to parse backend switch response body") - } - // Find backend switch - backendSwitchDataArray := backendSwitchData["data"].([]interface{}) - backendSwitchDataArrayIndex := -1 - for i, backendSwitchDataArrayItem := range backendSwitchDataArray { - backendSwitchDataArrayItemMap := backendSwitchDataArrayItem.(map[string]interface{}) - if backendSwitchDataArrayItemMap["name"] == backendName && strings.Contains(backendSwitchDataArrayItemMap["cond_test"].(string), domainName) { - backendSwitchDataArrayIndex = i - break - } - } - if backendSwitchDataArrayIndex == -1 { - return errors.New("failed to find backend switch") - } - // Build query parameters - deleteBackendSwitchRequestQueryParams := QueryParameters{} - deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) - - // Delete backend switch - deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchDataArrayIndex), deleteBackendSwitchRequestQueryParams) - if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { - return errors.New("failed to delete backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPLink: ", err) - } - }(deleteBackendSwitchRes.Body) - return nil -} - -// AddHTTPSLink Add HTTPS Link [Backend Switch] to HAProxy configuration -// -- Manage ACLs with frontend [only domain_name] -// -- Manage rules with frontend and backend switch -func (s Manager) AddHTTPSLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_https" - // Build query parameters - addBackendSwitchRequestQueryParams := QueryParameters{} - addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - addBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Add backend switch request body - addBackendSwitchRequestBody := map[string]interface{}{ - "cond": "if", - "cond_test": `{ hdr(host) -i ` + domainName + ` }`, - "index": 1, - "name": backendName, - } - addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) - if err != nil { - return errors.New("failed to marshal add_backend_switch_request_body") - } - // Send add backend switch request - backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to add backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddHTTPSLink: ", err) - } - }(backendSwitchRes.Body) - return nil -} - -// DeleteHTTPSLink Delete HTTPS Link from HAProxy configuration -func (s Manager) DeleteHTTPSLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_https" - // Build query parameters - fetchBackendSwitchRequestQueryParams := QueryParameters{} - fetchBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - fetchBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Fetch backend switch - backendSwitchRes, backendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", fetchBackendSwitchRequestQueryParams) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to fetch backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPSLink: ", err) - } - }(backendSwitchRes.Body) - // Parse backend switch - backendSwitchData := map[string]interface{}{} - bodyBytes, err := io.ReadAll(backendSwitchRes.Body) - if err != nil { - return errors.New("failed to read backend switch response body") - } - err = json.Unmarshal(bodyBytes, &backendSwitchData) - if err != nil { - return errors.New("failed to parse backend switch response body") - } - // Find backend switch - backendSwitchDataArray := backendSwitchData["data"].([]interface{}) - backendSwitchDataArrayIndex := -1 - for i, backendSwitchDataArrayItem := range backendSwitchDataArray { - backendSwitchDataArrayItemMap := backendSwitchDataArrayItem.(map[string]interface{}) - if backendSwitchDataArrayItemMap["name"] == backendName && strings.Contains(backendSwitchDataArrayItemMap["cond_test"].(string), domainName) { - backendSwitchDataArrayIndex = i - break - } - } - if backendSwitchDataArrayIndex == -1 { - return errors.New("failed to find backend switch") - } - // Build query parameters - deleteBackendSwitchRequestQueryParams := QueryParameters{} - deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) - - // Delete backend switch - deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchDataArrayIndex), deleteBackendSwitchRequestQueryParams) - if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { - return errors.New("failed to delete backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPSLink: ", err) - } - }(deleteBackendSwitchRes.Body) - return nil -} - -// AddTCPLink Add TCP Frontend to HAProxy configuration -// -- Manage ACLs with frontend [port{required} and domain_name{optional}] -// -- Manage rules with frontend and backend switch -func (s Manager) AddTCPLink(transactionId string, backendName string, port int, domainName string, listenerMode ListenerMode, restrictedPorts []int) error { - if IsPortRestrictedForManualConfig(port, restrictedPorts) { - return errors.New("port is restricted for manual configuration") - } - frontendName := "" - if domainName == "" { - frontendName = "fe_tcp_" + strconv.Itoa(port) - } else { - frontendName = "fe_tcp_" + strconv.Itoa(port) + "_" + domainName - } - // Add TCP Frontend - addTcpFrontendRequestQueryParams := QueryParameters{} - addTcpFrontendRequestQueryParams.add("transaction_id", transactionId) - addTcpFrontendRequestBody := map[string]interface{}{ - "maxconn": 2000, - "mode": listenerMode, - "name": frontendName, - } - if strings.TrimSpace(domainName) == "" { - addTcpFrontendRequestBody["default_backend"] = backendName - } - // Create request bytes - addTcpFrontendRequestBodyBytes, err := json.Marshal(addTcpFrontendRequestBody) - if err != nil { - return errors.New("failed to marshal add_backend_switch_request_body") - } - // Send request - addTcpFrontendRes, addTcpFrontendErr := s.postRequest("/services/haproxy/configuration/frontends", addTcpFrontendRequestQueryParams, bytes.NewReader(addTcpFrontendRequestBodyBytes)) - if addTcpFrontendErr != nil || !isValidStatusCode(addTcpFrontendRes.StatusCode) { - return errors.New("failed to add tcp frontend") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddTCPLink: ", err) - } - }(addTcpFrontendRes.Body) - - // Add Port binding - addPortBindingRequestQueryParams := QueryParameters{} - addPortBindingRequestQueryParams.add("transaction_id", transactionId) - addPortBindingRequestQueryParams.add("frontend", frontendName) - - addPortBindingRequestBody := map[string]interface{}{ - "ssl": false, - "port": port, - } - // Create request bytes - addPortBindingRequestBodyBytes, err := json.Marshal(addPortBindingRequestBody) - if err != nil { - return errors.New("failed to marshal add_port_binding_request_body") - } - // Send request - addPortBindingRes, addPortBindingErr := s.postRequest("/services/haproxy/configuration/binds", addPortBindingRequestQueryParams, bytes.NewReader(addPortBindingRequestBodyBytes)) - if addPortBindingErr != nil || !isValidStatusCode(addPortBindingRes.StatusCode) { - return errors.New("failed to add port binding") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddTCPLink: ", err) - } - }(addPortBindingRes.Body) - - if strings.TrimSpace(domainName) != "" { - /// Add Backend Switch - // Build query parameters - addBackendSwitchRequestQueryParams := QueryParameters{} - addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - addBackendSwitchRequestQueryParams.add("frontend", frontendName) - - // Add backend switch request body - addBackendSwitchRequestBody := map[string]interface{}{ - "cond": "if", - "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(domainName) + `:` + strconv.Itoa(port) + ` }`, - "index": 0, - "name": backendName, - } - addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) - if err != nil { - return errors.New("failed to marshal add_backend_switch_request_body") - } - // Send add backend switch request - backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - - return errors.New("failed to add backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddTCPLink: ", err) - } - }(backendSwitchRes.Body) - } - - return nil -} - -// DeleteTCPLink Delete TCP Frontend from HAProxy configuration -func (s Manager) DeleteTCPLink(transactionId string, backendName string, port int, domainName string, restrictedPorts []int) error { - _ = backendName - if IsPortRestrictedForManualConfig(port, restrictedPorts) { - return errors.New("port is restricted for manual configuration") - } - frontendName := "" - if domainName == "" { - frontendName = "fe_tcp_" + strconv.Itoa(port) - } else { - frontendName = "fe_tcp_" + strconv.Itoa(port) + "_" + domainName - } - // Delete TCP Frontend - deleteTcpFrontendRequestQueryParams := QueryParameters{} - deleteTcpFrontendRequestQueryParams.add("transaction_id", transactionId) - deleteTcpFrontendRequestQueryParams.add("frontend", frontendName) - // Send request - deleteTcpFrontendRes, deleteTcpFrontendErr := s.deleteRequest("/services/haproxy/configuration/frontends/"+frontendName, deleteTcpFrontendRequestQueryParams) - if deleteTcpFrontendErr != nil { - return errors.New("failed to delete tcp frontend") - } - if deleteTcpFrontendRes.StatusCode == 404 { - return nil - } else if !isValidStatusCode(deleteTcpFrontendRes.StatusCode) { - return errors.New("failed to delete tcp frontend") - } - return nil -} - -// AddHTTPRedirectRule Add HTTP Redirect Rule -func (s Manager) AddHTTPRedirectRule(transactionId string, matchDomain string, redirectUrl string) error { - if strings.TrimSpace(matchDomain) == "" { - return errors.New("match domain is required") - } - if strings.TrimSpace(redirectUrl) == "" { - return errors.New("redirect domain is required") - } - // Add HTTP Redirect Rule - addHttpRedirectRuleRequestQueryParams := QueryParameters{} - addHttpRedirectRuleRequestQueryParams.add("transaction_id", transactionId) - addHttpRedirectRuleRequestQueryParams.add("parent_name", "fe_http") - addHttpRedirectRuleRequestQueryParams.add("parent_type", "frontend") - addHttpRedirectRuleRequestBody := map[string]interface{}{ - "type": "redirect", - "redir_code": 302, - "redir_type": "location", - "redir_value": redirectUrl, - "index": 0, - "cond": "if", - "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(matchDomain) + ` } !letsencrypt-acl`, - } - // Create request bytes - addHttpRedirectRuleRequestBodyBytes, err := json.Marshal(addHttpRedirectRuleRequestBody) - if err != nil { - return errors.New("failed to marshal add_http_redirect_rule_request_body") - } - // Send request - addHttpRedirectRuleRes, addHttpRedirectRuleErr := s.postRequest("/services/haproxy/configuration/http_request_rules", addHttpRedirectRuleRequestQueryParams, bytes.NewReader(addHttpRedirectRuleRequestBodyBytes)) - if addHttpRedirectRuleErr != nil || !isValidStatusCode(addHttpRedirectRuleRes.StatusCode) { - return errors.New("failed to add http redirect rule") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddHTTPRedirectRule: ", err) - } - }(addHttpRedirectRuleRes.Body) - return nil -} - -// AddHTTPSRedirectRule Add HTTPS Redirect Rule -func (s Manager) AddHTTPSRedirectRule(transactionId string, matchDomain string, redirectUrl string) error { - if strings.TrimSpace(matchDomain) == "" { - return errors.New("match domain is required") - } - if strings.TrimSpace(redirectUrl) == "" { - return errors.New("redirect url is required") - } - // Add HTTPS Redirect Rule - addHttpsRedirectRuleRequestQueryParams := QueryParameters{} - addHttpsRedirectRuleRequestQueryParams.add("transaction_id", transactionId) - addHttpsRedirectRuleRequestQueryParams.add("parent_name", "fe_https") - addHttpsRedirectRuleRequestQueryParams.add("parent_type", "frontend") - addHttpsRedirectRuleRequestBody := map[string]interface{}{ - "type": "redirect", - "redir_code": 302, - "redir_type": "location", - "redir_value": redirectUrl, - "index": 0, - "cond": "if", - "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(matchDomain) + ` } !letsencrypt-acl`, - } - // Create request bytes - addHttpsRedirectRuleRequestBodyBytes, err := json.Marshal(addHttpsRedirectRuleRequestBody) - if err != nil { - return errors.New("failed to marshal add_https_redirect_rule_request_body") - } - // Send request - addHttpsRedirectRuleRes, addHttpsRedirectRuleErr := s.postRequest("/services/haproxy/configuration/http_request_rules", addHttpsRedirectRuleRequestQueryParams, bytes.NewReader(addHttpsRedirectRuleRequestBodyBytes)) - if addHttpsRedirectRuleErr != nil || !isValidStatusCode(addHttpsRedirectRuleRes.StatusCode) { - return errors.New("failed to add https redirect rule") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddHTTPSRedirectRule: ", err) - } - }(addHttpsRedirectRuleRes.Body) - return nil -} - -// DeleteHTTPRedirectRule Delete HTTP Redirect Rule -func (s Manager) DeleteHTTPRedirectRule(transactionId string, matchDomain string) error { - if strings.TrimSpace(matchDomain) == "" { - return errors.New("match domain is required") - } - // Fetch all HTTP Redirect Rules - getHttpRedirectRulesRequestQueryParams := QueryParameters{} - getHttpRedirectRulesRequestQueryParams.add("transaction_id", transactionId) - getHttpRedirectRulesRequestQueryParams.add("parent_name", "fe_http") - getHttpRedirectRulesRequestQueryParams.add("parent_type", "frontend") - getHttpRedirectRulesRes, getHttpRedirectRulesErr := s.getRequest("/services/haproxy/configuration/http_request_rules", getHttpRedirectRulesRequestQueryParams) - if getHttpRedirectRulesErr != nil || !isValidStatusCode(getHttpRedirectRulesRes.StatusCode) { - return errors.New("failed to fetch http redirect rules") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPRedirectRule: ", err) - } - }(getHttpRedirectRulesRes.Body) - getHttpRedirectRulesResBody, getHttpRedirectRulesResBodyErr := io.ReadAll(getHttpRedirectRulesRes.Body) - if getHttpRedirectRulesResBodyErr != nil { - return errors.New("failed to read http redirect rules response body") - } - getHttpRedirectRulesResBodyJson := map[string]interface{}{} - getHttpRedirectRulesResBodyJsonErr := json.Unmarshal(getHttpRedirectRulesResBody, &getHttpRedirectRulesResBodyJson) - if getHttpRedirectRulesResBodyJsonErr != nil { - log.Println(getHttpRedirectRulesResBodyJsonErr) - return errors.New("[haproxy_manager] DeleteHTTPRedirectRule: failed to unmarshal http redirect rules response body") - } - // Find index of HTTP Redirect Rule - index := -1 - getHttpRedirectRules := getHttpRedirectRulesResBodyJson["data"].([]interface{}) - for _, httpRedirectRule := range getHttpRedirectRules { - httpRedirectRuleItem := httpRedirectRule.(map[string]interface{}) - if httpRedirectRuleItem["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(matchDomain)+` } !letsencrypt-acl` { - index = int(httpRedirectRuleItem["index"].(float64)) - break - } - } - // Delete HTTP Redirect Rule - if index != -1 { - deleteHttpRedirectRuleRequestQueryParams := QueryParameters{} - deleteHttpRedirectRuleRequestQueryParams.add("transaction_id", transactionId) - deleteHttpRedirectRuleRequestQueryParams.add("parent_name", "fe_http") - deleteHttpRedirectRuleRequestQueryParams.add("parent_type", "frontend") - // Send request - deleteHttpRedirectRuleRes, deleteHttpRedirectRuleErr := s.deleteRequest("/services/haproxy/configuration/http_request_rules/"+strconv.Itoa(index), deleteHttpRedirectRuleRequestQueryParams) - if deleteHttpRedirectRuleErr != nil || !isValidStatusCode(deleteHttpRedirectRuleRes.StatusCode) { - return errors.New("failed to delete http redirect rule") - } - return nil - } - return nil -} - -// DeleteHTTPSRedirectRule Delete HTTPS Redirect Rule -func (s Manager) DeleteHTTPSRedirectRule(transactionId string, matchDomain string) error { - if strings.TrimSpace(matchDomain) == "" { - return errors.New("match domain is required") - } - // Fetch all HTTPS Redirect Rules - getHttpsRedirectRulesRequestQueryParams := QueryParameters{} - getHttpsRedirectRulesRequestQueryParams.add("transaction_id", transactionId) - getHttpsRedirectRulesRequestQueryParams.add("parent_name", "fe_https") - getHttpsRedirectRulesRequestQueryParams.add("parent_type", "frontend") - getHttpsRedirectRulesRes, getHttpsRedirectRulesErr := s.getRequest("/services/haproxy/configuration/http_request_rules", getHttpsRedirectRulesRequestQueryParams) - if getHttpsRedirectRulesErr != nil || !isValidStatusCode(getHttpsRedirectRulesRes.StatusCode) { - return errors.New("failed to fetch https redirect rules") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPSRedirectRule: ", err) - } - }(getHttpsRedirectRulesRes.Body) - getHttpsRedirectRulesResBody, getHttpsRedirectRulesResBodyErr := io.ReadAll(getHttpsRedirectRulesRes.Body) - if getHttpsRedirectRulesResBodyErr != nil { - return errors.New("failed to read https redirect rules response body") - } - getHttpsRedirectRulesResBodyJson := map[string]interface{}{} - getHttpsRedirectRulesResBodyJsonErr := json.Unmarshal(getHttpsRedirectRulesResBody, &getHttpsRedirectRulesResBodyJson) - if getHttpsRedirectRulesResBodyJsonErr != nil { - log.Println(getHttpsRedirectRulesResBodyJsonErr) - return errors.New("failed to unmarshal https redirect rules response body") - } - // Find index of HTTPS Redirect Rule - index := -1 - getHttpsRedirectRules := getHttpsRedirectRulesResBodyJson["data"].([]interface{}) - for _, httpsRedirectRule := range getHttpsRedirectRules { - httpsRedirectRuleItem := httpsRedirectRule.(map[string]interface{}) - if httpsRedirectRuleItem["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(matchDomain)+` } !letsencrypt-acl` { - index = int(httpsRedirectRuleItem["index"].(float64)) - break - } - } - // Delete HTTPS Redirect Rule - if index != -1 { - deleteHttpsRedirectRuleRequestQueryParams := QueryParameters{} - deleteHttpsRedirectRuleRequestQueryParams.add("transaction_id", transactionId) - deleteHttpsRedirectRuleRequestQueryParams.add("parent_name", "fe_https") - deleteHttpsRedirectRuleRequestQueryParams.add("parent_type", "frontend") - // Send request - deleteHttpsRedirectRuleRes, deleteHttpsRedirectRule := s.deleteRequest("/services/haproxy/configuration/http_request_rules/"+strconv.Itoa(index), deleteHttpsRedirectRuleRequestQueryParams) - if deleteHttpsRedirectRule != nil || !isValidStatusCode(deleteHttpsRedirectRuleRes.StatusCode) { - return errors.New("failed to delete https redirect rule") - } - return nil - } - return nil -} diff --git a/haproxy_manager/predefined_link.go b/haproxy_manager/predefined_link.go new file mode 100644 index 0000000000..aafd66e30d --- /dev/null +++ b/haproxy_manager/predefined_link.go @@ -0,0 +1,199 @@ +package haproxymanager + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "log" + "strconv" + "strings" +) + +// AddHTTPLink Add HTTP Link [Backend Switch] to HAProxy configuration +// -- Manage ACLs with frontend [only domain_name] +// -- Manage rules with frontend and backend switch +func (s Manager) AddHTTPLink(transactionId string, backendName string, domainName string) error { + frontendName := "fe_http" + // Build query parameters + addBackendSwitchRequestQueryParams := QueryParameters{} + addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) + addBackendSwitchRequestQueryParams.add("frontend", frontendName) + // Add backend switch request body + addBackendSwitchRequestBody := map[string]interface{}{ + "cond": "if", + "cond_test": `{ hdr(host) -i ` + domainName + ` }`, + "index": 1, + "name": backendName, + } + addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) + if err != nil { + return errors.New("failed to marshal add_backend_switch_request_body") + } + // Send add backend switch request + backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) + if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { + return errors.New("failed to add backend switch") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] AddHTTPLink: ", err) + } + }(backendSwitchRes.Body) + return nil +} + +// DeleteHTTPLink Delete HTTP Link from HAProxy configuration +func (s Manager) DeleteHTTPLink(transactionId string, backendName string, domainName string) error { + frontendName := "fe_http" + // Build query parameters + fetchBackendSwitchRequestQueryParams := QueryParameters{} + fetchBackendSwitchRequestQueryParams.add("transaction_id", transactionId) + fetchBackendSwitchRequestQueryParams.add("frontend", frontendName) + // Fetch backend switch + backendSwitchRes, backendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", fetchBackendSwitchRequestQueryParams) + if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { + return errors.New("failed to fetch backend switch") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] DeleteHTTPLink: ", err) + } + }(backendSwitchRes.Body) + // Parse backend switch + backendSwitchData := map[string]interface{}{} + bodyBytes, err := io.ReadAll(backendSwitchRes.Body) + if err != nil { + return errors.New("failed to read backend switch response body") + } + err = json.Unmarshal(bodyBytes, &backendSwitchData) + if err != nil { + return errors.New("failed to parse backend switch response body") + } + // Find backend switch + backendSwitchDataArray := backendSwitchData["data"].([]interface{}) + backendSwitchDataArrayIndex := -1 + for i, backendSwitchDataArrayItem := range backendSwitchDataArray { + backendSwitchDataArrayItemMap := backendSwitchDataArrayItem.(map[string]interface{}) + if backendSwitchDataArrayItemMap["name"] == backendName && strings.Contains(backendSwitchDataArrayItemMap["cond_test"].(string), domainName) { + backendSwitchDataArrayIndex = i + break + } + } + if backendSwitchDataArrayIndex == -1 { + return errors.New("failed to find backend switch") + } + // Build query parameters + deleteBackendSwitchRequestQueryParams := QueryParameters{} + deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) + deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) + + // Delete backend switch + deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchDataArrayIndex), deleteBackendSwitchRequestQueryParams) + if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { + return errors.New("failed to delete backend switch") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] DeleteHTTPLink: ", err) + } + }(deleteBackendSwitchRes.Body) + return nil +} + +// AddHTTPSLink Add HTTPS Link [Backend Switch] to HAProxy configuration +// -- Manage ACLs with frontend [only domain_name] +// -- Manage rules with frontend and backend switch +func (s Manager) AddHTTPSLink(transactionId string, backendName string, domainName string) error { + frontendName := "fe_https" + // Build query parameters + addBackendSwitchRequestQueryParams := QueryParameters{} + addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) + addBackendSwitchRequestQueryParams.add("frontend", frontendName) + // Add backend switch request body + addBackendSwitchRequestBody := map[string]interface{}{ + "cond": "if", + "cond_test": `{ hdr(host) -i ` + domainName + ` }`, + "index": 1, + "name": backendName, + } + addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) + if err != nil { + return errors.New("failed to marshal add_backend_switch_request_body") + } + // Send add backend switch request + backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) + if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { + return errors.New("failed to add backend switch") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] AddHTTPSLink: ", err) + } + }(backendSwitchRes.Body) + return nil +} + +// DeleteHTTPSLink Delete HTTPS Link from HAProxy configuration +func (s Manager) DeleteHTTPSLink(transactionId string, backendName string, domainName string) error { + frontendName := "fe_https" + // Build query parameters + fetchBackendSwitchRequestQueryParams := QueryParameters{} + fetchBackendSwitchRequestQueryParams.add("transaction_id", transactionId) + fetchBackendSwitchRequestQueryParams.add("frontend", frontendName) + // Fetch backend switch + backendSwitchRes, backendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", fetchBackendSwitchRequestQueryParams) + if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { + return errors.New("failed to fetch backend switch") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] DeleteHTTPSLink: ", err) + } + }(backendSwitchRes.Body) + // Parse backend switch + backendSwitchData := map[string]interface{}{} + bodyBytes, err := io.ReadAll(backendSwitchRes.Body) + if err != nil { + return errors.New("failed to read backend switch response body") + } + err = json.Unmarshal(bodyBytes, &backendSwitchData) + if err != nil { + return errors.New("failed to parse backend switch response body") + } + // Find backend switch + backendSwitchDataArray := backendSwitchData["data"].([]interface{}) + backendSwitchDataArrayIndex := -1 + for i, backendSwitchDataArrayItem := range backendSwitchDataArray { + backendSwitchDataArrayItemMap := backendSwitchDataArrayItem.(map[string]interface{}) + if backendSwitchDataArrayItemMap["name"] == backendName && strings.Contains(backendSwitchDataArrayItemMap["cond_test"].(string), domainName) { + backendSwitchDataArrayIndex = i + break + } + } + if backendSwitchDataArrayIndex == -1 { + return errors.New("failed to find backend switch") + } + // Build query parameters + deleteBackendSwitchRequestQueryParams := QueryParameters{} + deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) + deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) + + // Delete backend switch + deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchDataArrayIndex), deleteBackendSwitchRequestQueryParams) + if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { + return errors.New("failed to delete backend switch") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] DeleteHTTPSLink: ", err) + } + }(deleteBackendSwitchRes.Body) + return nil +} diff --git a/haproxy_manager/redirect_rule.go b/haproxy_manager/redirect_rule.go new file mode 100644 index 0000000000..51c1c242f9 --- /dev/null +++ b/haproxy_manager/redirect_rule.go @@ -0,0 +1,205 @@ +package haproxymanager + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "log" + "strconv" + "strings" +) + +// AddHTTPRedirectRule Add HTTP Redirect Rule +func (s Manager) AddHTTPRedirectRule(transactionId string, matchDomain string, redirectUrl string) error { + if strings.TrimSpace(matchDomain) == "" { + return errors.New("match domain is required") + } + if strings.TrimSpace(redirectUrl) == "" { + return errors.New("redirect domain is required") + } + // Add HTTP Redirect Rule + addHttpRedirectRuleRequestQueryParams := QueryParameters{} + addHttpRedirectRuleRequestQueryParams.add("transaction_id", transactionId) + addHttpRedirectRuleRequestQueryParams.add("parent_name", "fe_http") + addHttpRedirectRuleRequestQueryParams.add("parent_type", "frontend") + addHttpRedirectRuleRequestBody := map[string]interface{}{ + "type": "redirect", + "redir_code": 302, + "redir_type": "location", + "redir_value": redirectUrl, + "index": 0, + "cond": "if", + "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(matchDomain) + ` } !letsencrypt-acl`, + } + // Create request bytes + addHttpRedirectRuleRequestBodyBytes, err := json.Marshal(addHttpRedirectRuleRequestBody) + if err != nil { + return errors.New("failed to marshal add_http_redirect_rule_request_body") + } + // Send request + addHttpRedirectRuleRes, addHttpRedirectRuleErr := s.postRequest("/services/haproxy/configuration/http_request_rules", addHttpRedirectRuleRequestQueryParams, bytes.NewReader(addHttpRedirectRuleRequestBodyBytes)) + if addHttpRedirectRuleErr != nil || !isValidStatusCode(addHttpRedirectRuleRes.StatusCode) { + return errors.New("failed to add http redirect rule") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] AddHTTPRedirectRule: ", err) + } + }(addHttpRedirectRuleRes.Body) + return nil +} + +// AddHTTPSRedirectRule Add HTTPS Redirect Rule +func (s Manager) AddHTTPSRedirectRule(transactionId string, matchDomain string, redirectUrl string) error { + if strings.TrimSpace(matchDomain) == "" { + return errors.New("match domain is required") + } + if strings.TrimSpace(redirectUrl) == "" { + return errors.New("redirect url is required") + } + // Add HTTPS Redirect Rule + addHttpsRedirectRuleRequestQueryParams := QueryParameters{} + addHttpsRedirectRuleRequestQueryParams.add("transaction_id", transactionId) + addHttpsRedirectRuleRequestQueryParams.add("parent_name", "fe_https") + addHttpsRedirectRuleRequestQueryParams.add("parent_type", "frontend") + addHttpsRedirectRuleRequestBody := map[string]interface{}{ + "type": "redirect", + "redir_code": 302, + "redir_type": "location", + "redir_value": redirectUrl, + "index": 0, + "cond": "if", + "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(matchDomain) + ` } !letsencrypt-acl`, + } + // Create request bytes + addHttpsRedirectRuleRequestBodyBytes, err := json.Marshal(addHttpsRedirectRuleRequestBody) + if err != nil { + return errors.New("failed to marshal add_https_redirect_rule_request_body") + } + // Send request + addHttpsRedirectRuleRes, addHttpsRedirectRuleErr := s.postRequest("/services/haproxy/configuration/http_request_rules", addHttpsRedirectRuleRequestQueryParams, bytes.NewReader(addHttpsRedirectRuleRequestBodyBytes)) + if addHttpsRedirectRuleErr != nil || !isValidStatusCode(addHttpsRedirectRuleRes.StatusCode) { + return errors.New("failed to add https redirect rule") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] AddHTTPSRedirectRule: ", err) + } + }(addHttpsRedirectRuleRes.Body) + return nil +} + +// DeleteHTTPRedirectRule Delete HTTP Redirect Rule +func (s Manager) DeleteHTTPRedirectRule(transactionId string, matchDomain string) error { + if strings.TrimSpace(matchDomain) == "" { + return errors.New("match domain is required") + } + // Fetch all HTTP Redirect Rules + getHttpRedirectRulesRequestQueryParams := QueryParameters{} + getHttpRedirectRulesRequestQueryParams.add("transaction_id", transactionId) + getHttpRedirectRulesRequestQueryParams.add("parent_name", "fe_http") + getHttpRedirectRulesRequestQueryParams.add("parent_type", "frontend") + getHttpRedirectRulesRes, getHttpRedirectRulesErr := s.getRequest("/services/haproxy/configuration/http_request_rules", getHttpRedirectRulesRequestQueryParams) + if getHttpRedirectRulesErr != nil || !isValidStatusCode(getHttpRedirectRulesRes.StatusCode) { + return errors.New("failed to fetch http redirect rules") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] DeleteHTTPRedirectRule: ", err) + } + }(getHttpRedirectRulesRes.Body) + getHttpRedirectRulesResBody, getHttpRedirectRulesResBodyErr := io.ReadAll(getHttpRedirectRulesRes.Body) + if getHttpRedirectRulesResBodyErr != nil { + return errors.New("failed to read http redirect rules response body") + } + getHttpRedirectRulesResBodyJson := map[string]interface{}{} + getHttpRedirectRulesResBodyJsonErr := json.Unmarshal(getHttpRedirectRulesResBody, &getHttpRedirectRulesResBodyJson) + if getHttpRedirectRulesResBodyJsonErr != nil { + log.Println(getHttpRedirectRulesResBodyJsonErr) + return errors.New("[haproxy_manager] DeleteHTTPRedirectRule: failed to unmarshal http redirect rules response body") + } + // Find index of HTTP Redirect Rule + index := -1 + getHttpRedirectRules := getHttpRedirectRulesResBodyJson["data"].([]interface{}) + for _, httpRedirectRule := range getHttpRedirectRules { + httpRedirectRuleItem := httpRedirectRule.(map[string]interface{}) + if httpRedirectRuleItem["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(matchDomain)+` } !letsencrypt-acl` { + index = int(httpRedirectRuleItem["index"].(float64)) + break + } + } + // Delete HTTP Redirect Rule + if index != -1 { + deleteHttpRedirectRuleRequestQueryParams := QueryParameters{} + deleteHttpRedirectRuleRequestQueryParams.add("transaction_id", transactionId) + deleteHttpRedirectRuleRequestQueryParams.add("parent_name", "fe_http") + deleteHttpRedirectRuleRequestQueryParams.add("parent_type", "frontend") + // Send request + deleteHttpRedirectRuleRes, deleteHttpRedirectRuleErr := s.deleteRequest("/services/haproxy/configuration/http_request_rules/"+strconv.Itoa(index), deleteHttpRedirectRuleRequestQueryParams) + if deleteHttpRedirectRuleErr != nil || !isValidStatusCode(deleteHttpRedirectRuleRes.StatusCode) { + return errors.New("failed to delete http redirect rule") + } + return nil + } + return nil +} + +// DeleteHTTPSRedirectRule Delete HTTPS Redirect Rule +func (s Manager) DeleteHTTPSRedirectRule(transactionId string, matchDomain string) error { + if strings.TrimSpace(matchDomain) == "" { + return errors.New("match domain is required") + } + // Fetch all HTTPS Redirect Rules + getHttpsRedirectRulesRequestQueryParams := QueryParameters{} + getHttpsRedirectRulesRequestQueryParams.add("transaction_id", transactionId) + getHttpsRedirectRulesRequestQueryParams.add("parent_name", "fe_https") + getHttpsRedirectRulesRequestQueryParams.add("parent_type", "frontend") + getHttpsRedirectRulesRes, getHttpsRedirectRulesErr := s.getRequest("/services/haproxy/configuration/http_request_rules", getHttpsRedirectRulesRequestQueryParams) + if getHttpsRedirectRulesErr != nil || !isValidStatusCode(getHttpsRedirectRulesRes.StatusCode) { + return errors.New("failed to fetch https redirect rules") + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + log.Println("[haproxy_manager] DeleteHTTPSRedirectRule: ", err) + } + }(getHttpsRedirectRulesRes.Body) + getHttpsRedirectRulesResBody, getHttpsRedirectRulesResBodyErr := io.ReadAll(getHttpsRedirectRulesRes.Body) + if getHttpsRedirectRulesResBodyErr != nil { + return errors.New("failed to read https redirect rules response body") + } + getHttpsRedirectRulesResBodyJson := map[string]interface{}{} + getHttpsRedirectRulesResBodyJsonErr := json.Unmarshal(getHttpsRedirectRulesResBody, &getHttpsRedirectRulesResBodyJson) + if getHttpsRedirectRulesResBodyJsonErr != nil { + log.Println(getHttpsRedirectRulesResBodyJsonErr) + return errors.New("failed to unmarshal https redirect rules response body") + } + // Find index of HTTPS Redirect Rule + index := -1 + getHttpsRedirectRules := getHttpsRedirectRulesResBodyJson["data"].([]interface{}) + for _, httpsRedirectRule := range getHttpsRedirectRules { + httpsRedirectRuleItem := httpsRedirectRule.(map[string]interface{}) + if httpsRedirectRuleItem["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(matchDomain)+` } !letsencrypt-acl` { + index = int(httpsRedirectRuleItem["index"].(float64)) + break + } + } + // Delete HTTPS Redirect Rule + if index != -1 { + deleteHttpsRedirectRuleRequestQueryParams := QueryParameters{} + deleteHttpsRedirectRuleRequestQueryParams.add("transaction_id", transactionId) + deleteHttpsRedirectRuleRequestQueryParams.add("parent_name", "fe_https") + deleteHttpsRedirectRuleRequestQueryParams.add("parent_type", "frontend") + // Send request + deleteHttpsRedirectRuleRes, deleteHttpsRedirectRule := s.deleteRequest("/services/haproxy/configuration/http_request_rules/"+strconv.Itoa(index), deleteHttpsRedirectRuleRequestQueryParams) + if deleteHttpsRedirectRule != nil || !isValidStatusCode(deleteHttpsRedirectRuleRes.StatusCode) { + return errors.New("failed to delete https redirect rule") + } + return nil + } + return nil +} diff --git a/swiftwave_service/worker/process_ingress_rule_delete_request.go b/swiftwave_service/worker/process_ingress_rule_delete_request.go index 195e913b7c..9b8f117bbb 100644 --- a/swiftwave_service/worker/process_ingress_rule_delete_request.go +++ b/swiftwave_service/worker/process_ingress_rule_delete_request.go @@ -114,7 +114,7 @@ func (m Manager) IngressRuleDelete(request IngressRuleDeleteRequest, ctx context return err } } else { - err = haproxyManager.DeleteTCPLink(haproxyTransactionId, backendName, int(ingressRule.Port), domain.Name, restrictedPorts) + err = haproxyManager.DeleteTCPLink(haproxyTransactionId, backendName, int(ingressRule.Port), domain.Name, haproxymanager.HTTPMode) if err != nil { // set status as failed and exit // because `DeleteTCPLink` can fail only if haproxy not working @@ -125,7 +125,8 @@ func (m Manager) IngressRuleDelete(request IngressRuleDeleteRequest, ctx context } } } else if ingressRule.Protocol == core.TCPProtocol { - err = haproxyManager.DeleteTCPLink(haproxyTransactionId, backendName, int(ingressRule.Port), "", restrictedPorts) + + err = haproxyManager.DeleteTCPLink(haproxyTransactionId, backendName, int(ingressRule.Port), "", haproxymanager.TCPMode) if err != nil { // set status as failed and exit // because `DeleteTCPLink` can fail only if haproxy not working From 0e59cf0c17e52e1008963662f8c33cb374a02006 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 04:11:17 +0530 Subject: [PATCH 02/13] feat: minimal setup for haproxy testcases --- go.mod | 28 +++++++++--- go.sum | 57 ++++++++++++++++++++---- haproxy_manager/haproxy_test.go | 77 +++++++++++++++++++++++++++++++++ haproxy_manager/utils_test.go | 52 ++++++++++++++++++++++ 4 files changed, 201 insertions(+), 13 deletions(-) create mode 100644 haproxy_manager/haproxy_test.go create mode 100644 haproxy_manager/utils_test.go diff --git a/go.mod b/go.mod index cc6db81c7f..adc5f66362 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/oklog/ulid v1.3.1 github.com/rabbitmq/amqp091-go v1.9.0 github.com/spf13/cobra v1.8.0 + github.com/testcontainers/testcontainers-go v0.29.1 github.com/vektah/gqlparser/v2 v2.5.11 golang.org/x/term v0.18.0 gopkg.in/yaml.v3 v3.0.1 @@ -55,38 +56,56 @@ require ( ) require ( - github.com/containerd/containerd v1.7.11 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/patternmatcher v0.5.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/sergi/go-diff v1.3.1 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect go.uber.org/goleak v1.3.0 // indirect + golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/sync v0.6.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) require ( github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // direct @@ -98,7 +117,7 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect @@ -111,5 +130,4 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.19.0 // indirect - gotest.tools/v3 v3.5.0 // indirect ) diff --git a/go.sum b/go.sum index 7a2ef0e835..d8d3e9f062 100644 --- a/go.sum +++ b/go.sum @@ -39,12 +39,16 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= +github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -58,8 +62,8 @@ github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v26.0.0+incompatible h1:Ng2qi+gdKADUa/VM+6b6YaY2nlZhk/lVJiKR/2bMudU= github.com/docker/docker v26.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= @@ -87,6 +91,8 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -97,8 +103,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -152,6 +162,10 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -163,8 +177,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= -github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= @@ -183,14 +197,16 @@ github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= -github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -199,6 +215,11 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shoenig/test v0.6.6 h1:Oe8TPH9wAbv++YPNDKJWUnI8Q4PPWCx3UbOfH+FxiMU= github.com/shoenig/test v0.6.6/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -214,14 +235,22 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.29.1 h1:z8kxdFlovA2y97RWx98v/TQ+tR+SXZm6p35M+xB92zk= +github.com/testcontainers/testcontainers-go v0.29.1/go.mod h1:SnKnKQav8UcgtKqjp/AD8bE1MqZm+3TDb/B8crE3XnI= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -237,6 +266,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= @@ -271,6 +302,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -298,12 +331,15 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -312,6 +348,9 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -351,6 +390,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/haproxy_manager/haproxy_test.go b/haproxy_manager/haproxy_test.go new file mode 100644 index 0000000000..638e4d49c4 --- /dev/null +++ b/haproxy_manager/haproxy_test.go @@ -0,0 +1,77 @@ +package haproxymanager + +import ( + "context" + "encoding/json" + "fmt" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" + "io" + "os" +) + +// This file contains functions to run integration tests on HAProxy Manager + +type haproxyContainer struct { + testcontainers.Container + UnixSocketPath string +} + +func startTestContainer(ctx context.Context) (*haproxyContainer, error) { + // get a temp directory + tmpDir, err := os.MkdirTemp("", "haproxy-manager-test-*") + if err != nil { + fmt.Println("Error creating temp directory") + return nil, err + } + req := testcontainers.ContainerRequest{ + Image: "ghcr.io/swiftwave-org/haproxy:3.0", + ExposedPorts: []string{}, + Env: map[string]string{ + "ADMIN_USER": "admin", + "ADMIN_PASSWORD": "admin", + "SWIFTWAVE_SERVICE_ENDPOINT": "localhost:80", + }, + Mounts: testcontainers.ContainerMounts{ + { + Source: testcontainers.GenericBindMountSource{HostPath: tmpDir}, + Target: "/home", + }, + }, + WaitingFor: wait.ForExec([]string{"ls", "/home/dataplaneapi.sock"}), + } + container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + if err != nil { + fmt.Println("Error starting container") + return nil, err + } + unixSocket := fmt.Sprintf("%s/dataplaneapi.sock", tmpDir) + return &haproxyContainer{ + Container: container, + UnixSocketPath: unixSocket, + }, nil +} + +// Fetch HAProxy configuration +func (s Manager) fetchConfig(transactionId string) (string, error) { + params := QueryParameters{} + params.add("transaction_id", transactionId) + // Send request + getConfigRes, getConfigErr := s.getRequest("/services/haproxy/configuration/raw", params) + if getConfigErr != nil || !isValidStatusCode(getConfigRes.StatusCode) { + return "", getConfigErr + } + defer func(body io.ReadCloser) { + _ = body.Close() + }(getConfigRes.Body) + // Parse response + var config map[string]interface{} + err := json.NewDecoder(getConfigRes.Body).Decode(&config) + if err != nil { + return "", err + } + return config["data"].(string), nil +} diff --git a/haproxy_manager/utils_test.go b/haproxy_manager/utils_test.go new file mode 100644 index 0000000000..2eca3ee925 --- /dev/null +++ b/haproxy_manager/utils_test.go @@ -0,0 +1,52 @@ +package haproxymanager + +import ( + "context" + "log" + "net" + "os" + "testing" +) + +var haproxyTestManager Manager + +func TestMain(m *testing.M) { + //Set up container + ctx := context.Background() + haproxyContainer, err := startTestContainer(ctx) + if err != nil { + panic(err) + } + // Set up haproxy manager + haproxyTestManager = New(func() (net.Conn, error) { + return net.Dial("unix", haproxyContainer.UnixSocketPath) + }, "admin", "admin") + //executing all other test suite + exitCode := m.Run() + //Destruct database container after completing tests + if err := haproxyContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + os.Exit(exitCode) +} + +func TestConfigurationEndpoint(t *testing.T) { + // Creat transaction + transactionId, err := haproxyTestManager.FetchNewTransactionId() + if err != nil { + t.Fatal(err) + } + t.Run("TestFetchConfig", func(t *testing.T) { + // get configuration + data, err := haproxyTestManager.fetchConfig(transactionId) + if err != nil { + t.Fatal(err) + } + // data should be more than 0 + if len(data) == 0 { + t.Error("failed to fetch configuration") + } else { + //t.Log(data) + } + }) +} From b4f169850dd17afee024f757c503fa628778d043 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 04:17:28 +0530 Subject: [PATCH 03/13] ci: haproxy tests added in ci --- .../workflows/{go-lint-build.yml => go-lint.yml} | 0 .github/workflows/go-test.yml | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) rename .github/workflows/{go-lint-build.yml => go-lint.yml} (100%) create mode 100644 .github/workflows/go-test.yml diff --git a/.github/workflows/go-lint-build.yml b/.github/workflows/go-lint.yml similarity index 100% rename from .github/workflows/go-lint-build.yml rename to .github/workflows/go-lint.yml diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000000..c83320a6df --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,16 @@ +name: Go Test +on: + [push, pull_request, workflow_dispatch] +jobs: + haproxy_manager: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.22 + - name: Run tests + run: go test ./haproxy_manager -v \ No newline at end of file From 9aa7128f724e97a2c2c400458c7a09359b3a4b84 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 04:24:42 +0530 Subject: [PATCH 04/13] ci: fix permission --- .github/workflows/go-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index c83320a6df..b5b1df865c 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -13,4 +13,4 @@ jobs: with: go-version: 1.22 - name: Run tests - run: go test ./haproxy_manager -v \ No newline at end of file + run: sudo go test ./haproxy_manager -v \ No newline at end of file From 053efd40bf876505f3572aee4bef559bc289e731 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:04:54 +0530 Subject: [PATCH 05/13] test: haproxy backend testcases added --- go.mod | 2 + haproxy_manager/backend.go | 10 +-- haproxy_manager/backend_test.go | 123 ++++++++++++++++++++++++++++++++ haproxy_manager/haproxy_test.go | 75 +++++++++++++++---- haproxy_manager/utils_test.go | 52 -------------- 5 files changed, 193 insertions(+), 69 deletions(-) create mode 100644 haproxy_manager/backend_test.go delete mode 100644 haproxy_manager/utils_test.go diff --git a/go.mod b/go.mod index adc5f66362..e5616f07bd 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.7 gorm.io/gorm v1.25.8 + gotest.tools/v3 v3.5.0 ) require ( @@ -69,6 +70,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect diff --git a/haproxy_manager/backend.go b/haproxy_manager/backend.go index 8637c22aa8..7d689e8b4a 100644 --- a/haproxy_manager/backend.go +++ b/haproxy_manager/backend.go @@ -15,9 +15,10 @@ func (s Manager) GenerateBackendName(serviceName string, port int) string { } // IsBackendExist : Check backend exist in HAProxy configuration -func (s Manager) IsBackendExist(backendName string) (bool, error) { +func (s Manager) IsBackendExist(transactionId string, backendName string) (bool, error) { // Build query parameters isBackendExistRequestQueryParams := QueryParameters{} + isBackendExistRequestQueryParams.add("transaction_id", transactionId) // Send request to check if backend exist isBackendExistRes, isBackendExistErr := s.getRequest("/services/haproxy/configuration/backends/"+backendName, isBackendExistRequestQueryParams) if isBackendExistErr != nil { @@ -34,6 +35,7 @@ func (s Manager) IsBackendExist(backendName string) (bool, error) { } else if isBackendExistRes.StatusCode == 200 { return true, nil } + return false, errors.New("failed to check if backend exist") } @@ -42,7 +44,7 @@ func (s Manager) IsBackendExist(backendName string) (bool, error) { func (s Manager) AddBackend(transactionId string, serviceName string, port int, replicas int) (string, error) { backendName := s.GenerateBackendName(serviceName, port) // Check if backend exist - isBackendExist, err := s.IsBackendExist(backendName) + isBackendExist, err := s.IsBackendExist(transactionId, backendName) if err != nil { log.Println(err) return "", err @@ -119,7 +121,7 @@ func (s Manager) AddBackend(transactionId string, serviceName string, port int, func (s Manager) GetReplicaCount(transactionId string, serviceName string, port int) (int, error) { backendName := s.GenerateBackendName(serviceName, port) // Check if backend exist - isBackendExist, err := s.IsBackendExist(backendName) + isBackendExist, err := s.IsBackendExist(transactionId, backendName) if err != nil { log.Println(err) return 0, err @@ -173,7 +175,7 @@ func (s Manager) GetReplicaCount(transactionId string, serviceName string, port func (s Manager) UpdateBackendReplicas(transactionId string, serviceName string, port int, replicas int) error { backendName := s.GenerateBackendName(serviceName, port) // Check if backend exist - isBackendExist, err := s.IsBackendExist(backendName) + isBackendExist, err := s.IsBackendExist(transactionId, backendName) if err != nil { log.Println(err) return err diff --git a/haproxy_manager/backend_test.go b/haproxy_manager/backend_test.go new file mode 100644 index 0000000000..a0a752a122 --- /dev/null +++ b/haproxy_manager/backend_test.go @@ -0,0 +1,123 @@ +package haproxymanager + +import ( + "fmt" + "gotest.tools/v3/assert" + "strings" + "testing" +) + +func TestBackend(t *testing.T) { + serviceName := "test-service" + servicePort := 8080 + serviceReplicas := 3 + + t.Run("is backend exists", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + // backend name + backendName := haproxyTestManager.GenerateBackendName(serviceName, servicePort) + // check if backend exists + isExists, err := haproxyTestManager.IsBackendExist(transactionId, backendName) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "backend already exists [api]") + // add backend + _, err = haproxyTestManager.AddBackend(transactionId, serviceName, servicePort, serviceReplicas) + if err != nil { + t.Fatal(err) + } + isExists, err = haproxyTestManager.IsBackendExist(transactionId, backendName) + if err != nil { + t.Fatal(err) + } + config := fetchConfig(transactionId) + assert.Check(t, strings.Contains(config, fmt.Sprintf("backend %s", backendName)), "backend not found in config") + assert.Check(t, strings.Contains(config, fmt.Sprintf("server-template %s_container- %d %s:%d no-check init-addr none resolvers docker", serviceName, serviceReplicas, serviceName, servicePort)), "server template not found in config") + assert.Check(t, isExists == true, "backend does not exist [api]") + }) + + t.Run("add backend", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + backendName, err := haproxyTestManager.AddBackend(transactionId, serviceName, servicePort, 1) + if err != nil { + t.Fatal(err) + } + assert.Check(t, strings.Compare(backendName, haproxyTestManager.GenerateBackendName(serviceName, servicePort)) == 0, "created backend name is not correct") + assert.Check(t, strings.Contains(fetchConfig(transactionId), backendName), "backend not found in config") + }) + + t.Run("add backend with existing backend", func(t *testing.T) {}) + + t.Run("delete backend", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + backendName, err := haproxyTestManager.AddBackend(transactionId, serviceName, servicePort, 1) + if err != nil { + t.Fatal(err) + } + isExists, err := haproxyTestManager.IsBackendExist(transactionId, backendName) + if err != nil { + t.Fatal(err) + } + if !isExists { + t.Fatal("backend has not been created") + } + err = haproxyTestManager.DeleteBackend(transactionId, backendName) + if err != nil { + t.Fatal(err) + } + isExists, err = haproxyTestManager.IsBackendExist(transactionId, backendName) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "backend has not been deleted") + }) + + t.Run("delete backend with non-existing backend name", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.DeleteBackend(transactionId, "non-existing-backend-name") + assert.Check(t, err == nil, "delete backend with invalid backend name should not return error") + }) + + t.Run("fetch replica count", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + _, err := haproxyTestManager.AddBackend(transactionId, serviceName, servicePort, serviceReplicas) + if err != nil { + t.Fatal(err) + } + replicas, err := haproxyTestManager.GetReplicaCount(transactionId, serviceName, servicePort) + if err != nil { + t.Fatal(err) + } + assert.Check(t, replicas == serviceReplicas, "replicas count does not match") + }) + + t.Run("update replica count", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + _, err := haproxyTestManager.AddBackend(transactionId, serviceName, servicePort, 1) + if err != nil { + t.Fatal(err) + } + replicas, err := haproxyTestManager.GetReplicaCount(transactionId, serviceName, servicePort) + if err != nil { + t.Fatal(err) + } + assert.Check(t, replicas == 1, "replicas count does not match") + err = haproxyTestManager.UpdateBackendReplicas(transactionId, serviceName, servicePort, 1) + if err != nil { + t.Fatal(err) + } + replicas, err = haproxyTestManager.GetReplicaCount(transactionId, serviceName, servicePort) + if err != nil { + t.Fatal(err) + } + assert.Check(t, replicas == 1, "replicas count does not match") + }) + +} diff --git a/haproxy_manager/haproxy_test.go b/haproxy_manager/haproxy_test.go index 638e4d49c4..d74184d0e1 100644 --- a/haproxy_manager/haproxy_test.go +++ b/haproxy_manager/haproxy_test.go @@ -4,10 +4,15 @@ import ( "context" "encoding/json" "fmt" + "github.com/labstack/gommon/random" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "io" + "log" + "net" "os" + "os/exec" + "testing" ) // This file contains functions to run integration tests on HAProxy Manager @@ -15,15 +20,39 @@ import ( type haproxyContainer struct { testcontainers.Container UnixSocketPath string + VolumeName string } -func startTestContainer(ctx context.Context) (*haproxyContainer, error) { - // get a temp directory - tmpDir, err := os.MkdirTemp("", "haproxy-manager-test-*") +var haproxyTestManager Manager + +func TestMain(m *testing.M) { + //Set up container + ctx := context.Background() + haproxyContainer, err := startTestContainer(ctx) if err != nil { - fmt.Println("Error creating temp directory") - return nil, err + panic(err) } + // Set up haproxy manager + haproxyTestManager = New(func() (net.Conn, error) { + return net.Dial("unix", haproxyContainer.UnixSocketPath) + }, "admin", "admin") + // Create a transaction id + //executing all other test suite + exitCode := m.Run() + //Destruct database container after completing tests + if err := haproxyContainer.Terminate(ctx); err != nil { + log.Fatalf("failed to terminate container: %s", err) + } + // Delete volume + _, err = exec.Command("docker", "volume", "rm", haproxyContainer.VolumeName).Output() + if err != nil { + log.Fatalf("failed to delete volume: %s", err) + } + os.Exit(exitCode) +} + +func startTestContainer(ctx context.Context) (*haproxyContainer, error) { + volumeName := "haproxy-data-" + random.String(4) req := testcontainers.ContainerRequest{ Image: "ghcr.io/swiftwave-org/haproxy:3.0", ExposedPorts: []string{}, @@ -34,7 +63,9 @@ func startTestContainer(ctx context.Context) (*haproxyContainer, error) { }, Mounts: testcontainers.ContainerMounts{ { - Source: testcontainers.GenericBindMountSource{HostPath: tmpDir}, + Source: testcontainers.GenericVolumeMountSource{ + Name: volumeName, + }, Target: "/home", }, }, @@ -45,24 +76,42 @@ func startTestContainer(ctx context.Context) (*haproxyContainer, error) { Started: true, }) if err != nil { - fmt.Println("Error starting container") + log.Println("Error starting container") return nil, err } - unixSocket := fmt.Sprintf("%s/dataplaneapi.sock", tmpDir) + unixSocket := fmt.Sprintf("%s/dataplaneapi.sock", "/var/lib/docker/volumes/"+volumeName+"/_data") return &haproxyContainer{ Container: container, UnixSocketPath: unixSocket, + VolumeName: volumeName, }, nil } +// Generate a new transaction id +func newTransaction() string { + transactionId, err := haproxyTestManager.FetchNewTransactionId() + if err != nil { + log.Fatal(err) + } + return transactionId +} + +// Delete a transaction +func deleteTransaction(transactionId string) { + err := haproxyTestManager.DeleteTransaction(transactionId) + if err != nil { + log.Fatal(err) + } +} + // Fetch HAProxy configuration -func (s Manager) fetchConfig(transactionId string) (string, error) { +func fetchConfig(transactionId string) string { params := QueryParameters{} params.add("transaction_id", transactionId) // Send request - getConfigRes, getConfigErr := s.getRequest("/services/haproxy/configuration/raw", params) + getConfigRes, getConfigErr := haproxyTestManager.getRequest("/services/haproxy/configuration/raw", params) if getConfigErr != nil || !isValidStatusCode(getConfigRes.StatusCode) { - return "", getConfigErr + log.Fatal("failed to fetch config") } defer func(body io.ReadCloser) { _ = body.Close() @@ -71,7 +120,7 @@ func (s Manager) fetchConfig(transactionId string) (string, error) { var config map[string]interface{} err := json.NewDecoder(getConfigRes.Body).Decode(&config) if err != nil { - return "", err + log.Fatal("failed to parse config") } - return config["data"].(string), nil + return config["data"].(string) } diff --git a/haproxy_manager/utils_test.go b/haproxy_manager/utils_test.go deleted file mode 100644 index 2eca3ee925..0000000000 --- a/haproxy_manager/utils_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package haproxymanager - -import ( - "context" - "log" - "net" - "os" - "testing" -) - -var haproxyTestManager Manager - -func TestMain(m *testing.M) { - //Set up container - ctx := context.Background() - haproxyContainer, err := startTestContainer(ctx) - if err != nil { - panic(err) - } - // Set up haproxy manager - haproxyTestManager = New(func() (net.Conn, error) { - return net.Dial("unix", haproxyContainer.UnixSocketPath) - }, "admin", "admin") - //executing all other test suite - exitCode := m.Run() - //Destruct database container after completing tests - if err := haproxyContainer.Terminate(ctx); err != nil { - log.Fatalf("failed to terminate container: %s", err) - } - os.Exit(exitCode) -} - -func TestConfigurationEndpoint(t *testing.T) { - // Creat transaction - transactionId, err := haproxyTestManager.FetchNewTransactionId() - if err != nil { - t.Fatal(err) - } - t.Run("TestFetchConfig", func(t *testing.T) { - // get configuration - data, err := haproxyTestManager.fetchConfig(transactionId) - if err != nil { - t.Fatal(err) - } - // data should be more than 0 - if len(data) == 0 { - t.Error("failed to fetch configuration") - } else { - //t.Log(data) - } - }) -} From 87c2c7be331fec328f80acc924ca50921b67eda6 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:21:16 +0530 Subject: [PATCH 06/13] test: haproxy frontend testcases added --- haproxy_manager/backend_switch.go | 10 +- haproxy_manager/backend_test.go | 22 ++-- haproxy_manager/custom_link.go | 2 +- haproxy_manager/frontend.go | 24 ++-- haproxy_manager/frontend_test.go | 200 ++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+), 23 deletions(-) create mode 100644 haproxy_manager/frontend_test.go diff --git a/haproxy_manager/backend_switch.go b/haproxy_manager/backend_switch.go index b31f0c237d..c35e015b86 100644 --- a/haproxy_manager/backend_switch.go +++ b/haproxy_manager/backend_switch.go @@ -8,7 +8,7 @@ import ( "strings" ) -func (s Manager) CreateBackendSwitch(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) error { +func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) error { params := QueryParameters{} params.add("transaction_id", transactionId) params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) @@ -55,12 +55,14 @@ func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode List _ = body.Close() }(getBackendSwitchRes.Body) // Parse response - var backendSwitchRules []map[string]interface{} - err := json.NewDecoder(getBackendSwitchRes.Body).Decode(&backendSwitchRules) + var backendSwitchRulesData map[string]interface{} + err := json.NewDecoder(getBackendSwitchRes.Body).Decode(&backendSwitchRulesData) if err != nil { return -1, err } - for _, rule := range backendSwitchRules { + backendSwitchRules := backendSwitchRulesData["data"].([]interface{}) + for _, r := range backendSwitchRules { + rule := r.(map[string]interface{}) if rule["name"] == backendName && rule["cond"] == "if" && rule["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(domainName)+`:`+strconv.Itoa(bindPort)+` }` { diff --git a/haproxy_manager/backend_test.go b/haproxy_manager/backend_test.go index a0a752a122..4b007f281d 100644 --- a/haproxy_manager/backend_test.go +++ b/haproxy_manager/backend_test.go @@ -22,7 +22,7 @@ func TestBackend(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Check(t, isExists == false, "backend already exists [api]") + assert.Check(t, isExists == false, "backend should not exist [api]") // add backend _, err = haproxyTestManager.AddBackend(transactionId, serviceName, servicePort, serviceReplicas) if err != nil { @@ -33,8 +33,8 @@ func TestBackend(t *testing.T) { t.Fatal(err) } config := fetchConfig(transactionId) - assert.Check(t, strings.Contains(config, fmt.Sprintf("backend %s", backendName)), "backend not found in config") - assert.Check(t, strings.Contains(config, fmt.Sprintf("server-template %s_container- %d %s:%d no-check init-addr none resolvers docker", serviceName, serviceReplicas, serviceName, servicePort)), "server template not found in config") + assert.Check(t, strings.Contains(config, fmt.Sprintf("backend %s", backendName)), "backend name should be in config") + assert.Check(t, strings.Contains(config, fmt.Sprintf("server-template %s_container- %d %s:%d no-check init-addr none resolvers docker", serviceName, serviceReplicas, serviceName, servicePort)), "server template should be in config") assert.Check(t, isExists == true, "backend does not exist [api]") }) @@ -45,11 +45,11 @@ func TestBackend(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Check(t, strings.Compare(backendName, haproxyTestManager.GenerateBackendName(serviceName, servicePort)) == 0, "created backend name is not correct") - assert.Check(t, strings.Contains(fetchConfig(transactionId), backendName), "backend not found in config") + assert.Check(t, strings.Compare(backendName, haproxyTestManager.GenerateBackendName(serviceName, servicePort)) == 0, "created backend name should match with generated backend name") + assert.Check(t, strings.Contains(fetchConfig(transactionId), backendName), "backend name should be in config") }) - t.Run("add backend with existing backend", func(t *testing.T) {}) + t.Run("add backend with same pre-existing backend", func(t *testing.T) {}) t.Run("delete backend", func(t *testing.T) { transactionId := newTransaction() @@ -73,7 +73,7 @@ func TestBackend(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Check(t, isExists == false, "backend has not been deleted") + assert.Check(t, isExists == false, "backend should not exist after deletion") }) t.Run("delete backend with non-existing backend name", func(t *testing.T) { @@ -94,7 +94,7 @@ func TestBackend(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Check(t, replicas == serviceReplicas, "replicas count does not match") + assert.Check(t, replicas == serviceReplicas, "replicas count should match with expected replicas count") }) t.Run("update replica count", func(t *testing.T) { @@ -108,8 +108,8 @@ func TestBackend(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Check(t, replicas == 1, "replicas count does not match") - err = haproxyTestManager.UpdateBackendReplicas(transactionId, serviceName, servicePort, 1) + assert.Check(t, replicas == 1, "replicas count should be 1 initially") + err = haproxyTestManager.UpdateBackendReplicas(transactionId, serviceName, servicePort, 4) if err != nil { t.Fatal(err) } @@ -117,7 +117,7 @@ func TestBackend(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Check(t, replicas == 1, "replicas count does not match") + assert.Check(t, replicas == 4, "replicas count should be 4 after update") }) } diff --git a/haproxy_manager/custom_link.go b/haproxy_manager/custom_link.go index de989efe98..30a834b1fd 100644 --- a/haproxy_manager/custom_link.go +++ b/haproxy_manager/custom_link.go @@ -15,7 +15,7 @@ func (s Manager) AddTCPLink(transactionId string, backendName string, port int, return err } // Add Backend Switch - err = s.CreateBackendSwitch(transactionId, listenerMode, port, backendName, domainName) + err = s.AddBackendSwitch(transactionId, listenerMode, port, backendName, domainName) return err } diff --git a/haproxy_manager/frontend.go b/haproxy_manager/frontend.go index b79261bbe9..3ec6d2d376 100644 --- a/haproxy_manager/frontend.go +++ b/haproxy_manager/frontend.go @@ -15,6 +15,9 @@ func GenerateFrontendName(listenerMode ListenerMode, port int) string { } func (s Manager) AddFrontend(transactionId string, listenerMode ListenerMode, bindPort int, restrictedPorts []int) error { + if bindPort == 80 || bindPort == 443 { + return nil + } frontendName := GenerateFrontendName(listenerMode, bindPort) if IsPortRestrictedForManualConfig(bindPort, restrictedPorts) { return errors.New("port is restricted for manual configuration") @@ -118,6 +121,10 @@ func (s Manager) IsOtherSwitchingRuleExist(transactionId string, listenerMode Li } func (s Manager) DeleteFrontend(transactionId string, listenerMode ListenerMode, bindPort int) error { + // we should not delete frontend for port 80 and 443 + if bindPort == 80 || bindPort == 443 { + return nil + } // check if frontend exists isFrontendExist, err := s.IsFrontendExist(transactionId, listenerMode, bindPort) if err != nil { @@ -126,13 +133,16 @@ func (s Manager) DeleteFrontend(transactionId string, listenerMode ListenerMode, if !isFrontendExist { return nil } - // don't delete frontend if there are switching rules - isSwitchingRuleExist, err := s.IsOtherSwitchingRuleExist(transactionId, listenerMode, bindPort) - if err != nil { - return err - } - if isSwitchingRuleExist { - return nil + // ignore for tcp + if listenerMode == HTTPMode { + // don't delete frontend if there are switching rules + isSwitchingRuleExist, err := s.IsOtherSwitchingRuleExist(transactionId, listenerMode, bindPort) + if err != nil { + return err + } + if isSwitchingRuleExist { + return nil + } } // delete frontend frontendName := GenerateFrontendName(listenerMode, bindPort) diff --git a/haproxy_manager/frontend_test.go b/haproxy_manager/frontend_test.go new file mode 100644 index 0000000000..9c3475c728 --- /dev/null +++ b/haproxy_manager/frontend_test.go @@ -0,0 +1,200 @@ +package haproxymanager + +import ( + "fmt" + "gotest.tools/v3/assert" + "strings" + "testing" +) + +func TestFrontend(t *testing.T) { + t.Run("add tcp frontend", func(t *testing.T) { + // Add frontend + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + // Check if frontend exists + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "frontend should exist [api]") + assert.Check(t, strings.Compare(GenerateFrontendName(TCPMode, 8080), "fe_tcp_8080") == 0, "frontend name should be fe_tcp_8080") + }) + + t.Run("add http frontend", func(t *testing.T) { + // Add frontend + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + // Check if frontend exists + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, HTTPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "frontend should exist [api]") + assert.Check(t, strings.Compare(GenerateFrontendName(HTTPMode, 8080), "fe_http_8080") == 0, "frontend name should be fe_http_8080") + }) + + t.Run("add duplicate frontend", func(t *testing.T) { + // Add frontend + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + // Add frontend + err = haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + assert.Check(t, err == nil, "frontend added successfully") + }) + + t.Run("is frontend exist", func(t *testing.T) { + // Check if frontend exists + transactionId := newTransaction() + defer deleteTransaction(transactionId) + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "frontend should not exist [api]") + // add frontend + err = haproxyTestManager.AddFrontend(transactionId, TCPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + // Check if frontend exists + isExists, err = haproxyTestManager.IsFrontendExist(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "frontend should exist [api]") + }) + + t.Run("delete frontend", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + // Check if frontend exists + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "frontend should exist [api]") + // Delete frontend + err = haproxyTestManager.DeleteFrontend(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + // Check if frontend exists + isExists, err = haproxyTestManager.IsFrontendExist(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "frontend should not exist [api]") + }) + + t.Run("delete non-existing frontend should not return error", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.DeleteFrontend(transactionId, TCPMode, 8080) + assert.Check(t, err == nil, "delete non-existing frontend should not return error") + }) + + t.Run("delete tcp frontend with switching rules should delete frontend", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + backendName, err := haproxyTestManager.AddBackend(transactionId, "service", 8080, 1) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddBackendSwitch(transactionId, TCPMode, 8080, backendName, "example.com") + if err != nil { + t.Fatal(err) + } + assert.Check(t, strings.Contains(fetchConfig(transactionId), fmt.Sprintf("use_backend %s", backendName)), "for tcp mode, use_backend should be in config") + err = haproxyTestManager.DeleteFrontend(transactionId, TCPMode, 8080) + assert.Equal(t, err, nil, "delete rcp frontend with switching rules should not return error") + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, TCPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "frontend should not exist [api]") + }) + + t.Run("delete http frontend with switching rules should not delete frontend", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + backendName, err := haproxyTestManager.AddBackend(transactionId, "service", 8080, 1) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddBackendSwitch(transactionId, HTTPMode, 8080, backendName, "example.com") + if err != nil { + t.Fatal(err) + } + index, err := haproxyTestManager.FetchBackendSwitchIndex(transactionId, HTTPMode, 8080, backendName, "example.com") + if err != nil { + t.Fatal(err) + } + assert.Check(t, index != -1, "backend switch should be created") + err = haproxyTestManager.DeleteFrontend(transactionId, HTTPMode, 8080) + assert.Equal(t, err, nil, "delete http frontend with switching rules should not return error") + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, HTTPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "frontend should exist [api]") + }) + + t.Run("is switching rule exist", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + isExists, err := haproxyTestManager.IsOtherSwitchingRuleExist(transactionId, HTTPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "switching rule should not exist [api]") + backendName, err := haproxyTestManager.AddBackend(transactionId, "service", 8080, 1) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddBackendSwitch(transactionId, HTTPMode, 8080, backendName, "example.com") + if err != nil { + t.Fatal(err) + } + isExists, err = haproxyTestManager.IsOtherSwitchingRuleExist(transactionId, HTTPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "switching rule should exist [api]") + }) + + t.Run("creation of frontend should respect restricted ports", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{8080}) + assert.Check(t, err != nil, "frontend creation should fail for restricted port") + }) +} From 86c7d1f1aca092dc486ae0f321aea07755352489 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:26:40 +0530 Subject: [PATCH 07/13] test: haproxy frontend testcases added --- haproxy_manager/frontend_test.go | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/haproxy_manager/frontend_test.go b/haproxy_manager/frontend_test.go index 9c3475c728..94cdb77799 100644 --- a/haproxy_manager/frontend_test.go +++ b/haproxy_manager/frontend_test.go @@ -164,6 +164,59 @@ func TestFrontend(t *testing.T) { assert.Check(t, isExists == true, "frontend should exist [api]") }) + t.Run("delete http frontend with all switching rules delete should delete frontend", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + backend1Name, err := haproxyTestManager.AddBackend(transactionId, "service", 8080, 1) + if err != nil { + t.Fatal(err) + } + backend2Name, err := haproxyTestManager.AddBackend(transactionId, "service", 8081, 1) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddBackendSwitch(transactionId, HTTPMode, 8080, backend1Name, "example.com") + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddBackendSwitch(transactionId, HTTPMode, 8080, backend2Name, "example.com") + if err != nil { + t.Fatal(err) + } + // delete first backend switch + err = haproxyTestManager.DeleteBackendSwitch(transactionId, HTTPMode, 8080, backend1Name, "example.com") + if err != nil { + t.Fatal(err) + } + // try to delete frontend + err = haproxyTestManager.DeleteFrontend(transactionId, HTTPMode, 8080) + assert.Equal(t, err, nil, "delete http frontend with switching rules should not return error") + // check if frontend exists + isExists, err := haproxyTestManager.IsFrontendExist(transactionId, HTTPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == true, "frontend should exist as it has one backend switch [api]") + // delete second backend switch + err = haproxyTestManager.DeleteBackendSwitch(transactionId, HTTPMode, 8080, backend2Name, "example.com") + if err != nil { + t.Fatal(err) + } + // try to delete frontend + err = haproxyTestManager.DeleteFrontend(transactionId, HTTPMode, 8080) + assert.Equal(t, err, nil, "delete http frontend with switching rules should not return error") + // check if frontend exists + isExists, err = haproxyTestManager.IsFrontendExist(transactionId, HTTPMode, 8080) + if err != nil { + t.Fatal(err) + } + assert.Check(t, isExists == false, "frontend should not exist as it has no backend switch [api]") + }) + t.Run("is switching rule exist", func(t *testing.T) { transactionId := newTransaction() defer deleteTransaction(transactionId) From b6fe9b2bdf92f54c895fe8ece4ea4c9462f43fb1 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 19:46:14 +0530 Subject: [PATCH 08/13] test: haproxy predefine links testcases added --- haproxy_manager/backend_switch.go | 12 +- haproxy_manager/predefined_link.go | 99 ++++----------- haproxy_manager/predefined_link_test.go | 157 ++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 74 deletions(-) create mode 100644 haproxy_manager/predefined_link_test.go diff --git a/haproxy_manager/backend_switch.go b/haproxy_manager/backend_switch.go index c35e015b86..ef91cf1c39 100644 --- a/haproxy_manager/backend_switch.go +++ b/haproxy_manager/backend_switch.go @@ -43,9 +43,13 @@ func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMod } func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) (int, error) { + return s.FetchBackendSwitchIndexByName(transactionId, GenerateFrontendName(listenerMode, bindPort), bindPort, backendName, domainName) +} + +func (s Manager) FetchBackendSwitchIndexByName(transactionId string, frontendName string, bindPort int, backendName string, domainName string) (int, error) { params := QueryParameters{} params.add("transaction_id", transactionId) - params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) + params.add("frontend", frontendName) // Send request getBackendSwitchRes, getBackendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", params) if getBackendSwitchErr != nil || !isValidStatusCode(getBackendSwitchRes.StatusCode) { @@ -61,11 +65,15 @@ func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode List return -1, err } backendSwitchRules := backendSwitchRulesData["data"].([]interface{}) + condTest := `{ hdr(host) -i ` + strings.TrimSpace(domainName) + `:` + strconv.Itoa(bindPort) + ` }` + if bindPort == 80 || bindPort == 443 { + condTest = `{ hdr(host) -i ` + strings.TrimSpace(domainName) + ` }` + } for _, r := range backendSwitchRules { rule := r.(map[string]interface{}) if rule["name"] == backendName && rule["cond"] == "if" && - rule["cond_test"] == `{ hdr(host) -i `+strings.TrimSpace(domainName)+`:`+strconv.Itoa(bindPort)+` }` { + rule["cond_test"] == condTest { return int(rule["index"].(float64)), nil } } diff --git a/haproxy_manager/predefined_link.go b/haproxy_manager/predefined_link.go index aafd66e30d..a56a99aedc 100644 --- a/haproxy_manager/predefined_link.go +++ b/haproxy_manager/predefined_link.go @@ -7,7 +7,6 @@ import ( "io" "log" "strconv" - "strings" ) // AddHTTPLink Add HTTP Link [Backend Switch] to HAProxy configuration @@ -15,6 +14,14 @@ import ( // -- Manage rules with frontend and backend switch func (s Manager) AddHTTPLink(transactionId string, backendName string, domainName string) error { frontendName := "fe_http" + // Check if backend switch already exists + backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 80, backendName, domainName) + if err != nil { + return err + } + if backendSwitchIndex != -1 { + return nil + } // Build query parameters addBackendSwitchRequestQueryParams := QueryParameters{} addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) @@ -47,43 +54,12 @@ func (s Manager) AddHTTPLink(transactionId string, backendName string, domainNam // DeleteHTTPLink Delete HTTP Link from HAProxy configuration func (s Manager) DeleteHTTPLink(transactionId string, backendName string, domainName string) error { frontendName := "fe_http" - // Build query parameters - fetchBackendSwitchRequestQueryParams := QueryParameters{} - fetchBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - fetchBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Fetch backend switch - backendSwitchRes, backendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", fetchBackendSwitchRequestQueryParams) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to fetch backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPLink: ", err) - } - }(backendSwitchRes.Body) - // Parse backend switch - backendSwitchData := map[string]interface{}{} - bodyBytes, err := io.ReadAll(backendSwitchRes.Body) + backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 80, backendName, domainName) if err != nil { - return errors.New("failed to read backend switch response body") - } - err = json.Unmarshal(bodyBytes, &backendSwitchData) - if err != nil { - return errors.New("failed to parse backend switch response body") - } - // Find backend switch - backendSwitchDataArray := backendSwitchData["data"].([]interface{}) - backendSwitchDataArrayIndex := -1 - for i, backendSwitchDataArrayItem := range backendSwitchDataArray { - backendSwitchDataArrayItemMap := backendSwitchDataArrayItem.(map[string]interface{}) - if backendSwitchDataArrayItemMap["name"] == backendName && strings.Contains(backendSwitchDataArrayItemMap["cond_test"].(string), domainName) { - backendSwitchDataArrayIndex = i - break - } + return err } - if backendSwitchDataArrayIndex == -1 { - return errors.New("failed to find backend switch") + if backendSwitchIndex == -1 { + return nil } // Build query parameters deleteBackendSwitchRequestQueryParams := QueryParameters{} @@ -91,7 +67,7 @@ func (s Manager) DeleteHTTPLink(transactionId string, backendName string, domain deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) // Delete backend switch - deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchDataArrayIndex), deleteBackendSwitchRequestQueryParams) + deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchIndex), deleteBackendSwitchRequestQueryParams) if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { return errors.New("failed to delete backend switch") } @@ -109,6 +85,14 @@ func (s Manager) DeleteHTTPLink(transactionId string, backendName string, domain // -- Manage rules with frontend and backend switch func (s Manager) AddHTTPSLink(transactionId string, backendName string, domainName string) error { frontendName := "fe_https" + // Check if backend switch already exists + backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 443, backendName, domainName) + if err != nil { + return err + } + if backendSwitchIndex != -1 { + return nil + } // Build query parameters addBackendSwitchRequestQueryParams := QueryParameters{} addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) @@ -142,50 +126,21 @@ func (s Manager) AddHTTPSLink(transactionId string, backendName string, domainNa func (s Manager) DeleteHTTPSLink(transactionId string, backendName string, domainName string) error { frontendName := "fe_https" // Build query parameters - fetchBackendSwitchRequestQueryParams := QueryParameters{} - fetchBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - fetchBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Fetch backend switch - backendSwitchRes, backendSwitchErr := s.getRequest("/services/haproxy/configuration/backend_switching_rules", fetchBackendSwitchRequestQueryParams) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to fetch backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPSLink: ", err) - } - }(backendSwitchRes.Body) - // Parse backend switch - backendSwitchData := map[string]interface{}{} - bodyBytes, err := io.ReadAll(backendSwitchRes.Body) + backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 443, backendName, domainName) if err != nil { - return errors.New("failed to read backend switch response body") + return err } - err = json.Unmarshal(bodyBytes, &backendSwitchData) - if err != nil { - return errors.New("failed to parse backend switch response body") - } - // Find backend switch - backendSwitchDataArray := backendSwitchData["data"].([]interface{}) - backendSwitchDataArrayIndex := -1 - for i, backendSwitchDataArrayItem := range backendSwitchDataArray { - backendSwitchDataArrayItemMap := backendSwitchDataArrayItem.(map[string]interface{}) - if backendSwitchDataArrayItemMap["name"] == backendName && strings.Contains(backendSwitchDataArrayItemMap["cond_test"].(string), domainName) { - backendSwitchDataArrayIndex = i - break - } - } - if backendSwitchDataArrayIndex == -1 { - return errors.New("failed to find backend switch") + if backendSwitchIndex == -1 { + return nil } + // Build query parameters deleteBackendSwitchRequestQueryParams := QueryParameters{} deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) // Delete backend switch - deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchDataArrayIndex), deleteBackendSwitchRequestQueryParams) + deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchIndex), deleteBackendSwitchRequestQueryParams) if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { return errors.New("failed to delete backend switch") } diff --git a/haproxy_manager/predefined_link_test.go b/haproxy_manager/predefined_link_test.go new file mode 100644 index 0000000000..8219163722 --- /dev/null +++ b/haproxy_manager/predefined_link_test.go @@ -0,0 +1,157 @@ +package haproxymanager + +import ( + "gotest.tools/v3/assert" + "strings" + "testing" +) + +func TestPredefinedLink(t *testing.T) { + + t.Run("add http link", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend dummy_backend if { hdr(host) -i example.com }" + // ensure http link doesn't exist + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), false, "http link should not exist") + // add http link + err := haproxyTestManager.AddHTTPLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure http link exists + config = fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "http link should exist") + }) + + t.Run("add duplicate http link should silently ignore", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend dummy_backend if { hdr(host) -i example.com }" + // add http link + err := haproxyTestManager.AddHTTPLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure http link exists + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "http link should exist") + // add duplicate http link + err = haproxyTestManager.AddHTTPLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure http link exists + config = fetchConfig(transactionId) + assert.Equal(t, strings.Count(config, output), 1, "http link should exist") + }) + + t.Run("delete http link", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend dummy_backend if { hdr(host) -i example.com }" + + // add http link + err := haproxyTestManager.AddHTTPLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure http link exists + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "http link should exist") + // delete http link + err = haproxyTestManager.DeleteHTTPLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure http link doesn't exist + config = fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), false, "http link should not exist") + }) + + t.Run("delete non-existing http link should not return error", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + err := haproxyTestManager.DeleteHTTPLink(transactionId, "dummy_backend", "example.com") + assert.Equal(t, err, nil, "delete non-existing http link should not return error") + }) + + t.Run("add https link", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend dummy_backend if { hdr(host) -i example.com }" + // ensure https link doesn't exist + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), false, "https link should not exist") + // add https link + err := haproxyTestManager.AddHTTPSLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure https link exists + config = fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "https link should exist") + }) + + t.Run("add duplicate https link should silently ignore", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend dummy_backend if { hdr(host) -i example.com }" + // add https link + err := haproxyTestManager.AddHTTPSLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure https link exists + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "https link should exist") + // add duplicate https link + err = haproxyTestManager.AddHTTPSLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure https link exists + config = fetchConfig(transactionId) + assert.Equal(t, strings.Count(config, output), 1, "https link should exist") + }) + + t.Run("delete https link", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend dummy_backend if { hdr(host) -i example.com }" + + // add https link + err := haproxyTestManager.AddHTTPSLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure https link exists + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "https link should exist") + // delete https link + err = haproxyTestManager.DeleteHTTPSLink(transactionId, "dummy_backend", "example.com") + if err != nil { + t.Fatal(err) + } + // ensure https link doesn't exist + config = fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), false, "https link should not exist") + }) + + t.Run("delete non-existing https link should not return error", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + err := haproxyTestManager.DeleteHTTPSLink(transactionId, "dummy_backend", "example.com") + assert.Equal(t, err, nil, "delete non-existing https link should not return error") + }) + +} From 6dbd5b9c848c29111b388967cce3dda6227b46f1 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:04:20 +0530 Subject: [PATCH 09/13] chore: code refactor --- haproxy_manager/backend_switch.go | 25 +++++-- haproxy_manager/frontend.go | 7 ++ haproxy_manager/predefined_link.go | 114 ++--------------------------- 3 files changed, 34 insertions(+), 112 deletions(-) diff --git a/haproxy_manager/backend_switch.go b/haproxy_manager/backend_switch.go index ef91cf1c39..c35f9428e7 100644 --- a/haproxy_manager/backend_switch.go +++ b/haproxy_manager/backend_switch.go @@ -20,10 +20,26 @@ func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMod "name": backendName, } } else { + // check if backend switch already exists + index, err := s.FetchBackendSwitchIndex(transactionId, listenerMode, bindPort, backendName, domainName) + if err != nil { + return err + } + if index != -1 { + return nil + } + condTest := `{ hdr(host) -i ` + strings.TrimSpace(domainName) + `:` + strconv.Itoa(bindPort) + ` }` + if bindPort == 80 || bindPort == 443 { + condTest = `{ hdr(host) -i ` + strings.TrimSpace(domainName) + ` }` + } + aclIndex := 0 + if listenerMode == HTTPMode && (bindPort == 80 || bindPort == 443) { + aclIndex = 1 + } reqBody = map[string]interface{}{ "cond": "if", - "cond_test": `{ hdr(host) -i ` + strings.TrimSpace(domainName) + `:` + strconv.Itoa(bindPort) + ` }`, - "index": 0, + "cond_test": condTest, + "index": aclIndex, "name": backendName, } } @@ -43,10 +59,7 @@ func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMod } func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) (int, error) { - return s.FetchBackendSwitchIndexByName(transactionId, GenerateFrontendName(listenerMode, bindPort), bindPort, backendName, domainName) -} - -func (s Manager) FetchBackendSwitchIndexByName(transactionId string, frontendName string, bindPort int, backendName string, domainName string) (int, error) { + frontendName := GenerateFrontendName(listenerMode, bindPort) params := QueryParameters{} params.add("transaction_id", transactionId) params.add("frontend", frontendName) diff --git a/haproxy_manager/frontend.go b/haproxy_manager/frontend.go index 3ec6d2d376..486ed807c8 100644 --- a/haproxy_manager/frontend.go +++ b/haproxy_manager/frontend.go @@ -11,6 +11,13 @@ import ( var defaultBackend = "error_backend" func GenerateFrontendName(listenerMode ListenerMode, port int) string { + if listenerMode == HTTPMode { + if port == 80 { + return "fe_http" + } else if port == 443 { + return "fe_https" + } + } return "fe_" + string(listenerMode) + "_" + strconv.Itoa(port) } diff --git a/haproxy_manager/predefined_link.go b/haproxy_manager/predefined_link.go index a56a99aedc..250d3d7a7d 100644 --- a/haproxy_manager/predefined_link.go +++ b/haproxy_manager/predefined_link.go @@ -1,154 +1,56 @@ package haproxymanager -import ( - "bytes" - "encoding/json" - "errors" - "io" - "log" - "strconv" -) - // AddHTTPLink Add HTTP Link [Backend Switch] to HAProxy configuration // -- Manage ACLs with frontend [only domain_name] // -- Manage rules with frontend and backend switch func (s Manager) AddHTTPLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_http" // Check if backend switch already exists - backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 80, backendName, domainName) + backendSwitchIndex, err := s.FetchBackendSwitchIndex(transactionId, HTTPMode, 80, backendName, domainName) if err != nil { return err } if backendSwitchIndex != -1 { return nil } - // Build query parameters - addBackendSwitchRequestQueryParams := QueryParameters{} - addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - addBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Add backend switch request body - addBackendSwitchRequestBody := map[string]interface{}{ - "cond": "if", - "cond_test": `{ hdr(host) -i ` + domainName + ` }`, - "index": 1, - "name": backendName, - } - addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) - if err != nil { - return errors.New("failed to marshal add_backend_switch_request_body") - } - // Send add backend switch request - backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to add backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddHTTPLink: ", err) - } - }(backendSwitchRes.Body) - return nil + return s.AddBackendSwitch(transactionId, HTTPMode, 80, backendName, domainName) } // DeleteHTTPLink Delete HTTP Link from HAProxy configuration func (s Manager) DeleteHTTPLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_http" - backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 80, backendName, domainName) + backendSwitchIndex, err := s.FetchBackendSwitchIndex(transactionId, HTTPMode, 80, backendName, domainName) if err != nil { return err } if backendSwitchIndex == -1 { return nil } - // Build query parameters - deleteBackendSwitchRequestQueryParams := QueryParameters{} - deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) - - // Delete backend switch - deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchIndex), deleteBackendSwitchRequestQueryParams) - if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { - return errors.New("failed to delete backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPLink: ", err) - } - }(deleteBackendSwitchRes.Body) - return nil + return s.DeleteBackendSwitch(transactionId, HTTPMode, 80, backendName, domainName) } // AddHTTPSLink Add HTTPS Link [Backend Switch] to HAProxy configuration // -- Manage ACLs with frontend [only domain_name] // -- Manage rules with frontend and backend switch func (s Manager) AddHTTPSLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_https" // Check if backend switch already exists - backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 443, backendName, domainName) + backendSwitchIndex, err := s.FetchBackendSwitchIndex(transactionId, HTTPMode, 443, backendName, domainName) if err != nil { return err } if backendSwitchIndex != -1 { return nil } - // Build query parameters - addBackendSwitchRequestQueryParams := QueryParameters{} - addBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - addBackendSwitchRequestQueryParams.add("frontend", frontendName) - // Add backend switch request body - addBackendSwitchRequestBody := map[string]interface{}{ - "cond": "if", - "cond_test": `{ hdr(host) -i ` + domainName + ` }`, - "index": 1, - "name": backendName, - } - addBackendSwitchRequestBodyBytes, err := json.Marshal(addBackendSwitchRequestBody) - if err != nil { - return errors.New("failed to marshal add_backend_switch_request_body") - } - // Send add backend switch request - backendSwitchRes, backendSwitchErr := s.postRequest("/services/haproxy/configuration/backend_switching_rules", addBackendSwitchRequestQueryParams, bytes.NewReader(addBackendSwitchRequestBodyBytes)) - if backendSwitchErr != nil || !isValidStatusCode(backendSwitchRes.StatusCode) { - return errors.New("failed to add backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] AddHTTPSLink: ", err) - } - }(backendSwitchRes.Body) - return nil + return s.AddBackendSwitch(transactionId, HTTPMode, 443, backendName, domainName) } // DeleteHTTPSLink Delete HTTPS Link from HAProxy configuration func (s Manager) DeleteHTTPSLink(transactionId string, backendName string, domainName string) error { - frontendName := "fe_https" // Build query parameters - backendSwitchIndex, err := s.FetchBackendSwitchIndexByName(transactionId, frontendName, 443, backendName, domainName) + backendSwitchIndex, err := s.FetchBackendSwitchIndex(transactionId, HTTPMode, 443, backendName, domainName) if err != nil { return err } if backendSwitchIndex == -1 { return nil } - - // Build query parameters - deleteBackendSwitchRequestQueryParams := QueryParameters{} - deleteBackendSwitchRequestQueryParams.add("transaction_id", transactionId) - deleteBackendSwitchRequestQueryParams.add("frontend", frontendName) - - // Delete backend switch - deleteBackendSwitchRes, deleteBackendSwitchErr := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(backendSwitchIndex), deleteBackendSwitchRequestQueryParams) - if deleteBackendSwitchErr != nil || !isValidStatusCode(deleteBackendSwitchRes.StatusCode) { - return errors.New("failed to delete backend switch") - } - defer func(Body io.ReadCloser) { - err := Body.Close() - if err != nil { - log.Println("[haproxy_manager] DeleteHTTPSLink: ", err) - } - }(deleteBackendSwitchRes.Body) - return nil + return s.DeleteBackendSwitch(transactionId, HTTPMode, 443, backendName, domainName) } From f55b72fb806578b457fd4c8a9c259a099f608950 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:36:00 +0530 Subject: [PATCH 10/13] test: haproxy custom link links testcases added --- haproxy_manager/backend.go | 2 - haproxy_manager/backend_switch.go | 37 ++++--- haproxy_manager/custom_link.go | 5 - haproxy_manager/custom_link_test.go | 103 ++++++++++++++++++ haproxy_manager/frontend.go | 10 +- haproxy_manager/frontend_test.go | 4 +- .../process_application_deploy_request.go | 2 +- 7 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 haproxy_manager/custom_link_test.go diff --git a/haproxy_manager/backend.go b/haproxy_manager/backend.go index 7d689e8b4a..f788a2917d 100644 --- a/haproxy_manager/backend.go +++ b/haproxy_manager/backend.go @@ -123,7 +123,6 @@ func (s Manager) GetReplicaCount(transactionId string, serviceName string, port // Check if backend exist isBackendExist, err := s.IsBackendExist(transactionId, backendName) if err != nil { - log.Println(err) return 0, err } @@ -177,7 +176,6 @@ func (s Manager) UpdateBackendReplicas(transactionId string, serviceName string, // Check if backend exist isBackendExist, err := s.IsBackendExist(transactionId, backendName) if err != nil { - log.Println(err) return err } diff --git a/haproxy_manager/backend_switch.go b/haproxy_manager/backend_switch.go index c35f9428e7..7c63240cbf 100644 --- a/haproxy_manager/backend_switch.go +++ b/haproxy_manager/backend_switch.go @@ -9,9 +9,17 @@ import ( ) func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) error { + // check if backend switch already exists + index, err := s.FetchBackendSwitchIndex(transactionId, listenerMode, bindPort, backendName, domainName) + if err != nil { + return err + } + if index != -1 { + return nil + } params := QueryParameters{} params.add("transaction_id", transactionId) - params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) + params.add("frontend", s.GenerateFrontendName(listenerMode, bindPort)) var reqBody map[string]interface{} // for tcp mode, just add use_backend rule without ACL if listenerMode == TCPMode { @@ -20,14 +28,6 @@ func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMod "name": backendName, } } else { - // check if backend switch already exists - index, err := s.FetchBackendSwitchIndex(transactionId, listenerMode, bindPort, backendName, domainName) - if err != nil { - return err - } - if index != -1 { - return nil - } condTest := `{ hdr(host) -i ` + strings.TrimSpace(domainName) + `:` + strconv.Itoa(bindPort) + ` }` if bindPort == 80 || bindPort == 443 { condTest = `{ hdr(host) -i ` + strings.TrimSpace(domainName) + ` }` @@ -59,7 +59,7 @@ func (s Manager) AddBackendSwitch(transactionId string, listenerMode ListenerMod } func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode ListenerMode, bindPort int, backendName string, domainName string) (int, error) { - frontendName := GenerateFrontendName(listenerMode, bindPort) + frontendName := s.GenerateFrontendName(listenerMode, bindPort) params := QueryParameters{} params.add("transaction_id", transactionId) params.add("frontend", frontendName) @@ -84,10 +84,17 @@ func (s Manager) FetchBackendSwitchIndex(transactionId string, listenerMode List } for _, r := range backendSwitchRules { rule := r.(map[string]interface{}) - if rule["name"] == backendName && - rule["cond"] == "if" && - rule["cond_test"] == condTest { - return int(rule["index"].(float64)), nil + + if listenerMode == HTTPMode { + if rule["name"] == backendName && + rule["cond"] == "if" && + rule["cond_test"] == condTest { + return int(rule["index"].(float64)), nil + } + } else { + if rule["name"] == backendName { + return int(rule["index"].(float64)), nil + } } } return -1, nil @@ -111,7 +118,7 @@ func (s Manager) DeleteBackendSwitch(transactionId string, listenerMode Listener // Build query parameters params := QueryParameters{} params.add("transaction_id", transactionId) - params.add("frontend", GenerateFrontendName(listenerMode, bindPort)) + params.add("frontend", s.GenerateFrontendName(listenerMode, bindPort)) deleteReq, err := s.deleteRequest("/services/haproxy/configuration/backend_switching_rules/"+strconv.Itoa(index), params) if err != nil || !isValidStatusCode(deleteReq.StatusCode) { return err diff --git a/haproxy_manager/custom_link.go b/haproxy_manager/custom_link.go index 30a834b1fd..760b92c961 100644 --- a/haproxy_manager/custom_link.go +++ b/haproxy_manager/custom_link.go @@ -1,14 +1,9 @@ package haproxymanager -import "errors" - // AddTCPLink Add TCP Frontend to HAProxy configuration // -- Manage ACLs with frontend [port{required} and domain_name{optional}] // -- Manage rules with frontend and backend switch func (s Manager) AddTCPLink(transactionId string, backendName string, port int, domainName string, listenerMode ListenerMode, restrictedPorts []int) error { - if IsPortRestrictedForManualConfig(port, restrictedPorts) { - return errors.New("port is restricted for manual configuration") - } // Add Frontend err := s.AddFrontend(transactionId, listenerMode, port, restrictedPorts) if err != nil { diff --git a/haproxy_manager/custom_link_test.go b/haproxy_manager/custom_link_test.go new file mode 100644 index 0000000000..0644ab3d50 --- /dev/null +++ b/haproxy_manager/custom_link_test.go @@ -0,0 +1,103 @@ +package haproxymanager + +import ( + "gotest.tools/v3/assert" + "strings" + "testing" +) + +func TestCustomLink(t *testing.T) { + + t.Run("add tcp link", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend " + haproxyTestManager.GenerateBackendName("dummy_backend", 8080) + + // create tcp frontend + backendName, err := haproxyTestManager.AddBackend(transactionId, "dummy_backend", 8080, 2) + if err != nil { + t.Fatal(err) + } + // ensure tcp link doesn't exist + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), false, "tcp link should not exist") + // add tcp link + err = haproxyTestManager.AddTCPLink(transactionId, backendName, 8080, "", TCPMode, []int{}) + if err != nil { + t.Fatal(err) + } + // check now + config = fetchConfig(transactionId) + t.Log(config) + assert.Equal(t, strings.Contains(config, output), true, "tcp link should exist") + }) + + t.Run("add duplicate tcp link should silently ignore", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend " + haproxyTestManager.GenerateBackendName("dummy_backend", 8080) + + // create tcp frontend + backendName, err := haproxyTestManager.AddBackend(transactionId, "dummy_backend", 8080, 2) + if err != nil { + t.Fatal(err) + } + // add tcp link + err = haproxyTestManager.AddTCPLink(transactionId, backendName, 8080, "", TCPMode, []int{}) + if err != nil { + t.Fatal(err) + } + // ensure tcp link exists + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "tcp link should exist") + // add duplicate tcp link + err = haproxyTestManager.AddTCPLink(transactionId, backendName, 8080, "", TCPMode, []int{}) + if err != nil { + t.Fatal(err) + } + // ensure tcp link exists only once + config = fetchConfig(transactionId) + t.Log(config) + assert.Equal(t, strings.Count(config, output), 1, "tcp link should exist") + }) + + t.Run("delete tcp link", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + output := "use_backend " + haproxyTestManager.GenerateBackendName("dummy_backend", 8080) + + // create tcp frontend + backendName, err := haproxyTestManager.AddBackend(transactionId, "dummy_backend", 8080, 2) + if err != nil { + t.Fatal(err) + } + // add tcp link + err = haproxyTestManager.AddTCPLink(transactionId, backendName, 8080, "", TCPMode, []int{}) + if err != nil { + t.Fatal(err) + } + // ensure tcp link exists + config := fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), true, "tcp link should exist") + // delete tcp link + err = haproxyTestManager.DeleteTCPLink(transactionId, backendName, 8080, "", TCPMode) + if err != nil { + t.Fatal(err) + } + // ensure tcp link doesn't exist + config = fetchConfig(transactionId) + assert.Equal(t, strings.Contains(config, output), false, "tcp link should not exist") + }) + + t.Run("delete non-existing tcp link should silently ignore [no frontend]", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + + // delete tcp link + err := haproxyTestManager.DeleteTCPLink(transactionId, "dummy_backend", 8080, "", TCPMode) + assert.NilError(t, err, "delete non-existing tcp link should not return error") + }) +} diff --git a/haproxy_manager/frontend.go b/haproxy_manager/frontend.go index 486ed807c8..32ced2c195 100644 --- a/haproxy_manager/frontend.go +++ b/haproxy_manager/frontend.go @@ -10,7 +10,7 @@ import ( var defaultBackend = "error_backend" -func GenerateFrontendName(listenerMode ListenerMode, port int) string { +func (s Manager) GenerateFrontendName(listenerMode ListenerMode, port int) string { if listenerMode == HTTPMode { if port == 80 { return "fe_http" @@ -25,7 +25,7 @@ func (s Manager) AddFrontend(transactionId string, listenerMode ListenerMode, bi if bindPort == 80 || bindPort == 443 { return nil } - frontendName := GenerateFrontendName(listenerMode, bindPort) + frontendName := s.GenerateFrontendName(listenerMode, bindPort) if IsPortRestrictedForManualConfig(bindPort, restrictedPorts) { return errors.New("port is restricted for manual configuration") } @@ -83,7 +83,7 @@ func (s Manager) AddFrontend(transactionId string, listenerMode ListenerMode, bi } func (s Manager) IsFrontendExist(transactionId string, listenerMode ListenerMode, bindPort int) (bool, error) { - frontendName := GenerateFrontendName(listenerMode, bindPort) + frontendName := s.GenerateFrontendName(listenerMode, bindPort) params := QueryParameters{} params.add("transaction_id", transactionId) // Send request to check if frontend exist @@ -104,7 +104,7 @@ func (s Manager) IsFrontendExist(transactionId string, listenerMode ListenerMode } func (s Manager) IsOtherSwitchingRuleExist(transactionId string, listenerMode ListenerMode, bindPort int) (bool, error) { - frontendName := GenerateFrontendName(listenerMode, bindPort) + frontendName := s.GenerateFrontendName(listenerMode, bindPort) params := QueryParameters{} params.add("transaction_id", transactionId) params.add("frontend", frontendName) @@ -152,7 +152,7 @@ func (s Manager) DeleteFrontend(transactionId string, listenerMode ListenerMode, } } // delete frontend - frontendName := GenerateFrontendName(listenerMode, bindPort) + frontendName := s.GenerateFrontendName(listenerMode, bindPort) params := QueryParameters{} params.add("transaction_id", transactionId) // Send request diff --git a/haproxy_manager/frontend_test.go b/haproxy_manager/frontend_test.go index 94cdb77799..084d606dca 100644 --- a/haproxy_manager/frontend_test.go +++ b/haproxy_manager/frontend_test.go @@ -22,7 +22,7 @@ func TestFrontend(t *testing.T) { t.Fatal(err) } assert.Check(t, isExists == true, "frontend should exist [api]") - assert.Check(t, strings.Compare(GenerateFrontendName(TCPMode, 8080), "fe_tcp_8080") == 0, "frontend name should be fe_tcp_8080") + assert.Check(t, strings.Compare(haproxyTestManager.GenerateFrontendName(TCPMode, 8080), "fe_tcp_8080") == 0, "frontend name should be fe_tcp_8080") }) t.Run("add http frontend", func(t *testing.T) { @@ -39,7 +39,7 @@ func TestFrontend(t *testing.T) { t.Fatal(err) } assert.Check(t, isExists == true, "frontend should exist [api]") - assert.Check(t, strings.Compare(GenerateFrontendName(HTTPMode, 8080), "fe_http_8080") == 0, "frontend name should be fe_http_8080") + assert.Check(t, strings.Compare(haproxyTestManager.GenerateFrontendName(HTTPMode, 8080), "fe_http_8080") == 0, "frontend name should be fe_http_8080") }) t.Run("add duplicate frontend", func(t *testing.T) { diff --git a/swiftwave_service/worker/process_application_deploy_request.go b/swiftwave_service/worker/process_application_deploy_request.go index 9d1a6e55e9..53192f53c5 100644 --- a/swiftwave_service/worker/process_application_deploy_request.go +++ b/swiftwave_service/worker/process_application_deploy_request.go @@ -247,7 +247,7 @@ func (m Manager) deployApplicationHelper(request DeployApplicationRequest, docke transactionIdMap[haproxyManager] = haproxyTransactionId for _, targetPort := range targetPorts { backendName := haproxyManager.GenerateBackendName(application.Name, targetPort) - isBackendExist, err := haproxyManager.IsBackendExist(backendName) + isBackendExist, err := haproxyManager.IsBackendExist(haproxyTransactionId, backendName) if err != nil { isFailed = true log.Println("failed to check if backend exist", err) From b40e51851936fc98542ec5c9f4cc79c51ba3b5b5 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:48:19 +0530 Subject: [PATCH 11/13] test: haproxy tcp frontend advanced check testcases added --- haproxy_manager/frontend.go | 21 ++++++++++++++++++ haproxy_manager/frontend_test.go | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/haproxy_manager/frontend.go b/haproxy_manager/frontend.go index 32ced2c195..f59795e254 100644 --- a/haproxy_manager/frontend.go +++ b/haproxy_manager/frontend.go @@ -23,6 +23,9 @@ func (s Manager) GenerateFrontendName(listenerMode ListenerMode, port int) strin func (s Manager) AddFrontend(transactionId string, listenerMode ListenerMode, bindPort int, restrictedPorts []int) error { if bindPort == 80 || bindPort == 443 { + if listenerMode == TCPMode { + return errors.New("frontend with tcp mode cannot be created for port 80 or 443") + } return nil } frontendName := s.GenerateFrontendName(listenerMode, bindPort) @@ -34,6 +37,24 @@ func (s Manager) AddFrontend(transactionId string, listenerMode ListenerMode, bi if isFrontendExist { return nil } + // if frontend with tcp/http mode already exists, then raise error + if listenerMode == TCPMode { + isConflictingFrontendExist, err := s.IsFrontendExist(transactionId, HTTPMode, bindPort) + if err != nil { + return err + } + if isConflictingFrontendExist { + return errors.New("frontend with http mode already exists") + } + } else if listenerMode == HTTPMode { + isConflictingFrontendExist, err := s.IsFrontendExist(transactionId, TCPMode, bindPort) + if err != nil { + return err + } + if isConflictingFrontendExist { + return errors.New("frontend with tcp mode already exists") + } + } // create frontend params := QueryParameters{} params.add("transaction_id", transactionId) diff --git a/haproxy_manager/frontend_test.go b/haproxy_manager/frontend_test.go index 084d606dca..7c934d4204 100644 --- a/haproxy_manager/frontend_test.go +++ b/haproxy_manager/frontend_test.go @@ -250,4 +250,41 @@ func TestFrontend(t *testing.T) { err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{8080}) assert.Check(t, err != nil, "frontend creation should fail for restricted port") }) + + t.Run("if http frontend exists can't create tcp frontend at same port", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddFrontend(transactionId, TCPMode, 8080, []int{}) + assert.Check(t, err != nil, "tcp frontend creation should fail if http frontend exists") + }) + + t.Run("if tcp frontend exists can't create http frontend at same port", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 8080, []int{}) + if err != nil { + t.Fatal(err) + } + err = haproxyTestManager.AddFrontend(transactionId, HTTPMode, 8080, []int{}) + assert.Check(t, err != nil, "http frontend creation should fail if tcp frontend exists") + }) + + t.Run("add tcp frontend at port 80 should raise error", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 80, []int{}) + assert.Check(t, err != nil, "tcp frontend creation should fail for port 80") + }) + + t.Run("add tcp frontend at port 443 should raise error", func(t *testing.T) { + transactionId := newTransaction() + defer deleteTransaction(transactionId) + err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 443, []int{}) + assert.Check(t, err != nil, "tcp frontend creation should fail for port 443") + }) + } From 88a67f31127b78618475b1d409053ab72d6aa847 Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:48:34 +0530 Subject: [PATCH 12/13] chore: reformat --- haproxy_manager/frontend_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/haproxy_manager/frontend_test.go b/haproxy_manager/frontend_test.go index 7c934d4204..aa922adbbb 100644 --- a/haproxy_manager/frontend_test.go +++ b/haproxy_manager/frontend_test.go @@ -286,5 +286,4 @@ func TestFrontend(t *testing.T) { err := haproxyTestManager.AddFrontend(transactionId, TCPMode, 443, []int{}) assert.Check(t, err != nil, "tcp frontend creation should fail for port 443") }) - } From 54b9ff797956272ba17895ac47243369195bb7aa Mon Sep 17 00:00:00 2001 From: Tanmoy Sarkar <57363826+tanmoysrt@users.noreply.github.com> Date: Fri, 22 Mar 2024 20:51:54 +0530 Subject: [PATCH 13/13] chore: remove test line --- haproxy_manager/custom_link_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/haproxy_manager/custom_link_test.go b/haproxy_manager/custom_link_test.go index 0644ab3d50..b0a1a8d6de 100644 --- a/haproxy_manager/custom_link_test.go +++ b/haproxy_manager/custom_link_test.go @@ -29,7 +29,6 @@ func TestCustomLink(t *testing.T) { } // check now config = fetchConfig(transactionId) - t.Log(config) assert.Equal(t, strings.Contains(config, output), true, "tcp link should exist") }) @@ -59,7 +58,6 @@ func TestCustomLink(t *testing.T) { } // ensure tcp link exists only once config = fetchConfig(transactionId) - t.Log(config) assert.Equal(t, strings.Count(config, output), 1, "tcp link should exist") })