Skip to content

Commit

Permalink
Merge pull request #910 from ywc689/udpping_checker
Browse files Browse the repository at this point in the history
healthcheck: more checker methods and bugfix
  • Loading branch information
ywc689 authored Sep 15, 2023
2 parents e250eb9 + 1ca55d8 commit 6e4bbfe
Show file tree
Hide file tree
Showing 11 changed files with 385 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/ipvs/ip_vs_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ struct dp_vs_conn *dp_vs_schedule(struct dp_vs_service *svc,

dest = svc->scheduler->schedule(svc, mbuf, iph);
if (!dest) {
RTE_LOG(WARNING, IPVS, "%s: no dest found.\n", __func__);
RTE_LOG(INFO, IPVS, "%s: no dest found.\n", __func__);
#ifdef CONFIG_DPVS_MBUF_DEBUG
dp_vs_mbuf_dump("found dest failed.", iph->af, mbuf);
#endif
Expand Down
2 changes: 1 addition & 1 deletion tools/dpvs-agent/cmd/ipvs/post_vs_vip_port_rs.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (h *postVsRs) Handle(params apiVs.PostVsVipPortRsParams) middleware.Respond
rss[i].SetProto(front.GetProto())
rss[i].SetAddr(rs.IP)
rss[i].SetInhibited(rs.Inhibited)
rss[i].SetOverloaded(rs.Inhibited)
rss[i].SetOverloaded(rs.Overloaded)
rss[i].SetFwdMode(fwdmode)
}

Expand Down
2 changes: 1 addition & 1 deletion tools/dpvs-agent/cmd/ipvs/put_vs_vip_port_rs.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (h *putVsRs) Handle(params apiVs.PutVsVipPortRsParams) middleware.Responder
rss[i].SetWeight(uint32(rs.Weight))
rss[i].SetFwdMode(fwdmode)
rss[i].SetInhibited(rs.Inhibited)
rss[i].SetOverloaded(rs.Inhibited)
rss[i].SetOverloaded(rs.Overloaded)
}
}

Expand Down
182 changes: 182 additions & 0 deletions tools/healthcheck/pkg/helthcheck/http_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright 2023 IQiYi Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The healthcheck package refers to the framework of "github.com/google/
// seesaw/healthcheck" heavily, with only some adaption changes for DPVS.

package hc

import (
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)

var _ CheckMethod = (*HttpChecker)(nil)

type HttpCodeRange struct {
start int // inclusive
end int // inclusive
}

// HttpChecker contains configuration specific to a HTTP(S) healthcheck.
type HttpChecker struct {
Config *CheckerConfig

Method string
Host string
Uri string
ResponseCodes []HttpCodeRange
Response string

Secure bool
TLSVerify bool
Proxy bool
}

// NewHttpChecker returns an initialised HttpChecker.
func NewHttpChecker(method, host, uri string) *HttpChecker {
if len(method) == 0 {
method = "GET"
}
if len(uri) == 0 {
uri = "/"
}
return &HttpChecker{
Method: method,
Uri: uri,
ResponseCodes: []HttpCodeRange{{200, 299}, {300, 399}, {400, 499}},
Response: "",
Secure: false,
TLSVerify: true,
Proxy: false,
}
}

func (hc *HttpChecker) BindConfig(conf *CheckerConfig) {
hc.Config = conf
if len(hc.Host) == 0 {
hc.Host = conf.Target.Addr()
}
}

// String returns the string representation of a HTTP healthcheck.
func (hc *HttpChecker) String() string {
attr := []string{hc.Method, hc.Host, hc.Uri}
if hc.Secure {
attr = append(attr, "secure")
if hc.TLSVerify {
attr = append(attr, "tls-verify")
}
}
if hc.Proxy {
attr = append(attr, "proxy")
}

return fmt.Sprintf("HTTP checker for %v [%s]", hc.Config.Id, strings.Join(attr, ", "))
}

// Check executes a HTTP healthcheck.
func (hc *HttpChecker) Check(target Target, timeout time.Duration) *Result {
var msg string
if hc.Secure {
msg = fmt.Sprintf("HTTPS %s to %s", hc.Method, hc.Host)
} else {
msg = fmt.Sprintf("HTTP %s to %s", hc.Method, hc.Host)
}

start := time.Now()
if timeout == time.Duration(0) {
timeout = DefaultCheckConfig.Timeout
}

u, err := url.Parse(hc.Uri)
if err != nil {
return NewResult(start, fmt.Sprintf("%s; url parse failed", msg), false, err)
}
if hc.Secure {
u.Scheme = "https"
} else {
u.Scheme = "http"
}
if len(u.Host) == 0 {
u.Host = hc.Host
}

proxy := (func(*http.Request) (*url.URL, error))(nil)
if hc.Proxy {
proxy = http.ProxyURL(u)
}

tlsConfig := &tls.Config{
InsecureSkipVerify: !hc.TLSVerify,
}
client := &http.Client{
Transport: &http.Transport{
Proxy: proxy,
TLSClientConfig: tlsConfig,
},
Timeout: timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return errors.New("redirect not permitted")
},
}

req, err := http.NewRequest(hc.Method, hc.Uri, nil)
req.URL = u

// If we received a response we want to process it, even in the
// presence of an error - a redirect 3xx will result in both the
// response and an error being returned.
resp, err := client.Do(req)
if resp == nil {
return NewResult(start, fmt.Sprintf("%s; got no response", msg), false, err)
}
if resp.Body != nil {
defer resp.Body.Close()
}

// Check response code.
codeOk := false
for _, cr := range hc.ResponseCodes {
if resp.StatusCode >= cr.start && resp.StatusCode <= cr.end {
codeOk = true
break
}
}

// Check response body.
bodyOk := false
msg = fmt.Sprintf("%s; got %s", msg, resp.Status)
if len(hc.Response) == 0 {
bodyOk = true
} else if resp.Body != nil {
buf := make([]byte, len(hc.Response))
n, err := io.ReadFull(resp.Body, buf)
if err != nil && err != io.ErrUnexpectedEOF {
msg = fmt.Sprintf("%s; failed to read HTTP response", msg)
} else if string(buf) != hc.Response {
msg = fmt.Sprintf("%s; unexpected response - %q", msg, string(buf[0:n]))
} else {
bodyOk = true
}
}

return NewResult(start, msg, codeOk && bodyOk, nil)
}
78 changes: 78 additions & 0 deletions tools/healthcheck/pkg/helthcheck/http_checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2023 IQiYi Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The healthcheck package refers to the framework of "github.com/google/
// seesaw/healthcheck" heavily, with only some adaption changes for DPVS.

package hc

import (
"fmt"
"net"
"strings"
"testing"
"time"

"github.com/iqiyi/dpvs/tools/healthcheck/pkg/utils"
)

var http_targets = []Target{
{net.ParseIP("192.168.88.30"), 80, utils.IPProtoTCP},
{net.ParseIP("192.168.88.30"), 443, utils.IPProtoTCP},
{net.ParseIP("2001::30"), 80, utils.IPProtoTCP},
{net.ParseIP("2001::30"), 443, utils.IPProtoTCP},
}

var http_url_targets = []string{
"http://www.baidu.com",
"https://www.baidu.com",
"http://www.iqiyi.com",
"https://www.iqiyi.com",
}

func TestHttpChecker(t *testing.T) {
for _, target := range http_targets {
checker := NewHttpChecker("", "", "")
checker.Host = target.Addr()
/*
if target.Port == 443 {
checker.Secure = true
}
*/
id := Id(target.String())
config := NewCheckerConfig(&id, checker, &target, StateUnknown,
0, 3*time.Second, 2*time.Second, 3)
result := checker.Check(target, config.Timeout)
fmt.Printf("[ HTTP ] %s ==> %v\n", target, result)
}

for _, target := range http_url_targets {
host := target[strings.Index(target, "://")+3:]
checker := NewHttpChecker("GET", target, "")
checker.Host = host
checker.ResponseCodes = []HttpCodeRange{{200, 200}}
if strings.HasPrefix(target, "https") {
checker.Secure = true
}
id := Id(host)
config := NewCheckerConfig(&id, checker, &Target{}, StateUnknown,
0, 3*time.Second, 2*time.Second, 3)
result := checker.Check(Target{}, config.Timeout)
if result.Success == false {
t.Errorf("[ HTTP ] %s ==> %v\n", target, result)
} else {
fmt.Printf("[ HTTP ] %s ==> %v\n", target, result)
}
}
}
7 changes: 4 additions & 3 deletions tools/healthcheck/pkg/helthcheck/ping_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
package hc

import (
"fmt"
"net"
"testing"
"time"
)

var targets = []Target{
var ping_targets = []Target{
{net.ParseIP("127.0.0.1"), 0, 0},
{net.ParseIP("192.168.88.30"), 0, 0},
{net.ParseIP("11.22.33.44"), 0, 0},
Expand All @@ -33,13 +34,13 @@ var targets = []Target{
}

func TestPingChecker(t *testing.T) {
for _, target := range targets {
for _, target := range ping_targets {
checker := NewPingChecker()
id := Id(target.IP.String())
config := NewCheckerConfig(&id, checker,
&target, StateUnknown, 0,
3*time.Second, 1*time.Second, 3)
result := checker.Check(target, config.Timeout)
t.Logf("%v", result)
fmt.Printf("[ Ping ]%s ==>%v\n", target, result)
}
}
8 changes: 5 additions & 3 deletions tools/healthcheck/pkg/helthcheck/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ func (s *Server) NewChecker(typ lb.Checker, proto utils.IPProto) CheckMethod {
checker = NewUDPChecker("", "")
case lb.CheckerPING:
checker = NewPingChecker()
case lb.CheckerUDPPing:
checker = NewUDPPingChecker("", "")
case lb.CheckerNone:
if s.config.LbAutoMethod {
switch proto {
case utils.IPProtoTCP:
checker = NewTCPChecker("", "")
case utils.IPProtoUDP:
checker = NewUDPChecker("", "")
checker = NewUDPPingChecker("", "")
}
}
}
Expand All @@ -109,9 +111,9 @@ func (s *Server) getHealthchecks() (*Checkers, error) {
}
weight := rs.Weight
state := StateUnknown
if weight > 0 {
if weight > 0 && rs.Inhibited == false {
state = StateHealthy
} else if rs.Inhibited {
} else if weight == 0 && rs.Inhibited == true {
state = StateUnhealthy
}
// TODO: allow users to specify check interval, timeout and retry
Expand Down
4 changes: 2 additions & 2 deletions tools/healthcheck/pkg/helthcheck/udp_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (hc *UDPChecker) String() string {
return fmt.Sprintf("UDP checker for %v", hc.Config.Id)
}

// Check executes a UDP healthcheck.
// Check executes an UDP healthcheck.
func (hc *UDPChecker) Check(target Target, timeout time.Duration) *Result {
msg := fmt.Sprintf("UDP check to %s", target.Addr())
start := time.Now()
Expand Down Expand Up @@ -81,7 +81,7 @@ func (hc *UDPChecker) Check(target Target, timeout time.Duration) *Result {
return NewResult(start, msg, false, err)
}

buf := make([]byte, len(hc.Receive)+1)
buf := make([]byte, len(hc.Receive))
n, _, err := udpConn.ReadFrom(buf)
if err != nil {
if hc.Send == "" && hc.Receive == "" {
Expand Down
Loading

0 comments on commit 6e4bbfe

Please sign in to comment.