Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

healthcheck: more checker methods and bugfix #910

Merged
merged 5 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading