Skip to content

Commit 33a4354

Browse files
author
Girish Kalele
committed
Node network health check utility - performs a quick HTTP GET test
1 parent 2b47e14 commit 33a4354

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

pkg/util/nethealth/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Nethealth HTTP Check
2+
3+
The nethealth binary performs a quick HTTP GET download speed check
4+
Key Features:
5+
- Shell-script friendly - returns a non-zero exit code on timeout or corruption or bandwidth threshold failures
6+
- Timeout configurable to abort the test early (for super slow links)
7+
- Can compare actual bandwidth against a command line minimum bandwidth parameter and return a non-zero exit code.
8+
- Corruption check - can download a checksum file and compute blob checksum and compare.
9+
- Configurable object URL for non-GCE environments
10+
11+
## Generating content files
12+
13+
The following steps can be used if you wish to generate content files for different sizes.
14+
15+
Assumptions: openssl installed and 64MB blob
16+
17+
```
18+
$ openssl rand -out 64MB.bin 67108864
19+
$ openssl sha512 64MB.bin > sha512.txt
20+
$ cat sha512.txt
21+
SHA512(64MB.bin)= 9abddc26a6bda88864326bdc8130f0653562669fcc7f617b26a53ea73781d5cb2eb9418fde53a5743c86aa2c493aeb8933d829a812d275c2fc4ecd84427999bf
22+
$
23+
```
24+
25+
26+
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/util/nethealth/README.md?pixel)]()
27+
28+
29+
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/util/nethealth/README.md?pixel)]()

pkg/util/nethealth/nethealth.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
Copyright 2014 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// The nethealth binary performs a quick HTTP GET download speed check
18+
// Key Features:
19+
// Shell-script friendly - returns a non-zero exit code on timeout or corruption or bandwidth threshold failures
20+
// Timeout configurable to abort the test early (for super slow links)
21+
// Can compare actual bandwidth against a command line minimum bandwidth parameter and return a non-zero exit code.
22+
// Corruption check - can download a checksum file and compute blob checksum and compare.
23+
// Configurable object URL for non-GCE environments
24+
25+
package main
26+
27+
import (
28+
"crypto/sha512"
29+
"encoding/hex"
30+
"flag"
31+
"io/ioutil"
32+
"log"
33+
"net/http"
34+
"strings"
35+
"time"
36+
)
37+
38+
var (
39+
objectUrl string
40+
objectHashUrl string
41+
objectLength int64
42+
timeout int
43+
minimum int64
44+
)
45+
46+
func init() {
47+
// Defaults to a public bucket with a 64 MB file with random data
48+
flag.StringVar(&objectUrl, "url", "http://storage.googleapis.com/k8s-bandwidth-test/64MB.bin", "Blob URL")
49+
flag.StringVar(&objectHashUrl, "hashurl", "http://storage.googleapis.com/k8s-bandwidth-test/sha512.txt", "Blob Hash URL")
50+
flag.Int64Var(&objectLength, "length", 64*1024*1024, "Expected content length")
51+
flag.IntVar(&timeout, "timeout", 30, "Maximum Seconds to wait")
52+
// If the transfer bandwidth is lower than the minimum, process returns non-zero exit status
53+
flag.Int64Var(&minimum, "minimum", 10, "Minimum bandwidth expected (MiB/sec)")
54+
}
55+
56+
func monitorTimeElapsed(downloadStartTime time.Time, timeoutSeconds time.Duration) {
57+
for true {
58+
time.Sleep(1 * time.Second)
59+
// Check the status of the Get request if possible and check if timeout elapsed
60+
if time.Since(downloadStartTime) > timeoutSeconds*time.Second {
61+
log.Fatalf("ERROR: Timeout (%d) seconds occurred before GET finished - declaring TOO SLOW", timeout)
62+
}
63+
}
64+
}
65+
66+
func main() {
67+
flag.Parse()
68+
// Quick object existence check before a full GET
69+
res, err := http.Head(objectUrl)
70+
if err != nil {
71+
log.Fatalf("Failed to find URL %s (%s)", objectUrl, err)
72+
}
73+
if res.ContentLength != objectLength {
74+
log.Fatalf("Length reported (%d) is not equal to expected length (%d)", res.ContentLength, objectLength)
75+
}
76+
log.Printf("HTTP HEAD reports content length: %d - running GET\n", res.ContentLength)
77+
res, err = http.Head(objectHashUrl)
78+
if err != nil {
79+
log.Fatalf("Failed to find hash URL %s (%s)", objectHashUrl, err)
80+
}
81+
/* Now, setup a Client with a transport with compression disabled and timeouts enabled */
82+
tr := &http.Transport{
83+
DisableCompression: true,
84+
}
85+
downloadStartTime := time.Now()
86+
go monitorTimeElapsed(downloadStartTime, time.Duration(timeout))
87+
client := &http.Client{Transport: tr}
88+
res, err = client.Get(objectUrl)
89+
if err != nil {
90+
log.Fatalf("Failure (%s) while reading %s", err, objectUrl)
91+
}
92+
if res.ContentLength != objectLength {
93+
log.Fatalf("Length reported (%d) is not equal to expected length (%d)", res.ContentLength, objectLength)
94+
}
95+
blobData, err := ioutil.ReadAll(res.Body)
96+
res.Body.Close()
97+
if err != nil {
98+
log.Fatal("Failed to read full content", err)
99+
}
100+
elapsedMs := int64(time.Since(downloadStartTime) / time.Millisecond)
101+
bandwidth := (res.ContentLength * 1000) / (elapsedMs * 1024)
102+
103+
log.Printf("DOWNLOAD: %d bytes %d ms Bandwidth ~ %d KiB/sec\n", res.ContentLength, elapsedMs, bandwidth)
104+
// Check if this bandwidth exceeds minimum expected
105+
if minimum*1024 > bandwidth {
106+
log.Fatalf("ERROR: Minimum bandwidth guarantee of %d MiB/sec not met - network connectivity is slow", minimum)
107+
}
108+
// Perform SHA512 hash and compare against the expected hash to check for corruption.
109+
res, err = client.Get(objectHashUrl)
110+
if err != nil {
111+
log.Fatalf("Failure (%s) while reading %s", err, objectHashUrl)
112+
}
113+
content, err := ioutil.ReadAll(res.Body)
114+
res.Body.Close()
115+
if err != nil {
116+
log.Fatal("Failed to read full content of hash file", err)
117+
}
118+
parts := strings.Split(string(content), " ")
119+
if len(parts) <= 1 {
120+
log.Fatalf("Could not parse SHA hash file contents (%s)", content)
121+
}
122+
hash := parts[1]
123+
hash = strings.Trim(hash, "\n ")
124+
sumBytes := sha512.Sum512(blobData)
125+
sumString := hex.EncodeToString(sumBytes[0:])
126+
if strings.Compare(sumString, hash) == 0 {
127+
log.Println("Hash Matches expected value")
128+
} else {
129+
log.Fatalf("ERROR: Hash Mismatch - Computed hash = '%s' Expected hash = '%s'", sumString, hash)
130+
}
131+
}

0 commit comments

Comments
 (0)