diff --git a/README.md b/README.md index 1ad76a7..297ade4 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,27 @@ and `goldpinger` should show something like this: ![screenshot-DNS-resolution](./extras/dns-screenshot.png) +### TCP and HTTP checks to external targets + +Instances can also be configured to do simple TCP or HTTP checks on external targets. This is useful for visualizing more nuanced connectivity flows. + +```sh + --tcp-targets= A list of external targets(: or :) to attempt a TCP check on (space delimited) [$TCP_TARGETS] + --http-targets= A list of external targets(://) to attempt an HTTP{S} check on. A 200 HTTP code is considered successful. (space delimited) [$HTTP_TARGETS] + --tcp-targets-timeout= The timeout for a tcp check on the provided tcp-targets (default: 500) [$TCP_TARGETS_TIMEOUT] + --dns-targets-timeout= The timeout for a tcp check on the provided udp-targets (default: 500) [$DNS_TARGETS_TIMEOUT] +``` + +```yaml + - name: HTTP_TARGETS + value: http://bloomberg.com + - name: TCP_TARGETS + value: 10.34.5.141:5000 10.34.195.193:6442 +``` + +the timeouts for the TCP, DNS and HTTP checks can be configured via `TCP_TARGETS_TIMEOUT`, `DNS_TARGETS_TIMEOUT` and `HTTP_TARGETS_TIMEOUT` respectively. + +![screenshot-tcp-http-checks](./extras/tcp-checks-screenshot.png) ## Usage diff --git a/cmd/goldpinger/main.go b/cmd/goldpinger/main.go index 2239834..670830f 100644 --- a/cmd/goldpinger/main.go +++ b/cmd/goldpinger/main.go @@ -144,6 +144,23 @@ func main() { logger.Error("Unknown IP version specified: expected values are 4 or 6", zap.Strings("IPVersions", goldpinger.GoldpingerConfig.IPVersions)) } + // Handle deprecated flags + if int(goldpinger.GoldpingerConfig.PingTimeout) == 0 { + logger.Warn("ping-timeout-ms is deprecated in favor of ping-timeout and will be removed in the future", + zap.Int64("ping-timeout-ms", goldpinger.GoldpingerConfig.PingTimeoutMs)) + goldpinger.GoldpingerConfig.PingTimeout = time.Duration(goldpinger.GoldpingerConfig.PingTimeoutMs) * time.Millisecond + } + if int(goldpinger.GoldpingerConfig.CheckTimeout) == 0 { + logger.Warn("check-timeout-ms is deprecated in favor of check-timeout and will be removed in the future", + zap.Int64("check-timeout-ms", goldpinger.GoldpingerConfig.CheckTimeoutMs)) + goldpinger.GoldpingerConfig.CheckTimeout = time.Duration(goldpinger.GoldpingerConfig.CheckTimeoutMs) * time.Millisecond + } + if int(goldpinger.GoldpingerConfig.CheckAllTimeout) == 0 { + logger.Warn("check-all-timeout-ms is deprecated in favor of check-all-timeout will be removed in the future", + zap.Int64("check-all-timeout-ms", goldpinger.GoldpingerConfig.CheckAllTimeoutMs)) + goldpinger.GoldpingerConfig.CheckAllTimeout = time.Duration(goldpinger.GoldpingerConfig.CheckAllTimeoutMs) * time.Millisecond + } + server.ConfigureAPI() goldpinger.StartUpdater() diff --git a/extras/tcp-checks-screenshot.png b/extras/tcp-checks-screenshot.png new file mode 100644 index 0000000..a7094d4 Binary files /dev/null and b/extras/tcp-checks-screenshot.png differ diff --git a/go.mod b/go.mod index 0184b8e..885824e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.15 require ( github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/cespare/xxhash v1.1.0 - github.com/go-openapi/errors v0.20.0 + github.com/go-openapi/errors v0.20.2 github.com/go-openapi/loads v0.19.5 github.com/go-openapi/runtime v0.19.26 github.com/go-openapi/spec v0.19.8 diff --git a/go.sum b/go.sum index 43e9f8b..0b0dfbc 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/errors v0.20.0 h1:Sxpo9PjEHDzhs3FbnGNonvDgWcMW2U7wGTcDDSFSceM= github.com/go-openapi/errors v0.20.0/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8= +github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= diff --git a/pkg/goldpinger/client.go b/pkg/goldpinger/client.go index 9b77eb9..64c0ab6 100644 --- a/pkg/goldpinger/client.go +++ b/pkg/goldpinger/client.go @@ -37,15 +37,12 @@ func CheckNeighbours(ctx context.Context) *models.CheckResults { // Mux to prevent concurrent map address checkResultsMux.Lock() defer checkResultsMux.Unlock() - final := models.CheckResults{} final.PodResults = make(map[string]models.PodResult) for podName, podResult := range checkResults.PodResults { final.PodResults[podName] = podResult } - if len(GoldpingerConfig.DnsHosts) > 0 { - final.DNSResults = *checkDNS() - } + final.ProbeResults = checkTargets() return &final } @@ -127,22 +124,58 @@ func pickPodHostIP(podIP, hostIP string) string { return podIP } -func checkDNS() *models.DNSResults { - results := models.DNSResults{} - for _, host := range GoldpingerConfig.DnsHosts { +func checkTargets() models.ProbeResults { + results := make(map[string][]models.ProbeResult) + probes := []struct { + protocol string + hosts []string + probeFn func(addr string, timeout time.Duration) error + statFn func(host string) + timeout time.Duration + }{ + { + protocol: "dns", + hosts: GoldpingerConfig.DnsHosts, + probeFn: doDNSProbe, + statFn: CountDnsError, + timeout: GoldpingerConfig.DnsCheckTimeout, + }, + { + protocol: "http", + hosts: GoldpingerConfig.HTTPTargets, + probeFn: doHTTPProbe, + statFn: CountHttpError, + timeout: GoldpingerConfig.HTTPCheckTimeout, + }, + { + protocol: "tcp", + hosts: GoldpingerConfig.TCPTargets, + probeFn: doTCPProbe, + statFn: CountTcpError, + timeout: GoldpingerConfig.TCPCheckTimeout, + }, + } + + for _, probe := range probes { + for _, host := range probe.hosts { + if _, ok := results[host]; !ok { + results[host] = []models.ProbeResult{} + } - var dnsResult models.DNSResult + res := models.ProbeResult{Protocol: probe.protocol} + start := time.Now() + err := probe.probeFn(host, probe.timeout) + if err != nil { + res.Error = err.Error() + probe.statFn(host) + } - start := time.Now() - _, err := net.LookupIP(host) - if err != nil { - dnsResult.Error = err.Error() - CountDnsError(host) + res.ResponseTimeMs = time.Since(start).Milliseconds() + results[host] = append(results[host], res) } - dnsResult.ResponseTimeMs = time.Since(start).Nanoseconds() / int64(time.Millisecond) - results[host] = dnsResult } - return &results + + return results } // CheckServicePodsResult results of the /check operation @@ -195,7 +228,7 @@ func CheckAllPods(checkAllCtx context.Context, pods map[string]*GoldpingerPod) * } else { checkCtx, cancel := context.WithTimeout( checkAllCtx, - time.Duration(GoldpingerConfig.CheckTimeoutMs)*time.Millisecond, + GoldpingerConfig.CheckTimeout, ) defer cancel() @@ -238,15 +271,15 @@ func CheckAllPods(checkAllCtx context.Context, pods map[string]*GoldpingerPod) * PodIP: response.podIPv4, }) if response.checkAllPodResult.Response != nil && - response.checkAllPodResult.Response.DNSResults != nil { - if result.DNSResults == nil { - result.DNSResults = make(map[string]models.DNSResults) + response.checkAllPodResult.Response.ProbeResults != nil { + if result.ProbeResults == nil { + result.ProbeResults = make(map[string]models.ProbeResults) } - for host := range response.checkAllPodResult.Response.DNSResults { - if result.DNSResults[host] == nil { - result.DNSResults[host] = make(map[string]models.DNSResult) + for host := range response.checkAllPodResult.Response.ProbeResults { + if result.ProbeResults[host] == nil { + result.ProbeResults[host] = make(map[string][]models.ProbeResult) } - result.DNSResults[host][response.podName] = response.checkAllPodResult.Response.DNSResults[host] + result.ProbeResults[host][response.podName] = response.checkAllPodResult.Response.ProbeResults[host] } } } diff --git a/pkg/goldpinger/config.go b/pkg/goldpinger/config.go index b71da60..3b8abd7 100644 --- a/pkg/goldpinger/config.go +++ b/pkg/goldpinger/config.go @@ -15,6 +15,8 @@ package goldpinger import ( + "time" + "k8s.io/client-go/kubernetes" ) @@ -35,12 +37,20 @@ var GoldpingerConfig = struct { DisplayNodeName bool `long:"display-nodename" description:"Display nodename other than podname in UI (defaults is podname)." env:"DISPLAY_NODENAME"` KubernetesClient *kubernetes.Clientset - DnsHosts []string `long:"host-to-resolve" description:"A host to attempt dns resolve on (space delimited)" env:"HOSTS_TO_RESOLVE" env-delim:" "` + DnsHosts []string `long:"host-to-resolve" description:"A host to attempt dns resolve on (space delimited)" env:"HOSTS_TO_RESOLVE" env-delim:" "` + TCPTargets []string `long:"tcp-targets" description:"A list of external targets(: or :) to attempt a TCP check on (space delimited)" env:"TCP_TARGETS" env-delim:" "` + HTTPTargets []string `long:"http-targets" description:"A list of external targets(://) to attempt an HTTP{S} check on. A 200 HTTP code is considered successful.(space delimited)" env:"HTTP_TARGETS" env-delim:" "` IPVersions []string `long:"ip-versions" description:"The IP versions to use (space delimited). Possible values are 4 and 6 (defaults to 4)." env:"IP_VERSIONS" env-delim:" "` // Timeouts - PingTimeoutMs int64 `long:"ping-timeout-ms" description:"The timeout in milliseconds for a ping call to other goldpinger pods" env:"PING_TIMEOUT_MS" default:"300"` - CheckTimeoutMs int64 `long:"check-timeout-ms" description:"The timeout in milliseconds for a check call to other goldpinger pods" env:"CHECK_TIMEOUT_MS" default:"1000"` - CheckAllTimeoutMs int64 `long:"check-all-timeout-ms" description:"The timeout in milliseconds for a check-all call to other goldpinger pods" env:"CHECK_ALL_TIMEOUT_MS" default:"5000"` + PingTimeoutMs int64 `long:"ping-timeout-ms" description:"The timeout in milliseconds for a ping call to other goldpinger pods(deprecated)" env:"PING_TIMEOUT_MS" default:"300"` + CheckTimeoutMs int64 `long:"check-timeout-ms" description:"The timeout in milliseconds for a check call to other goldpinger pods(deprecated)" env:"CHECK_TIMEOUT_MS" default:"1000"` + CheckAllTimeoutMs int64 `long:"check-all-timeout-ms" description:"The timeout in milliseconds for a check-all call to other goldpinger pods(deprecated)" env:"CHECK_ALL_TIMEOUT_MS" default:"5000"` + PingTimeout time.Duration `long:"ping-timeout" description:"The timeout for a ping call to other goldpinger pods" env:"PING_TIMEOUT" default:"300ms"` + CheckTimeout time.Duration `long:"check-timeout" description:"The timeout for a check call to other goldpinger pods" env:"CHECK_TIMEOUT" default:"1000ms"` + CheckAllTimeout time.Duration `long:"check-all-timeout" description:"The timeout for a check-all call to other goldpinger pods" env:"CHECK_ALL_TIMEOUT" default:"5000ms"` + TCPCheckTimeout time.Duration `long:"tcp-targets-timeout" description:"The timeout for a tcp check on the provided tcp-targets" env:"TCP_TARGETS_TIMEOUT" default:"500ms"` + DnsCheckTimeout time.Duration `long:"dns-targets-timeout" description:"The timeout for a dns check on the provided dns-targets" env:"DNS_TARGETS_TIMEOUT" default:"500ms"` + HTTPCheckTimeout time.Duration `long:"http-targets-timeout" description:"The timeout for a http check on the provided http-targets" env:"HTTP_TARGETS_TIMEOUT" default:"500ms"` }{} diff --git a/pkg/goldpinger/heatmap.go b/pkg/goldpinger/heatmap.go index 781a051..3377f31 100644 --- a/pkg/goldpinger/heatmap.go +++ b/pkg/goldpinger/heatmap.go @@ -26,7 +26,6 @@ import ( "net/http" "sort" "strconv" - "time" "go.uber.org/zap" "golang.org/x/image/font" @@ -83,7 +82,7 @@ func HeatmapHandler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout( r.Context(), - time.Duration(GoldpingerConfig.CheckAllTimeoutMs)*time.Millisecond, + GoldpingerConfig.CheckAllTimeout, ) defer cancel() diff --git a/pkg/goldpinger/pinger.go b/pkg/goldpinger/pinger.go index e83cd1e..0553aeb 100644 --- a/pkg/goldpinger/pinger.go +++ b/pkg/goldpinger/pinger.go @@ -46,7 +46,7 @@ type Pinger struct { func NewPinger(pod *GoldpingerPod, resultsChan chan<- PingAllPodsResult) *Pinger { p := Pinger{ pod: pod, - timeout: time.Duration(GoldpingerConfig.PingTimeoutMs) * time.Millisecond, + timeout: GoldpingerConfig.PingTimeout, resultsChan: resultsChan, stopChan: make(chan struct{}), diff --git a/pkg/goldpinger/probes.go b/pkg/goldpinger/probes.go new file mode 100644 index 0000000..c9b55ea --- /dev/null +++ b/pkg/goldpinger/probes.go @@ -0,0 +1,72 @@ +// Copyright 2018 Bloomberg Finance L.P. +// +// 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. + +package goldpinger + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "net/url" + "time" +) + +func doDNSProbe(addr string, timeout time.Duration) error { + resolver := net.Resolver{} + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + ips, err := resolver.LookupHost(ctx, addr) + if len(ips) == 0 { + return fmt.Errorf("%s was resolved to 0 ips", addr) + } + return err +} + +func doTCPProbe(addr string, timeout time.Duration) error { + conn, err := net.DialTimeout("tcp", addr, timeout) + if conn != nil { + defer conn.Close() + } + return err +} + +func doHTTPProbe(addr string, timeout time.Duration) error { + client := http.Client{Timeout: timeout} + u, err := url.Parse(addr) + if err != nil { + return err + } + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("invalid url scheme: '%s' in address", u.Scheme) + } + if u.Scheme == "https" { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + } + resp, err := client.Get(addr) + if err != nil { + return err + } + + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("%s returned non-200 resp: %d", addr, resp.StatusCode) + } + return err +} diff --git a/pkg/goldpinger/stats.go b/pkg/goldpinger/stats.go index 9798caa..3014301 100644 --- a/pkg/goldpinger/stats.go +++ b/pkg/goldpinger/stats.go @@ -103,7 +103,26 @@ var ( "host", }, ) - + goldPingerTcpErrorsCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "goldpinger_tcp_errors_total", + Help: "Statistics of TCP probe errors per instance", + }, + []string{ + "goldpinger_instance", + "host", + }, + ) + goldPingerHttpErrorsCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "goldpinger_http_errors_total", + Help: "Statistics of HTTP probe errors per instance", + }, + []string{ + "goldpinger_instance", + "host", + }, + ) bootTime = time.Now() ) @@ -115,6 +134,8 @@ func init() { prometheus.MustRegister(goldpingerResponseTimeKubernetesHistogram) prometheus.MustRegister(goldpingerErrorsCounter) prometheus.MustRegister(goldpingerDnsErrorsCounter) + prometheus.MustRegister(goldPingerHttpErrorsCounter) + prometheus.MustRegister(goldPingerTcpErrorsCounter) zap.L().Info("Metrics setup - see /metrics") } @@ -173,6 +194,22 @@ func CountDnsError(host string) { ).Inc() } +// CountTcpError counts instances of tcp errors for prober +func CountTcpError(host string) { + goldPingerTcpErrorsCounter.WithLabelValues( + GoldpingerConfig.Hostname, + host, + ).Inc() +} + +// CountHttpError counts instances of tcp errors for prober +func CountHttpError(host string) { + goldPingerHttpErrorsCounter.WithLabelValues( + GoldpingerConfig.Hostname, + host, + ).Inc() +} + // returns a timer for easy observing of the durations of calls to kubernetes API func GetLabeledKubernetesCallsTimer() *prometheus.Timer { return prometheus.NewTimer( diff --git a/pkg/goldpinger/updater.go b/pkg/goldpinger/updater.go index c538d43..a8c9c49 100644 --- a/pkg/goldpinger/updater.go +++ b/pkg/goldpinger/updater.go @@ -153,14 +153,17 @@ func updateCounters() { } } CountHealthyUnhealthyNodes(counterHealthy, float64(len(checkResults.PodResults))-counterHealthy) - // check DNS, but don't block the access to checkResultsMux + // check external targets, don't block the access to checkResultsMux nodesHealthy := int(counterHealthy) == len(checkResults.PodResults) go func(healthySoFar bool) { if healthySoFar { - for _, response := range *checkDNS() { - if response.Error != "" { - healthySoFar = false - break + probeResults := checkTargets() + for host := range probeResults { + for _, response := range probeResults[host] { + if response.Error != "" { + healthySoFar = false + break + } } } } diff --git a/pkg/models/check_all_pod_result.go b/pkg/models/check_all_pod_result.go index 86dcfb2..5936c09 100644 --- a/pkg/models/check_all_pod_result.go +++ b/pkg/models/check_all_pod_result.go @@ -95,6 +95,8 @@ func (m *CheckAllPodResult) validateResponse(formats strfmt.Registry) error { if err := m.Response.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("response") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("response") } return err } @@ -123,6 +125,8 @@ func (m *CheckAllPodResult) contextValidateResponse(ctx context.Context, formats if err := m.Response.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("response") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("response") } return err } diff --git a/pkg/models/check_all_results.go b/pkg/models/check_all_results.go index b795d8c..f2cd6e4 100644 --- a/pkg/models/check_all_results.go +++ b/pkg/models/check_all_results.go @@ -23,9 +23,6 @@ type CheckAllResults struct { // o k OK *bool `json:"OK,omitempty"` - // dns results - DNSResults map[string]DNSResults `json:"dnsResults,omitempty"` - // hosts Hosts []*CheckAllResultsHostsItems0 `json:"hosts"` @@ -35,6 +32,9 @@ type CheckAllResults struct { // hosts number HostsNumber int32 `json:"hosts-number,omitempty"` + // probe results + ProbeResults map[string]ProbeResults `json:"probeResults,omitempty"` + // responses Responses map[string]CheckAllPodResult `json:"responses,omitempty"` } @@ -43,11 +43,11 @@ type CheckAllResults struct { func (m *CheckAllResults) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateDNSResults(formats); err != nil { + if err := m.validateHosts(formats); err != nil { res = append(res, err) } - if err := m.validateHosts(formats); err != nil { + if err := m.validateProbeResults(formats); err != nil { res = append(res, err) } @@ -61,15 +61,23 @@ func (m *CheckAllResults) Validate(formats strfmt.Registry) error { return nil } -func (m *CheckAllResults) validateDNSResults(formats strfmt.Registry) error { - if swag.IsZero(m.DNSResults) { // not required +func (m *CheckAllResults) validateHosts(formats strfmt.Registry) error { + if swag.IsZero(m.Hosts) { // not required return nil } - for k := range m.DNSResults { + for i := 0; i < len(m.Hosts); i++ { + if swag.IsZero(m.Hosts[i]) { // not required + continue + } - if val, ok := m.DNSResults[k]; ok { - if err := val.Validate(formats); err != nil { + if m.Hosts[i] != nil { + if err := m.Hosts[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("hosts" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("hosts" + "." + strconv.Itoa(i)) + } return err } } @@ -79,21 +87,15 @@ func (m *CheckAllResults) validateDNSResults(formats strfmt.Registry) error { return nil } -func (m *CheckAllResults) validateHosts(formats strfmt.Registry) error { - if swag.IsZero(m.Hosts) { // not required +func (m *CheckAllResults) validateProbeResults(formats strfmt.Registry) error { + if swag.IsZero(m.ProbeResults) { // not required return nil } - for i := 0; i < len(m.Hosts); i++ { - if swag.IsZero(m.Hosts[i]) { // not required - continue - } + for k := range m.ProbeResults { - if m.Hosts[i] != nil { - if err := m.Hosts[i].Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("hosts" + "." + strconv.Itoa(i)) - } + if val, ok := m.ProbeResults[k]; ok { + if err := val.Validate(formats); err != nil { return err } } @@ -115,6 +117,11 @@ func (m *CheckAllResults) validateResponses(formats strfmt.Registry) error { } if val, ok := m.Responses[k]; ok { if err := val.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("responses" + "." + k) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("responses" + "." + k) + } return err } } @@ -128,11 +135,11 @@ func (m *CheckAllResults) validateResponses(formats strfmt.Registry) error { func (m *CheckAllResults) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error - if err := m.contextValidateDNSResults(ctx, formats); err != nil { + if err := m.contextValidateHosts(ctx, formats); err != nil { res = append(res, err) } - if err := m.contextValidateHosts(ctx, formats); err != nil { + if err := m.contextValidateProbeResults(ctx, formats); err != nil { res = append(res, err) } @@ -146,12 +153,17 @@ func (m *CheckAllResults) ContextValidate(ctx context.Context, formats strfmt.Re return nil } -func (m *CheckAllResults) contextValidateDNSResults(ctx context.Context, formats strfmt.Registry) error { +func (m *CheckAllResults) contextValidateHosts(ctx context.Context, formats strfmt.Registry) error { - for k := range m.DNSResults { + for i := 0; i < len(m.Hosts); i++ { - if val, ok := m.DNSResults[k]; ok { - if err := val.ContextValidate(ctx, formats); err != nil { + if m.Hosts[i] != nil { + if err := m.Hosts[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("hosts" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("hosts" + "." + strconv.Itoa(i)) + } return err } } @@ -161,15 +173,12 @@ func (m *CheckAllResults) contextValidateDNSResults(ctx context.Context, formats return nil } -func (m *CheckAllResults) contextValidateHosts(ctx context.Context, formats strfmt.Registry) error { +func (m *CheckAllResults) contextValidateProbeResults(ctx context.Context, formats strfmt.Registry) error { - for i := 0; i < len(m.Hosts); i++ { + for k := range m.ProbeResults { - if m.Hosts[i] != nil { - if err := m.Hosts[i].ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("hosts" + "." + strconv.Itoa(i)) - } + if val, ok := m.ProbeResults[k]; ok { + if err := val.ContextValidate(ctx, formats); err != nil { return err } } diff --git a/pkg/models/check_results.go b/pkg/models/check_results.go index 8529629..2c0941c 100644 --- a/pkg/models/check_results.go +++ b/pkg/models/check_results.go @@ -19,22 +19,22 @@ import ( // swagger:model CheckResults type CheckResults struct { - // dns results - DNSResults DNSResults `json:"dnsResults,omitempty"` - // pod results PodResults map[string]PodResult `json:"podResults,omitempty"` + + // probe results + ProbeResults ProbeResults `json:"probeResults,omitempty"` } // Validate validates this check results func (m *CheckResults) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateDNSResults(formats); err != nil { + if err := m.validatePodResults(formats); err != nil { res = append(res, err) } - if err := m.validatePodResults(formats); err != nil { + if err := m.validateProbeResults(formats); err != nil { res = append(res, err) } @@ -44,23 +44,6 @@ func (m *CheckResults) Validate(formats strfmt.Registry) error { return nil } -func (m *CheckResults) validateDNSResults(formats strfmt.Registry) error { - if swag.IsZero(m.DNSResults) { // not required - return nil - } - - if m.DNSResults != nil { - if err := m.DNSResults.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("dnsResults") - } - return err - } - } - - return nil -} - func (m *CheckResults) validatePodResults(formats strfmt.Registry) error { if swag.IsZero(m.PodResults) { // not required return nil @@ -73,6 +56,11 @@ func (m *CheckResults) validatePodResults(formats strfmt.Registry) error { } if val, ok := m.PodResults[k]; ok { if err := val.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("podResults" + "." + k) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("podResults" + "." + k) + } return err } } @@ -82,15 +70,34 @@ func (m *CheckResults) validatePodResults(formats strfmt.Registry) error { return nil } +func (m *CheckResults) validateProbeResults(formats strfmt.Registry) error { + if swag.IsZero(m.ProbeResults) { // not required + return nil + } + + if m.ProbeResults != nil { + if err := m.ProbeResults.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("probeResults") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("probeResults") + } + return err + } + } + + return nil +} + // ContextValidate validate this check results based on the context it is used func (m *CheckResults) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error - if err := m.contextValidateDNSResults(ctx, formats); err != nil { + if err := m.contextValidatePodResults(ctx, formats); err != nil { res = append(res, err) } - if err := m.contextValidatePodResults(ctx, formats); err != nil { + if err := m.contextValidateProbeResults(ctx, formats); err != nil { res = append(res, err) } @@ -100,18 +107,6 @@ func (m *CheckResults) ContextValidate(ctx context.Context, formats strfmt.Regis return nil } -func (m *CheckResults) contextValidateDNSResults(ctx context.Context, formats strfmt.Registry) error { - - if err := m.DNSResults.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("dnsResults") - } - return err - } - - return nil -} - func (m *CheckResults) contextValidatePodResults(ctx context.Context, formats strfmt.Registry) error { for k := range m.PodResults { @@ -127,6 +122,20 @@ func (m *CheckResults) contextValidatePodResults(ctx context.Context, formats st return nil } +func (m *CheckResults) contextValidateProbeResults(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ProbeResults.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("probeResults") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("probeResults") + } + return err + } + + return nil +} + // MarshalBinary interface implementation func (m *CheckResults) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/models/ping_results.go b/pkg/models/ping_results.go index 51eb4e4..af703e1 100644 --- a/pkg/models/ping_results.go +++ b/pkg/models/ping_results.go @@ -66,6 +66,8 @@ func (m *PingResults) validateReceived(formats strfmt.Registry) error { if err := m.Received.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("received") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("received") } return err } @@ -94,6 +96,8 @@ func (m *PingResults) contextValidateReceived(ctx context.Context, formats strfm if err := m.Received.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("received") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("received") } return err } diff --git a/pkg/models/pod_result.go b/pkg/models/pod_result.go index 3f2d827..3e7f7e2 100644 --- a/pkg/models/pod_result.go +++ b/pkg/models/pod_result.go @@ -118,6 +118,8 @@ func (m *PodResult) validateResponse(formats strfmt.Registry) error { if err := m.Response.Validate(formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("response") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("response") } return err } @@ -146,6 +148,8 @@ func (m *PodResult) contextValidateResponse(ctx context.Context, formats strfmt. if err := m.Response.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("response") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("response") } return err } diff --git a/pkg/models/probe_result.go b/pkg/models/probe_result.go new file mode 100644 index 0000000..acda177 --- /dev/null +++ b/pkg/models/probe_result.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ProbeResult probe result +// +// swagger:model ProbeResult +type ProbeResult struct { + + // error + Error string `json:"error,omitempty"` + + // protocol + Protocol string `json:"protocol,omitempty"` + + // response time ms + ResponseTimeMs int64 `json:"response-time-ms,omitempty"` +} + +// Validate validates this probe result +func (m *ProbeResult) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this probe result based on context it is used +func (m *ProbeResult) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ProbeResult) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ProbeResult) UnmarshalBinary(b []byte) error { + var res ProbeResult + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/models/probe_results.go b/pkg/models/probe_results.go new file mode 100644 index 0000000..a70134f --- /dev/null +++ b/pkg/models/probe_results.go @@ -0,0 +1,78 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// ProbeResults probe results +// +// swagger:model ProbeResults +type ProbeResults map[string][]ProbeResult + +// Validate validates this probe results +func (m ProbeResults) Validate(formats strfmt.Registry) error { + var res []error + + for k := range m { + + if err := validate.Required(k, "body", m[k]); err != nil { + return err + } + + for i := 0; i < len(m[k]); i++ { + + if err := m[k][i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(k + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(k + "." + strconv.Itoa(i)) + } + return err + } + + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validate this probe results based on the context it is used +func (m ProbeResults) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + for k := range m { + + for i := 0; i < len(m[k]); i++ { + + if err := m[k][i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(k + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(k + "." + strconv.Itoa(i)) + } + return err + } + + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/restapi/configure_goldpinger.go b/pkg/restapi/configure_goldpinger.go index e6dfa5a..1815e40 100644 --- a/pkg/restapi/configure_goldpinger.go +++ b/pkg/restapi/configure_goldpinger.go @@ -20,7 +20,6 @@ import ( "context" "crypto/tls" "net/http" - "time" "strings" @@ -61,7 +60,7 @@ func configureAPI(api *operations.GoldpingerAPI) http.Handler { ctx, cancel := context.WithTimeout( params.HTTPRequest.Context(), - time.Duration(goldpinger.GoldpingerConfig.PingTimeoutMs)*time.Millisecond, + goldpinger.GoldpingerConfig.PingTimeout, ) defer cancel() @@ -74,7 +73,7 @@ func configureAPI(api *operations.GoldpingerAPI) http.Handler { ctx, cancel := context.WithTimeout( params.HTTPRequest.Context(), - time.Duration(goldpinger.GoldpingerConfig.CheckTimeoutMs)*time.Millisecond, + goldpinger.GoldpingerConfig.CheckTimeout, ) defer cancel() @@ -87,7 +86,7 @@ func configureAPI(api *operations.GoldpingerAPI) http.Handler { ctx, cancel := context.WithTimeout( params.HTTPRequest.Context(), - time.Duration(goldpinger.GoldpingerConfig.CheckAllTimeoutMs)*time.Millisecond, + goldpinger.GoldpingerConfig.CheckAllTimeout, ) defer cancel() @@ -100,7 +99,7 @@ func configureAPI(api *operations.GoldpingerAPI) http.Handler { ctx, cancel := context.WithTimeout( params.HTTPRequest.Context(), - time.Duration(goldpinger.GoldpingerConfig.CheckAllTimeoutMs)*time.Millisecond, + goldpinger.GoldpingerConfig.CheckAllTimeout, ) defer cancel() diff --git a/pkg/restapi/embedded_spec.go b/pkg/restapi/embedded_spec.go index 6121fdc..8c448b8 100644 --- a/pkg/restapi/embedded_spec.go +++ b/pkg/restapi/embedded_spec.go @@ -203,11 +203,23 @@ func init() { "type": "integer", "format": "int32" }, + "httpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/HttpResults" + } + }, "responses": { "type": "object", "additionalProperties": { "$ref": "#/definitions/CheckAllPodResult" } + }, + "tcpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/TcpResults" + } } } }, @@ -217,11 +229,17 @@ func init() { "dnsResults": { "$ref": "#/definitions/DnsResults" }, + "httpResults": { + "$ref": "#/definitions/HttpResults" + }, "podResults": { "type": "object", "additionalProperties": { "$ref": "#/definitions/PodResult" } + }, + "tcpResults": { + "$ref": "#/definitions/TcpResults" } } }, @@ -261,21 +279,10 @@ func init() { } } }, - "DnsResult": { - "properties": { - "error": { - "type": "string" - }, - "response-time-ms": { - "type": "number", - "format": "int64" - } - } - }, "DnsResults": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/DnsResult" + "$ref": "#/definitions/ProbeResult" } }, "HealthCheckResults": { @@ -295,6 +302,12 @@ func init() { } } }, + "HttpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ProbeResult" + } + }, "PingResults": { "type": "object", "properties": { @@ -342,6 +355,23 @@ func init() { "format": "int32" } } + }, + "ProbeResult": { + "properties": { + "error": { + "type": "string" + }, + "response-time-ms": { + "type": "number", + "format": "int64" + } + } + }, + "TcpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ProbeResult" + } } } }`)) @@ -518,11 +548,23 @@ func init() { "type": "integer", "format": "int32" }, + "httpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/HttpResults" + } + }, "responses": { "type": "object", "additionalProperties": { "$ref": "#/definitions/CheckAllPodResult" } + }, + "tcpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/TcpResults" + } } } }, @@ -548,11 +590,17 @@ func init() { "dnsResults": { "$ref": "#/definitions/DnsResults" }, + "httpResults": { + "$ref": "#/definitions/HttpResults" + }, "podResults": { "type": "object", "additionalProperties": { "$ref": "#/definitions/PodResult" } + }, + "tcpResults": { + "$ref": "#/definitions/TcpResults" } } }, @@ -592,21 +640,10 @@ func init() { } } }, - "DnsResult": { - "properties": { - "error": { - "type": "string" - }, - "response-time-ms": { - "type": "number", - "format": "int64" - } - } - }, "DnsResults": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/DnsResult" + "$ref": "#/definitions/ProbeResult" } }, "HealthCheckResults": { @@ -626,6 +663,12 @@ func init() { } } }, + "HttpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ProbeResult" + } + }, "PingResults": { "type": "object", "properties": { @@ -673,6 +716,23 @@ func init() { "format": "int32" } } + }, + "ProbeResult": { + "properties": { + "error": { + "type": "string" + }, + "response-time-ms": { + "type": "number", + "format": "int64" + } + } + }, + "TcpResults": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ProbeResult" + } } } }`)) diff --git a/pkg/restapi/operations/check_all_pods.go b/pkg/restapi/operations/check_all_pods.go index 44bb319..f388530 100644 --- a/pkg/restapi/operations/check_all_pods.go +++ b/pkg/restapi/operations/check_all_pods.go @@ -42,7 +42,7 @@ type CheckAllPods struct { func (o *CheckAllPods) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewCheckAllPodsParams() if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params diff --git a/pkg/restapi/operations/check_service_pods.go b/pkg/restapi/operations/check_service_pods.go index b956747..7bc58c3 100644 --- a/pkg/restapi/operations/check_service_pods.go +++ b/pkg/restapi/operations/check_service_pods.go @@ -42,7 +42,7 @@ type CheckServicePods struct { func (o *CheckServicePods) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewCheckServicePodsParams() if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params diff --git a/pkg/restapi/operations/cluster_health.go b/pkg/restapi/operations/cluster_health.go index 6d28b83..0685907 100644 --- a/pkg/restapi/operations/cluster_health.go +++ b/pkg/restapi/operations/cluster_health.go @@ -42,7 +42,7 @@ type ClusterHealth struct { func (o *ClusterHealth) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewClusterHealthParams() if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params diff --git a/pkg/restapi/operations/healthz.go b/pkg/restapi/operations/healthz.go index 8bde162..94eab94 100644 --- a/pkg/restapi/operations/healthz.go +++ b/pkg/restapi/operations/healthz.go @@ -42,7 +42,7 @@ type Healthz struct { func (o *Healthz) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewHealthzParams() if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params diff --git a/pkg/restapi/operations/ping.go b/pkg/restapi/operations/ping.go index fc49602..9ac3026 100644 --- a/pkg/restapi/operations/ping.go +++ b/pkg/restapi/operations/ping.go @@ -42,7 +42,7 @@ type Ping struct { func (o *Ping) ServeHTTP(rw http.ResponseWriter, r *http.Request) { route, rCtx, _ := o.Context.RouteInfo(r) if rCtx != nil { - r = rCtx + *r = *rCtx } var Params = NewPingParams() if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params diff --git a/pkg/restapi/server.go b/pkg/restapi/server.go index ec2e636..0ef5a83 100644 --- a/pkg/restapi/server.go +++ b/pkg/restapi/server.go @@ -305,9 +305,6 @@ func (s *Server) Serve() (err error) { s.Fatalf("no certificate was configured for TLS") } - // must have at least one certificate or panics - httpsServer.TLSConfig.BuildNameToCertificate() - configureServer(httpsServer, "https", s.httpsServerL.Addr().String()) servers = append(servers, httpsServer) diff --git a/static/index.html b/static/index.html index b5985cc..5dc43ea 100644 --- a/static/index.html +++ b/static/index.html @@ -213,6 +213,7 @@ var global_data = undefined; var s; // the sigma graph + var main = function(timeout){ timeout = timeout || Number($("#timeout").val()) || 5.0; @@ -229,7 +230,6 @@ // prepare nodes var nodes = []; - var dnsCheckNodes = []; var podIPs = []; var resp = data.responses; for (let podIP in resp) { @@ -284,39 +284,44 @@ }) }) console.log(nodes); - - if ('dnsResults' in data) { - var yoffset = 0; - for (let checkedHost in data.dnsResults) { - let value = data.dnsResults[checkedHost]; - var allOk = true; - for (let pod in value) { - var podData = value[pod]; - var elapsed = 0; - if ('response-time-ms' in podData) { - elapsed = podData['response-time-ms']; - } - var podOk = (!('error' in podData)); - allOk = allOk && podOk - edges.push({ - source: pod, - target: checkedHost, - _data: { + + var probeResultsNodes = []; + if ('probeResults' in data) { + var yoffset = 0; + for (let checkedHost in data.probeResults) { + let value = data.probeResults[checkedHost]; + var allOk = true; + for (let pod in value) { + for (var i = 0; i < value[pod].length; i++){ + var elapsed = 0; + var podData = value[pod][i]; + if ('response-time-ms' in podData) { + elapsed = podData['response-time-ms']; + } + var podOk = (!('error' in podData)); + allOk = allOk && podOk + edges.push({ + source: pod, + target: checkedHost, + _data: { "OK": podOk, "elapsed": elapsed, - "isDNSCheckNode": true, - } - }); + "isprobeResultsNode": true, + } + }); + } } + value["OK"] = allOk - dnsCheckNodes.push({ - "label": checkedHost, - "id": checkedHost, - "x": 0, - "y": 0, - _data: value, - }); - } + probeResultsNodes.push({ + "label": checkedHost, + "id": checkedHost, + "x": 0, + "y": 0, + _data: value, + }); + + } } // build the actual graph @@ -351,7 +356,7 @@ }); // generate any dns nodes on the graph - dnsCheckNodes.forEach(function(node, i, a){ + probeResultsNodes.forEach(function(node, i, a){ node.x = 2; node.y = (0.6 * i / a.length) - 0.8; node.size = 10; @@ -359,7 +364,6 @@ if (!node._data.OK) { node.color = "red"; } - //console.log(node); s.graph.addNode(node); }); @@ -374,7 +378,7 @@ if (!edge._data.OK) { color = "red"; } - if ("isDNSCheckNode" in edge._data) { + if ("isprobeResultsNode" in edge._data) { type = "dashed"; } var edge = { diff --git a/swagger.yml b/swagger.yml index 0d03c48..48324eb 100644 --- a/swagger.yml +++ b/swagger.yml @@ -12,17 +12,21 @@ definitions: type: integer ping: type: integer - DnsResult: + ProbeResult: properties: response-time-ms: type: number format: int64 error: type: string - DnsResults: + protocol: + type: string + ProbeResults: type: object additionalProperties: - $ref: '#/definitions/DnsResult' + type: array + items: + $ref: '#/definitions/ProbeResult' PingResults: type: object properties: @@ -60,8 +64,8 @@ definitions: CheckResults: type: object properties: - dnsResults: - $ref: '#/definitions/DnsResults' + probeResults: + $ref: '#/definitions/ProbeResults' podResults: type: object additionalProperties: @@ -110,10 +114,10 @@ definitions: podIP: type: string format: ipv4 - dnsResults: + probeResults: type: object additionalProperties: - $ref: '#/definitions/DnsResults' + $ref: '#/definitions/ProbeResults' responses: type: object additionalProperties: