74 Commits
1.3.0 ... 2.0.0

Author SHA1 Message Date
Mikolaj Pawlikowski
f99ba84d17 Merge pull request #64 from seeker89/bump-major-version
Add info on how to use the DNS features
2019-09-06 15:10:44 +01:00
Mikolaj Pawlikowski
75315a872a Better wording in the README
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-09-06 14:58:10 +01:00
Mikolaj Pawlikowski
ae67cf3594 Add a note about the DNS addresses being space-delimited
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-09-06 14:57:08 +01:00
Mikolaj Pawlikowski
433a6b8b88 Add a note about the DNS usage
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-09-06 14:51:22 +01:00
Mikolaj Pawlikowski
0660db7922 Bump up major version to 2.0.0
Since this is effectively a backwards incompatible change

Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-09-06 14:42:36 +01:00
Mikolaj Pawlikowski
58285945f6 Merge pull request #61 from cgreen12/master
Optionally test DNS resolution for each pod
2019-09-06 12:23:36 +01:00
Mikolaj Pawlikowski
8a014ad7f4 Merge branch 'master' into master 2019-09-05 13:58:20 +01:00
Mikolaj Pawlikowski
096b149afa Merge pull request #63 from ufou/all_relative_paths
make all paths relative to allow for hosting at a sub path of root
2019-09-05 13:56:58 +01:00
Luke Alexander
5840353d9f bump patch version accordingly
Signed-off-by: Luke Alexander <luke.alexander@mixcloud.com>
2019-09-05 11:39:50 +01:00
Luke Alexander
ac9bf4224d make all paths relative to allow for hosting at a sub path of root
Signed-off-by: Luke Alexander <luke.alexander@mixcloud.com>
2019-09-05 11:10:23 +01:00
Chris Green
bcbc2ac6fc :trollface: re-ran swagger
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-08-21 04:53:01 -04:00
Chris Green
192fc433a2 Added metric for DNS failures
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-07-04 03:06:29 -04:00
Chris Green
dc3864f170 Colour the dns nodes red for any failures
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-09 09:11:49 -04:00
Chris Green
8a759433eb Show the info for dns nodes
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-09 08:53:52 -04:00
Chris Green
1b12b7dc6b Version bump
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-09 08:44:49 -04:00
Chris Green
9adf26e2cb Consistency ftw
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-09 08:43:47 -04:00
Chris Green
927125bbc5 slight re-positioning
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-09 07:37:48 -04:00
Chris Green
7585d010a6 Initial UI changes
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-05 14:56:37 -04:00
Chris Green
5e0ecdd8d1 Updates from swagger change
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-05 14:30:53 -04:00
Chris Green
d834433d30 Moved dns check into client
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-05 14:30:11 -04:00
Chris Green
b64f5152f2 Moved DnsResults into CheckResults
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-05 14:28:40 -04:00
Chris Green
bbac97c17e Refactored swagger.yml for fewer dns requests
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-04 02:54:23 -04:00
Chris Green
74ffcc8d2e Do the dns lookup
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-02 13:18:40 -04:00
Chris Green
9586e16237 Updated models from swagger
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-02 13:18:05 -04:00
Chris Green
6541250aa9 Updated swagger.yml for map of dnsresults
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-02 11:54:20 -04:00
Chris Green
31a503d831 Added env-delim for envvar config
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-02 11:18:25 -04:00
Chris Green
195691518b Added DnsHosts to config
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-01 15:18:26 -04:00
Chris Green
aa66c94d47 Initial thoughts on swagger change
Signed-off-by: Chris Green <34572557+cgreen12@users.noreply.github.com>
2019-06-01 11:41:01 -04:00
Mikolaj Pawlikowski
7b2affea29 Merge pull request #59 from kpfleming/travis-deploy-one-version
Ensure that only Golang 1.10 builds are pushed to DockerHub
2019-03-22 15:27:12 +00:00
Kevin P. Fleming
d1a86eae93 Ensure that only Golang 1.10 builds are pushed to DockerHub
Signed-off-by: Kevin P. Fleming <kpfleming@bloomberg.net>
2019-03-21 16:16:22 -04:00
Mikolaj Pawlikowski
a2e5925210 Merge pull request #58 from dannyk81/healthz_readme
Update readme and example with liveness and readiness probes
2019-03-18 09:56:04 +00:00
Danny Kulchinsky
d243f0fb59 update example
Signed-off-by: Danny Kulchinsky <danny.kul@gmail.com>
Signed-off-by: Danny Kulchinsky <dannyk@tuenti.com>
2019-03-17 21:01:15 -04:00
Danny Kulchinsky
477ba69a72 Add livenessProbe and readinessProbe to README
Signed-off-by: Danny Kulchinsky <danny.kul@gmail.com>
Signed-off-by: Danny Kulchinsky <dannyk@tuenti.com>
2019-03-17 21:01:15 -04:00
Mikolaj Pawlikowski
86064f208e Merge pull request #53 from stuartnelson3/stn/rendezvous-hashing
Add rendezvous hash for selecting subset of nodes
2019-03-14 12:24:33 +00:00
Mikolaj Pawlikowski
d8f8c20927 make the graph work with edges to nodes that are not reporting
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-14 08:13:44 -04:00
Mikolaj Pawlikowski
7c43626b1e Minor version bump to 1.5.0
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-14 11:28:32 +00:00
Mikolaj Pawlikowski
11ec058b3b Add PING_NUMBER envvar support
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-14 11:27:49 +00:00
Mikolaj Pawlikowski
c006eede86 Merge branch 'master' into stn/rendezvous-hashing 2019-03-13 17:11:23 +00:00
Mikolaj Pawlikowski
a585b103f3 Merge pull request #56 from seeker89/docker-push-updates
Updates for the docker images being pushed to docker.io
2019-03-13 17:09:50 +00:00
Mikolaj Pawlikowski
32823fd105 remove the ls and pwd artifacts
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:58:42 +00:00
Mikolaj Pawlikowski
87792cffca turns out vendor folder I tried to copy was in gitignore and Travis won't pick it up
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:52:23 +00:00
Mikolaj Pawlikowski
369e9ece78 add slashes for the folders to copy
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:47:48 +00:00
Mikolaj Pawlikowski
28af41e352 Revert "debugging Travis - see if the ARG makes a difference"
This reverts commit 52ea5546aa.

Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:47:08 +00:00
Mikolaj Pawlikowski
52ea5546aa debugging Travis - see if the ARG makes a difference
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:41:12 +00:00
Mikolaj Pawlikowski
9040b69933 debug the make vendor-build target
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:23:17 +00:00
Mikolaj Pawlikowski
3ce0c46f91 consistenly use COPY over ADD
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 16:15:13 +00:00
Mikolaj Pawlikowski
7b156add72 Revert "fix the path to copy vendor folder from"
This reverts commit 8b1dd49506.

Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 15:53:37 +00:00
Mikolaj Pawlikowski
8b1dd49506 fix the path to copy vendor folder from
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 15:46:22 +00:00
Mikolaj Pawlikowski
bd13d4d673 simplify the vendor building, make it build it with every Travis run
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 14:43:09 +00:00
stuart nelson
895af850a1 Make PodSelecter a member on config struct
Signed-off-by: stuart nelson <stuartnelson3@gmail.com>
2019-03-13 15:30:18 +01:00
stuart nelson
771f303062 Add rendezvous hash for selecting subset of nodes
Select a user-defined number of pods via
rendezvous hash. This is important for larger
clusters, where the metric cardinality explosion
is too much for a single prometheus to handle.

Signed-off-by: stuart nelson <stuartnelson3@gmail.com>
2019-03-13 15:30:18 +01:00
Mikolaj Pawlikowski
b7c1d2dfb4 add extra info in README, about the -vendor image tag
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 14:29:54 +00:00
Mikolaj Pawlikowski
86ddcf9505 push an additional image with -vendor postfix with all the sources
For licensing reasons.

Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 14:26:28 +00:00
Mikolaj Pawlikowski
bf43dcff98 remove the vendor from the standard build
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 14:13:17 +00:00
Mikolaj Pawlikowski
6de72deee0 When building multi-stage, copy over the vendor folder for licensing reasons
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-13 13:59:43 +00:00
Mikolaj Pawlikowski
6cd12ef2d5 Update example-with-kubeconfig.yaml
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-12 23:07:54 +00:00
Mikolaj Pawlikowski
8182369c02 Update example-serviceaccounts.yml
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-12 23:07:29 +00:00
Mikolaj Pawlikowski
82a0d6ae8c Merge branch 'master' into docker-push-updates 2019-03-12 23:05:32 +00:00
Mikolaj Pawlikowski
057e360c5b Update README.md
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-03-12 22:59:32 +00:00
Mikolaj Pawlikowski
4cf5dea621 Merge pull request #55 from kpfleming/travis-to-dockerhub
Enable push of images to DockerHub
2019-03-12 22:35:50 +00:00
Kevin P. Fleming
16a1d52741 Deployment improvements
* Only deploy from tags
* Only attempt deployment if DOCKER_PASSWORD has been set in environment
* Don't cleanup working directory before deployment

Signed-off-by: Kevin P. Fleming <kpfleming@bloomberg.net>
2019-03-12 09:15:22 -04:00
Kevin P. Fleming
0472791dae Use a 'deploy' stage and script
Pushing to DockerHub from the 'after_success' phase
was unreliable (triggered by PRs) so this patch changes
the process to use a 'deploy' stage script instead.

Signed-off-by: Kevin P. Fleming <kpfleming@bloomberg.net>
2019-03-11 16:28:16 -04:00
Kevin P. Fleming
84a43a3d28 Enable push of images to DockerHub
This patch will enable pushes of any successful builds (using Travis-CI)
of the 'master' branch to the proper repository on DockerHub. The credentials
necessary are provided via environment variables configured in the
Travis-CI settings.

Addresses issue #27

Signed-off-by: Kevin P. Fleming <kpfleming@bloomberg.net>
2019-03-11 15:29:58 -04:00
Mikolaj Pawlikowski
3f70a24804 Merge pull request #51 from seeker89/heatmap
Add heatmap
2019-03-05 17:48:00 +00:00
Mikolaj Pawlikowski
593307dc01 change the block size to 14 pixels (enough for two digits, so 100 machines cluster)
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-22 17:32:22 +00:00
Mikolaj Pawlikowski
950e2e4ab7 dep ensure
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 18:36:25 +00:00
Mikolaj Pawlikowski
d8f0d696ea add a basic legend to the heatmap image
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 18:34:30 +00:00
Mikolaj Pawlikowski
6deb5c3044 fix static folder when using Dockerfile-simple
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 17:43:54 +00:00
Mikolaj Pawlikowski
767d2dba7f quick test of how to integrate with the UI
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 17:41:19 +00:00
Mikolaj Pawlikowski
2efee0f5e5 read the tresholds from the query params
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 17:40:06 +00:00
Mikolaj Pawlikowski
3a729bf196 minor version bump
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 16:14:48 +00:00
Mikolaj Pawlikowski
fd84599157 remove debug print statements
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 16:13:20 +00:00
Mikolaj Pawlikowski
f5c2763000 add an endpoint for generating a /heatmap.png
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 13:51:20 +00:00
Mikolaj Pawlikowski
22b96d001d add a skeleton for creating a heatmap png
Signed-off-by: Mikolaj Pawlikowski <mikolaj@pawlikowski.pl>
2019-02-21 13:50:59 +00:00
34 changed files with 903 additions and 100 deletions

View File

@@ -1,2 +1 @@
.git/
/vendor/

View File

@@ -24,3 +24,17 @@ script:
- make clean && make build-multistage
- docker images
- docker run `make version` --help
# build an image with the vendor folder
- make clean && make vendor && make vendor-build
- docker images
- docker run `make version`-vendor --help
deploy:
provider: script
script: bash docker_deploy.sh
skip_cleanup: true
on:
tags: true
go: "1.10.x"
condition: -n "$DOCKER_PASSWORD"

View File

@@ -21,5 +21,4 @@ RUN make bin/goldpinger
FROM scratch
COPY --from=builder /go/src/github.com/bloomberg/goldpinger/bin/goldpinger /goldpinger
COPY ./static /static
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]

35
Gopkg.lock generated
View File

@@ -33,6 +33,14 @@
pruneopts = "UT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
digest = "1:998cf998358a303ac2430c386ba3fd3398477d6013153d3c6e11432765cc9ae6"
name = "github.com/cespare/xxhash"
packages = ["."]
pruneopts = "UT"
revision = "3b82fb7d186719faeedd0c2864f868c74fbf79a1"
version = "v2.0.0"
[[projects]]
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
name = "github.com/docker/go-units"
@@ -355,6 +363,14 @@
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:2731ee5852c26a3585f70d1a6cff8422383b98bad405eb6611cd4a2334be1df6"
name = "github.com/stuartnelson3/go-rendezvous"
packages = ["."]
pruneopts = "UT"
revision = "16dc0292e5f420d89a7dc4319775a1e5e461dc01"
version = "v0.2.0"
[[projects]]
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
name = "golang.org/x/crypto"
@@ -362,6 +378,19 @@
pruneopts = "UT"
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
[[projects]]
branch = "master"
digest = "1:09c175055d303dadf3f82513c66e6da0b95ba22bc8e5d267a2674d16d95ea77a"
name = "golang.org/x/image"
packages = [
"font",
"font/basicfont",
"font/plan9font",
"math/fixed",
]
pruneopts = "UT"
revision = "31aff87c08e9a5e5d524279a564f96968336f886"
[[projects]]
branch = "master"
digest = "1:eb8583d4582ffbc5c6d3ec00132cd0835101edde42b6f24eaafa628d1596f881"
@@ -612,6 +641,7 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/cespare/xxhash",
"github.com/go-openapi/errors",
"github.com/go-openapi/loads",
"github.com/go-openapi/runtime",
@@ -626,7 +656,10 @@
"github.com/jessevdk/go-flags",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"golang.org/x/net/context",
"github.com/stuartnelson3/go-rendezvous",
"golang.org/x/image/font",
"golang.org/x/image/font/basicfont",
"golang.org/x/image/math/fixed",
"golang.org/x/net/netutil",
"k8s.io/apimachinery/pkg/apis/meta/v1",
"k8s.io/client-go/kubernetes",

View File

@@ -73,6 +73,10 @@
name = "k8s.io/client-go"
version = "9.0.0"
[[constraint]]
name = "github.com/stuartnelson3/go-rendezvous"
version = "0.2.0"
[prune]
go-tests = true
unused-packages = true

View File

@@ -1,10 +1,10 @@
name ?= goldpinger
version ?= 1.3.0
version ?= 2.0.0
bin ?= goldpinger
pkg ?= "github.com/bloomberg/goldpinger"
tag = $(name):$(version)
goos ?= ${GOOS}
namespace ?= ""
namespace ?= "bloomberg/"
files = $(shell find . -iname "*.go")
@@ -38,8 +38,19 @@ push:
run:
go run ./cmd/goldpinger/main.go
version:
@echo $(tag)
.PHONY: clean vendor swagger build build-multistage tag push run version
vendor-build:
docker build -t $(tag)-vendor --build-arg TAG=$(tag) -f ./build/Dockerfile-vendor .
vendor-tag:
docker tag $(tag)-vendor $(namespace)$(tag)-vendor
vendor-push:
docker push $(namespace)$(tag)-vendor
.PHONY: clean vendor swagger build build-multistage vendor-build vendor-tag vendor-push tag push run version

View File

@@ -18,6 +18,7 @@ Oh, and it gives you the graph below for your cluster. Check out the [video expl
- [Installation](#installation)
- [Authentication with Kubernetes API](#authentication-with-kubernetes-api)
- [Example YAML](#example-yaml)
- [Note on DNS](#note-on-dns)
- [Usage](#usage)
- [UI](#ui)
- [API](#api)
@@ -38,11 +39,20 @@ If you'd like to know more, you can watch [our presentation at Kubecon 2018 Seat
## Quick start
Getting from sources:
```sh
go get github.com/bloomberg/goldpinger/cmd/goldpinger
goldpinger --help
```
Getting from [docker hub](https://hub.docker.com/r/bloomberg/goldpinger):
```sh
# get from docker hub
docker pull bloomberg/goldpinger
```
Note, that in order to guarantee correct versions of dependencies, the project [uses `dep`](./Makefile).
@@ -73,7 +83,7 @@ Building from source code consists of compiling the binary and building a [Docke
```sh
# step 0: check out the code into your $GOPATH
go get github.com/bloomberg/goldpinger/cmd/goldpinger
go get github.com/bloomberg/goldpinger/cmd/goldpinger
cd $GOPATH/src/github.com/bloomberg/goldpinger
# step 1: download the dependencies via dep ensure
@@ -103,9 +113,7 @@ namespace="docker.io/myhandle/" make push
### Example YAML
Here's an example of what you can do (using the in-cluster authentication to `Kubernetes` apiserver).
:warning: Replace `docker.io/mynamespace-replaceme/goldpinger:1.0.0` with the actual tag you built.
Here's an example of what you can do (using the in-cluster authentication to `Kubernetes` apiserver).
```yaml
---
@@ -119,12 +127,12 @@ spec:
selector:
matchLabels:
app: goldpinger
version: "1.0.0"
version: "1.5.0"
template:
metadata:
labels:
app: goldpinger
version: "1.0.0"
version: "1.5.0"
spec:
containers:
- name: goldpinger
@@ -138,10 +146,27 @@ spec:
valueFrom:
fieldRef:
fieldPath: spec.nodeName
image: "docker.io/mynamespace-replaceme/goldpinger:1.0.0"
# podIP is used to select a randomized subset of nodes to ping.
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
image: "docker.io/bloomberg/goldpinger:1.5.0"
ports:
- containerPort: 80
name: http
readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 20
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 20
periodSeconds: 5
---
apiVersion: v1
kind: Service
@@ -179,6 +204,28 @@ subjects:
You can also see [an example of using `kubeconfig` in the `./extras`](./extras/example-with-kubeconfig.yaml).
### Note on DNS
Note, that on top of resolving the other pods, all instances can also try to resolve arbitrary DNS. This allows you to test your DNS setup.
From `--help`:
```sh
--host-to-resolve= A host to attempt dns resolve on (space delimited) [$HOSTS_TO_RESOLVE]
```
So in order to test two domains, we could add an extra env var to the example above:
```yaml
- name: HOSTS_TO_RESOLVE
value: "www.bloomberg.com one.two.three"
```
and `goldpinger` should show something like this:
![screenshot-DNS-resolution](./extras/dns-screenshot.png)
## Usage
### UI
@@ -191,7 +238,7 @@ You can click on various nodes to gray out the clutter and see more information.
### API
The API exposed is via a well-defined [`Swagger` spec](./swagger.yml).
The API exposed is via a well-defined [`Swagger` spec](./swagger.yml).
The spec is used to generate both the server and the client of `Goldpinger`. If you make changes, you can re-generate them using [go-swagger](https://github.com/go-swagger/go-swagger) via [`make swagger`](./Makefile)
@@ -247,3 +294,5 @@ Before you create that PR, please make sure you read [CONTRIBUTING](./CONTRIBUTI
## License
Please read the [LICENSE](./LICENSE) file here.
For each version built by travis, there is also an additional version, appended with `-vendor`, which contains all source code of the dependencies used in `goldpinger`.

View File

@@ -1,6 +1,6 @@
FROM scratch
ADD bin/goldpinger /goldpinger
COPY static/index.html /static/index.html
COPY bin/goldpinger /goldpinger
COPY static /static
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]

3
build/Dockerfile-vendor Normal file
View File

@@ -0,0 +1,3 @@
ARG TAG
FROM $TAG
COPY vendor /goldpinger-vendor-sources

View File

@@ -96,6 +96,14 @@ func main() {
goldpinger.GoldpingerConfig.Port = server.Port
}
if goldpinger.GoldpingerConfig.PodIP == "" {
log.Println("PodIP not set: pinging all pods")
}
if goldpinger.GoldpingerConfig.PingNumber == 0 {
log.Println("--ping-number set to 0: pinging all pods")
}
goldpinger.GoldpingerConfig.PodSelecter = goldpinger.NewPodSelecter(goldpinger.GoldpingerConfig.PingNumber, goldpinger.GoldpingerConfig.PodIP, goldpinger.GetAllPods)
server.ConfigureAPI()
goldpinger.StartUpdater()

8
docker_deploy.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD" \
&& make tag \
&& make push \
&& make vendor-tag \
&& make vendor-push

BIN
extras/dns-screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

View File

@@ -1,4 +1,3 @@
---
apiVersion: v1
kind: ServiceAccount
@@ -38,7 +37,12 @@ spec:
valueFrom:
fieldRef:
fieldPath: spec.nodeName
image: "docker.io/mynamespace-replaceme/goldpinger:1.1.0"
# podIP is used to select a randomized subset of nodes to ping.
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
image: "docker.io/bloomberg/goldpinger:1.4.0"
ports:
- containerPort: 80
name: http

View File

@@ -5,19 +5,19 @@ metadata:
name: goldpinger
labels:
app: goldpinger
version: "1.1.0"
version: "1.5.0"
spec:
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: goldpinger
version: "1.1.0"
version: "1.5.0"
template:
metadata:
labels:
app: goldpinger
version: "1.1.0"
version: "1.5.0"
spec:
# if you'd like to use a secret to inject a kubeconfig, you can do it like this
volumes:
@@ -43,10 +43,27 @@ spec:
valueFrom:
fieldRef:
fieldPath: spec.nodeName
image: "docker.io/mynamespace-replaceme/goldpinger:1.1.0"
# podIP is used to select randomized subset of nodes to ping.
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
image: "docker.io/bloomberg/goldpinger:1.5.0"
ports:
- containerPort: 80
name: http
readinessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 20
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 20
periodSeconds: 5
volumeMounts:
- mountPath: /.kube/
name: kubeconfig

View File

@@ -47,7 +47,7 @@ func NewCheckServicePodsOK() *CheckServicePodsOK {
Success, return response
*/
type CheckServicePodsOK struct {
Payload models.CheckResults
Payload *models.CheckResults
}
func (o *CheckServicePodsOK) Error() string {
@@ -56,8 +56,10 @@ func (o *CheckServicePodsOK) Error() string {
func (o *CheckServicePodsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
o.Payload = new(models.CheckResults)
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
return err
}

View File

@@ -16,6 +16,7 @@ package goldpinger
import (
"fmt"
"net"
"sync"
"time"
@@ -27,14 +28,14 @@ import (
// CheckNeighbours queries the kubernetes API server for all other goldpinger pods
// then calls Ping() on each one
func CheckNeighbours() models.CheckResults {
return PingAllPods(GetAllPods())
func CheckNeighbours(ps *PodSelecter) *models.CheckResults {
return PingAllPods(ps.SelectPods())
}
// CheckNeighboursNeighbours queries the kubernetes API server for all other goldpinger
// pods then calls Check() on each one
func CheckNeighboursNeighbours() *models.CheckAllResults {
return CheckAllPods(GetAllPods())
func CheckNeighboursNeighbours(ps *PodSelecter) *models.CheckAllResults {
return CheckAllPods(ps.SelectPods())
}
type PingAllPodsResult struct {
@@ -50,7 +51,25 @@ func pickPodHostIP(podIP, hostIP string) string {
return podIP
}
func PingAllPods(pods map[string]string) models.CheckResults {
func checkDNS() *models.DNSResults {
results := models.DNSResults{}
for _, host := range GoldpingerConfig.DnsHosts{
var dnsResult models.DNSResult
start := time.Now()
_, err := net.LookupIP(host)
if err != nil {
dnsResult.Error = err.Error()
CountDnsError(host)
}
dnsResult.ResponseTimeMs = time.Since(start).Nanoseconds() / int64(time.Millisecond)
results[host] = dnsResult
}
return &results
}
func PingAllPods(pods map[string]string) *models.CheckResults {
result := models.CheckResults{}
@@ -84,11 +103,15 @@ func PingAllPods(pods map[string]string) models.CheckResults {
wg.Done()
}(podIP, hostIP)
}
if len(GoldpingerConfig.DnsHosts) > 0 {
result.DNSResults = *checkDNS()
}
wg.Wait()
close(ch)
counterHealthy, counterUnhealthy := 0.0, 0.0
result.PodResults = make(map[string]models.PodResult)
for response := range ch {
var podIPv4 strfmt.IPv4
podIPv4.UnmarshalText([]byte(response.podIP))
@@ -97,10 +120,10 @@ func PingAllPods(pods map[string]string) models.CheckResults {
} else {
counterUnhealthy++
}
result[response.podIP] = response.podResult
result.PodResults[response.podIP] = response.podResult
}
CountHealthyUnhealthyNodes(counterHealthy, counterUnhealthy)
return result
return &result
}
type CheckServicePodsResult struct {
@@ -161,6 +184,18 @@ func CheckAllPods(pods map[string]string) *models.CheckAllResults {
HostIP: response.hostIPv4,
PodIP: podIPv4,
})
if response.checkAllPodResult.Response != nil &&
response.checkAllPodResult.Response.DNSResults != nil {
if result.DNSResults == nil {
result.DNSResults = make(map[string]models.DNSResults)
}
for host := range response.checkAllPodResult.Response.DNSResults {
if result.DNSResults[host] == nil {
result.DNSResults[host] = make(map[string]models.DNSResult)
}
result.DNSResults[host][response.podIP] = response.checkAllPodResult.Response.DNSResults[host]
}
}
}
return &result
}

View File

@@ -24,8 +24,13 @@ var GoldpingerConfig = struct {
KubeConfigPath string `long:"kubeconfig" description:"Path to kubeconfig file" env:"KUBECONFIG"`
RefreshInterval int `long:"refresh-interval" description:"If > 0, will create a thread and collect stats every n seconds" env:"REFRESH_INTERVAL" default:"30"`
Hostname string `long:"hostname" description:"Hostname to use" env:"HOSTNAME"`
PodIP string `long:"pod-ip" description:"Pod IP to use" env:"POD_IP"`
PingNumber uint `long:"ping-number" description:"Number of peers to ping. A value of 0 indicates all peers should be pinged." default:"0" env:"PING_NUMBER"`
Port int `long:"client-port-override" description:"(for testing) use this port when calling other instances" env:"CLIENT_PORT_OVERRIDE"`
UseHostIP bool `long:"use-host-ip" description:"When making the calls, use host ip (defaults to pod ip)" env:"USE_HOST_IP"`
LabelSelector string `long:"label-selector" description:"label selector to use to discover goldpinger pods in the cluster" env:"LABEL_SELECTOR" default:"app=goldpinger"`
KubernetesClient *kubernetes.Clientset
*PodSelecter
DnsHosts []string `long:"host-to-resolve" description:"A host to attempt dns resolve on (space delimited)" env:"HOSTS_TO_RESOLVE" env-delim:" "`
}{}

146
pkg/goldpinger/heatmap.go Normal file
View File

@@ -0,0 +1,146 @@
// 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.
// This file is safe to edit. Once it exists it will not be overwritten
package goldpinger
import (
"bytes"
"fmt"
"image"
"image/color"
"image/png"
"log"
"net/http"
"sort"
"strconv"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
)
func addLabel(img *image.RGBA, x, y int, text string) {
drawer := &font.Drawer{
Dst: img,
Src: image.NewUniform(color.RGBA{25, 200, 25, 255}),
Face: basicfont.Face7x13,
Dot: fixed.Point26_6{fixed.Int26_6(x * 64), fixed.Int26_6(y * 64)},
}
drawer.DrawString(text)
}
// Calculates the color of the box to draw based on the latency and tresholds
// We are aiming at slightly more palatable colors than just moving from 255 green to 255 red,
// so we will use 25B, and then move from (25R, 200G) to (200R, 25G), so our scale is effectively 350 points
func getPingBoxColor(latency int64, tresholdLatencies [3]int64) *color.RGBA {
var red, green uint8 = 25, 200
if latency > tresholdLatencies[2] {
red, green = 200, 25
} else if latency >= tresholdLatencies[1] {
red, green = 200, 200
diff := (float32(latency-tresholdLatencies[1]) / float32(tresholdLatencies[2]-tresholdLatencies[1])) * 175
green = green - uint8(diff)
} else if latency >= tresholdLatencies[0] {
red, green = 25, 200
diff := (float32(latency-tresholdLatencies[0]) / float32(tresholdLatencies[1]-tresholdLatencies[0])) * 175
red = red + uint8(diff)
}
return &color.RGBA{red, green, 25, 255}
}
func drawPingBox(img *image.RGBA, _x, _y, size int, color *color.RGBA) {
for x := _x; x < _x+size; x++ {
for y := _y; y < _y+size; y++ {
img.Set(x, y, *color)
}
}
}
func getPingBoxCoordinates(col, row, boxSize, padding int) (int, int) {
return col * (boxSize + padding), row * (boxSize + padding)
}
// HeatmapHandler returns a PNG with a heatmap representation
func HeatmapHandler(w http.ResponseWriter, r *http.Request) {
// parse the query to set the parameters
query := r.URL.Query()
// get the results
checkResults := CheckAllPods(GetAllPods())
// set some sizes
numberOfPods := len(checkResults.Responses)
legendSize := 200
boxSize := 14
paddingSize := 1
heatmapSize := numberOfPods*(boxSize+paddingSize) + boxSize*2
tresholdLatencies := [3]int64{1, 10, 100}
for index := range tresholdLatencies {
stringValue := query["t"+fmt.Sprintf("%d", index)]
if len(stringValue) == 0 {
continue
}
if v, err := strconv.ParseInt(stringValue[0], 0, 64); err == nil && v >= 0 {
tresholdLatencies[index] = v
}
}
canvas := image.NewRGBA(image.Rect(0, 0, heatmapSize+legendSize, heatmapSize))
// establish an order and fix the max delay
var keys []string
for sourceIP := range checkResults.Responses {
keys = append(keys, sourceIP)
}
sort.Strings(keys)
order := make(map[string]int)
for index, key := range keys {
order[key] = index
}
// draw all the boxes
for sourceIP, results := range checkResults.Responses {
if *results.OK {
for destinationIP, response := range results.Response.PodResults {
x, y := getPingBoxCoordinates(order[sourceIP], order[destinationIP], boxSize, paddingSize)
color := getPingBoxColor(response.ResponseTimeMs, tresholdLatencies)
drawPingBox(canvas, boxSize+x, boxSize+y, boxSize, color)
}
}
}
// draw the legend
for index, ip := range keys {
// ip
addLabel(canvas, heatmapSize, (index+1)*(boxSize+paddingSize)+13, fmt.Sprintf("%d", index)+": "+ip)
// rows
addLabel(canvas, 0, (index+1)*(boxSize+paddingSize)+13, fmt.Sprintf("%d", index))
// columns
addLabel(canvas, (index+1)*(boxSize+paddingSize), 13, fmt.Sprintf("%d", index))
}
buffer := new(bytes.Buffer)
if err := png.Encode(buffer, canvas); err != nil {
log.Println("error encoding png", err)
}
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))
if _, err := w.Write(buffer.Bytes()); err != nil {
log.Println("error writing heatmap buffer out", err)
}
}

View File

@@ -0,0 +1,61 @@
// 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 (
"github.com/cespare/xxhash"
rendezvous "github.com/stuartnelson3/go-rendezvous"
)
// PodSelecter selects the result of getPods() down to count instances
// according to a rendezvous hash.
type PodSelecter struct {
count uint
podIP string
getPods func() map[string]string
}
// NewPodSelecter creates a new PodSelecter struct.
func NewPodSelecter(count uint, podIP string, getPods func() map[string]string) *PodSelecter {
if podIP == "" {
// If podIP is blank, then we can't use the rendezvous hash to
// assign the IP correctly. Setting count=0 will force all pods
// to be pinged.
count = 0
}
return &PodSelecter{
count: count,
podIP: podIP,
getPods: getPods,
}
}
// SelectPods returns a map of pods filtered according to its configuration.
func (p *PodSelecter) SelectPods() map[string]string {
allPods := p.getPods()
if p.count == 0 || p.count >= uint(len(allPods)) {
return allPods
}
rzv := rendezvous.New([]string{}, rendezvous.Hasher(xxhash.Sum64String))
for podIP := range allPods {
rzv.Add(podIP)
}
matches := rzv.LookupN(p.podIP, p.count)
toPing := make(map[string]string)
for _, podIP := range matches {
toPing[podIP] = allPods[podIP]
}
return toPing
}

View File

@@ -82,6 +82,16 @@ var (
"type",
},
)
goldpingerDnsErrorsCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "goldpinger_dns_errors_total",
Help: "Statistics of DNS errors per instance",
},
[]string{
"goldpinger_instance",
"host",
},
)
bootTime = time.Now()
)
@@ -92,6 +102,7 @@ func init() {
prometheus.MustRegister(goldpingerResponseTimePeersHistogram)
prometheus.MustRegister(goldpingerResponseTimeKubernetesHistogram)
prometheus.MustRegister(goldpingerErrorsCounter)
prometheus.MustRegister(goldpingerDnsErrorsCounter)
log.Println("Metrics setup - see /metrics")
}
@@ -131,6 +142,14 @@ func CountError(errorType string) {
).Inc()
}
// counts instances of dns errors
func CountDnsError(host string) {
goldpingerDnsErrorsCounter.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(

View File

@@ -21,17 +21,17 @@ import (
)
func StartUpdater() {
if GoldpingerConfig.RefreshInterval <= 0 {
log.Println("Not creating updater, period is 0")
return
}
// start the updater
go func() {
for {
results := PingAllPods(GetAllPods())
results := PingAllPods(GoldpingerConfig.PodSelecter.SelectPods())
var troublemakers []string
for podIP, value := range results {
for podIP, value := range results.PodResults {
if *value.OK != true {
troublemakers = append(troublemakers, fmt.Sprintf("%s (%s)", podIP, value.HostIP.String()))
}

View File

@@ -28,7 +28,7 @@ type CheckAllPodResult struct {
Error string `json:"error,omitempty"`
// response
Response CheckResults `json:"response,omitempty"`
Response *CheckResults `json:"response,omitempty"`
// status code
StatusCode int32 `json:"status-code,omitempty"`
@@ -71,11 +71,13 @@ func (m *CheckAllPodResult) validateResponse(formats strfmt.Registry) error {
return nil
}
if err := m.Response.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("response")
if m.Response != nil {
if err := m.Response.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("response")
}
return err
}
return err
}
return nil

View File

@@ -22,6 +22,9 @@ type CheckAllResults struct {
// o k
OK *bool `json:"OK,omitempty"`
// dns results
DNSResults map[string]DNSResults `json:"dnsResults,omitempty"`
// hosts
Hosts []*CheckAllResultsHostsItems0 `json:"hosts"`
@@ -39,6 +42,10 @@ type CheckAllResults struct {
func (m *CheckAllResults) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateDNSResults(formats); err != nil {
res = append(res, err)
}
if err := m.validateHosts(formats); err != nil {
res = append(res, err)
}
@@ -53,6 +60,25 @@ 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
return nil
}
for k := range m.DNSResults {
if val, ok := m.DNSResults[k]; ok {
if err := val.Validate(formats); err != nil {
return err
}
}
}
return nil
}
func (m *CheckAllResults) validateHosts(formats strfmt.Registry) error {
if swag.IsZero(m.Hosts) { // not required

View File

@@ -9,28 +9,31 @@ import (
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// CheckResults check results
// swagger:model CheckResults
type CheckResults map[string]PodResult
type CheckResults struct {
// dns results
DNSResults DNSResults `json:"dnsResults,omitempty"`
// pod results
PodResults map[string]PodResult `json:"podResults,omitempty"`
}
// Validate validates this check results
func (m CheckResults) Validate(formats strfmt.Registry) error {
func (m *CheckResults) Validate(formats strfmt.Registry) error {
var res []error
for k := range m {
if err := validate.Required(k, "body", m[k]); err != nil {
return err
}
if val, ok := m[k]; ok {
if err := val.Validate(formats); err != nil {
return err
}
}
if err := m.validateDNSResults(formats); err != nil {
res = append(res, err)
}
if err := m.validatePodResults(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
@@ -38,3 +41,59 @@ 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 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
}
for k := range m.PodResults {
if err := validate.Required("podResults"+"."+k, "body", m.PodResults[k]); err != nil {
return err
}
if val, ok := m.PodResults[k]; ok {
if err := val.Validate(formats); err != nil {
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *CheckResults) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *CheckResults) UnmarshalBinary(b []byte) error {
var res CheckResults
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

46
pkg/models/dns_result.go Normal file
View File

@@ -0,0 +1,46 @@
// 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 (
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// DNSResult Dns result
// swagger:model DnsResult
type DNSResult struct {
// error
Error string `json:"error,omitempty"`
// response time ms
ResponseTimeMs int64 `json:"response-time-ms,omitempty"`
}
// Validate validates this Dns result
func (m *DNSResult) Validate(formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *DNSResult) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *DNSResult) UnmarshalBinary(b []byte) error {
var res DNSResult
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

40
pkg/models/dns_results.go Normal file
View File

@@ -0,0 +1,40 @@
// 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 (
strfmt "github.com/go-openapi/strfmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/validate"
)
// DNSResults Dns results
// swagger:model DnsResults
type DNSResults map[string]DNSResult
// Validate validates this Dns results
func (m DNSResults) Validate(formats strfmt.Registry) error {
var res []error
for k := range m {
if err := validate.Required(k, "body", m[k]); err != nil {
return err
}
if val, ok := m[k]; ok {
if err := val.Validate(formats); err != nil {
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View File

@@ -47,6 +47,7 @@ func configureFlags(api *operations.GoldpingerAPI) {
func configureAPI(api *operations.GoldpingerAPI) http.Handler {
// configure the api here
ps := goldpinger.GoldpingerConfig.PodSelecter
api.ServeError = errors.ServeError
api.JSONConsumer = runtime.JSONConsumer()
@@ -61,13 +62,13 @@ func configureAPI(api *operations.GoldpingerAPI) http.Handler {
api.CheckServicePodsHandler = operations.CheckServicePodsHandlerFunc(
func(params operations.CheckServicePodsParams) middleware.Responder {
goldpinger.CountCall("received", "check")
return operations.NewCheckServicePodsOK().WithPayload(goldpinger.CheckNeighbours())
return operations.NewCheckServicePodsOK().WithPayload(goldpinger.CheckNeighbours(ps))
})
api.CheckAllPodsHandler = operations.CheckAllPodsHandlerFunc(
func(params operations.CheckAllPodsParams) middleware.Responder {
goldpinger.CountCall("received", "check_all")
return operations.NewCheckAllPodsOK().WithPayload(goldpinger.CheckNeighboursNeighbours())
return operations.NewCheckAllPodsOK().WithPayload(goldpinger.CheckNeighboursNeighbours(ps))
})
api.HealthzHandler = operations.HealthzHandlerFunc(
@@ -110,6 +111,8 @@ func fileServerMiddleware(next http.Handler) http.Handler {
fileServer := http.FileServer(http.Dir(goldpinger.GoldpingerConfig.StaticFilePath))
if r.URL.Path == "/" {
http.StripPrefix("/", fileServer).ServeHTTP(w, r)
} else if r.URL.Path == "/heatmap.png" {
goldpinger.HeatmapHandler(w, r)
} else if strings.HasPrefix(r.URL.Path, "/static/") {
http.StripPrefix("/static/", fileServer).ServeHTTP(w, r)
} else {

View File

@@ -7,7 +7,7 @@ Package restapi Goldpinger
http
Host: localhost
BasePath: /
Version: 1.0.0
Version: 2.0.0
Consumes:
- application/json

View File

@@ -21,7 +21,7 @@ func init() {
"swagger": "2.0",
"info": {
"title": "Goldpinger",
"version": "1.0.0"
"version": "2.0.0"
},
"paths": {
"/check": {
@@ -143,6 +143,12 @@ func init() {
"type": "boolean",
"default": false
},
"dnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DnsResults"
}
},
"hosts": {
"type": "array",
"items": {
@@ -176,9 +182,34 @@ func init() {
}
},
"CheckResults": {
"type": "object",
"properties": {
"dnsResults": {
"$ref": "#/definitions/DnsResults"
},
"podResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
}
}
}
},
"DnsResult": {
"properties": {
"error": {
"type": "string"
},
"response-time-ms": {
"type": "number",
"format": "int64"
}
}
},
"DnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
"$ref": "#/definitions/DnsResult"
}
},
"HealthCheckResults": {
@@ -244,7 +275,7 @@ func init() {
"swagger": "2.0",
"info": {
"title": "Goldpinger",
"version": "1.0.0"
"version": "2.0.0"
},
"paths": {
"/check": {
@@ -366,6 +397,12 @@ func init() {
"type": "boolean",
"default": false
},
"dnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DnsResults"
}
},
"hosts": {
"type": "array",
"items": {
@@ -399,9 +436,34 @@ func init() {
}
},
"CheckResults": {
"type": "object",
"properties": {
"dnsResults": {
"$ref": "#/definitions/DnsResults"
},
"podResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
}
}
}
},
"DnsResult": {
"properties": {
"error": {
"type": "string"
},
"response-time-ms": {
"type": "number",
"format": "int64"
}
}
},
"DnsResults": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PodResult"
"$ref": "#/definitions/DnsResult"
}
},
"HealthCheckResults": {

View File

@@ -25,7 +25,7 @@ type CheckServicePodsOK struct {
/*
In: Body
*/
Payload models.CheckResults `json:"body,omitempty"`
Payload *models.CheckResults `json:"body,omitempty"`
}
// NewCheckServicePodsOK creates CheckServicePodsOK with default headers values
@@ -35,13 +35,13 @@ func NewCheckServicePodsOK() *CheckServicePodsOK {
}
// WithPayload adds the payload to the check service pods o k response
func (o *CheckServicePodsOK) WithPayload(payload models.CheckResults) *CheckServicePodsOK {
func (o *CheckServicePodsOK) WithPayload(payload *models.CheckResults) *CheckServicePodsOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the check service pods o k response
func (o *CheckServicePodsOK) SetPayload(payload models.CheckResults) {
func (o *CheckServicePodsOK) SetPayload(payload *models.CheckResults) {
o.Payload = payload
}
@@ -49,13 +49,10 @@ func (o *CheckServicePodsOK) SetPayload(payload models.CheckResults) {
func (o *CheckServicePodsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if payload == nil {
// return empty map
payload = models.CheckResults{}
}
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -65,13 +65,13 @@ type GoldpingerAPI struct {
Middleware func(middleware.Builder) http.Handler
// BasicAuthenticator generates a runtime.Authenticator from the supplied basic auth function.
// It has a default implemention in the security package, however you can replace it for your particular usage.
// It has a default implementation in the security package, however you can replace it for your particular usage.
BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator
// APIKeyAuthenticator generates a runtime.Authenticator from the supplied token auth function.
// It has a default implemention in the security package, however you can replace it for your particular usage.
// It has a default implementation in the security package, however you can replace it for your particular usage.
APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator
// BearerAuthenticator generates a runtime.Authenticator from the supplied bearer token auth function.
// It has a default implemention in the security package, however you can replace it for your particular usage.
// It has a default implementation in the security package, however you can replace it for your particular usage.
BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator
// JSONConsumer registers a consumer for a "application/json" mime type

View File

@@ -87,7 +87,7 @@ type Server struct {
TLSHost string `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
TLSPort int `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
TLSCertificate flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure conections" env:"TLS_PRIVATE_KEY"`
TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
TLSCACertificate flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
TLSListenLimit int `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
TLSKeepAlive time.Duration `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`

View File

@@ -49,7 +49,7 @@ limitations under the License.
<script type="text/javascript" src="static/sigma.js/1.1.0/plugins/sigma.renderers.parallelEdges.min.js"></script>
<script type="text/javascript" src="static/sigma.js/1.1.0/plugins/sigma.renderers.snapshot.min.js"></script>
<script type="text/javascript" src="static/sigma.js/1.1.0/plugins/sigma.statistics.HITS.min.js"></script>
<!-- Bootstrap -->
<link rel="stylesheet" href="static/bootstrap/3.3.7/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="static/bootstrap/3.3.7/css/bootstrap-theme.min.css" crossorigin="anonymous">
@@ -86,6 +86,7 @@ limitations under the License.
<ul class="nav navbar-nav">
<li class="active"><a href="#">Graph</a></li>
<li><a href="#" id="show-data">Data</a></li>
<li><a href="#" id="show-heatmap">Heatmap</a></li>
<li><a href="check_all" >Raw</a></li>
<li><a href="metrics" >Metrics</a></li>
</ul>
@@ -106,24 +107,70 @@ limitations under the License.
</div>
<div id="modal-window-code" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-title">title</h4>
</div>
<div class="modal-body" id="modal-body">
body
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
<div id="modal-window-code" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-title">title</h4>
</div>
<div class="modal-body" id="modal-body">
body
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="modal-window-heatmap" class="modal fade bs-example-modal-lg" tabindex="-2" role="dialog" aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="modal-title">Heatmap</h4>
</div>
<div class="modal-body">
<div id="heatmap-body" style="padding: 10px"></div>
<div class="input-group">
<span class="input-group-addon" style="width: 200px">Good treshold</span>
<input id="t0" type="number" step="1" class="form-control" placeholder="2" value="2">
<span class="input-group-addon">milliseconds</span>
</div>
<div class="input-group">
<span class="input-group-addon" style="width: 200px">Warning treshold</span>
<input id="t1" type="number" step="1" class="form-control" placeholder="5" value="5">
<span class="input-group-addon">milliseconds</span>
</div>
<div class="input-group">
<span class="input-group-addon" style="width: 200px">Problem treshold</span>
<input id="t2" type="number" step="1" class="form-control" placeholder="100" value="100">
<span class="input-group-addon">milliseconds</span>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-success" id="update-heatmap" type="button">refresh</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
var getHeatmapUrl = function(){
return "heatmap.png?"
+ "t0=" + Number($("#t0").val())
+ "&t1=" + Number($("#t1").val())
+ "&t2=" + Number($("#t2").val())
+ "&now=" + Date.now();
}
var fetchJSON = function(url) {
return new Promise(function(resolve, reject) {
console.log("calling " + url);
@@ -172,7 +219,7 @@ var main = function(timeout){
loadingDialog(true);
fetchJSON("/check_all?timeout="+timeout)
fetchJSON("check_all?timeout="+timeout)
.then(function(data){
loadingDialog(false);
@@ -182,6 +229,8 @@ var main = function(timeout){
// prepare nodes
var nodes = [];
var dnsCheckNodes = [];
var podIPs = [];
var resp = data.responses;
for (let podIP in resp) {
let value = resp[podIP];
@@ -192,14 +241,14 @@ var main = function(timeout){
"y": 0,
_data: value,
});
podIPs.push(podIP);
};
//console.log(nodes);
// prepare the edges
var edges = [];
var resp = data.responses;
for (let callId in resp) {
var call = resp[callId].response;
var call = resp[callId].response['podResults'];
if (typeof call !== 'string'){
for (let target in call) {
edges.push({
@@ -210,8 +259,61 @@ var main = function(timeout){
};
}
};
//console.log(edges);
console.log(edges);
console.log(nodes);
// if running goldpinger with PING_NUMBER, then we need to add nodes for the edges
// that are not reporting but are reported to
edges.forEach(function(edge) {
[edge.source, edge.target].forEach(function(value) {
if (podIPs.indexOf(value) == -1){
nodes.push({
"label": value,
"id": value,
"x": 0,
"y": 0,
_data: {},
});
podIPs.push(value);
}
})
})
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: {
"OK": podOk,
"elapsed": elapsed,
"isDNSCheckNode": true,
}
});
}
value["OK"] = allOk
dnsCheckNodes.push({
"label": checkedHost,
"id": checkedHost,
"x": 0,
"y": 0,
_data: value,
});
}
}
// build the actual graph
s = new sigma({
@@ -244,6 +346,19 @@ var main = function(timeout){
s.graph.addNode(node);
});
// generate any dns nodes on the graph
dnsCheckNodes.forEach(function(node, i, a){
node.x = 2;
node.y = (0.6 * i / a.length) - 0.8;
node.size = 10;
node.color = "#4CC40B";
if (!node._data.OK) {
node.color = "red";
}
//console.log(node);
s.graph.addNode(node);
});
// generate the edges
edges.forEach(function(edge, i){
var color = "#ccc";
@@ -255,6 +370,9 @@ var main = function(timeout){
if (!edge._data.OK) {
color = "red";
}
if ("isDNSCheckNode" in edge._data) {
type = "dashed";
}
var edge = {
id: "e" + i,
source: edge.source,
@@ -305,7 +423,7 @@ var main = function(timeout){
$('#modal-title').html(node.id);
$('#modal-body').html(prepareJSON(node._data));
// show the things
// show the things
$('#modal-window-code').modal('show');
});
s.bind("clickStage", function (e) {
@@ -321,7 +439,7 @@ var main = function(timeout){
$('#modal-title').html(edge.id);
$('#modal-body').html(prepareJSON(edge._data));
// show the things
// show the things
$('#modal-window-code').modal('show');
});
})
@@ -337,18 +455,31 @@ main();
$("#show-data").click(function (e) {
// fill the voids
$('#modal-title').html("/check_all");
$('#modal-title').html("check_all");
$('#modal-body').html(prepareJSON(global_data));
// show the things
// show the things
$('#modal-window-code').modal('show');
});
$("#reload-graph").click(function (e) {
s.kill();
main();
});
$("#show-heatmap").click(function (e) {
updateHeatmap();
$('#modal-window-heatmap').modal('show');
});
var updateHeatmap = function(){
$('#heatmap-body').html(
'<img src="' + getHeatmapUrl() + '" />'
);
}
$("#update-heatmap").click(function (e) {
updateHeatmap();
});
</script>
</body>
</html>

View File

@@ -1,7 +1,7 @@
---
swagger: '2.0'
info:
version: 1.0.0
version: 2.0.0
title: Goldpinger
definitions:
CallStats:
@@ -12,6 +12,17 @@ definitions:
type: integer
ping:
type: integer
DnsResult:
properties:
response-time-ms:
type: number
format: int64
error:
type: string
DnsResults:
type: object
additionalProperties:
$ref: '#/definitions/DnsResult'
PingResults:
type: object
properties:
@@ -42,8 +53,13 @@ definitions:
description: wall clock time in milliseconds
CheckResults:
type: object
additionalProperties:
$ref: '#/definitions/PodResult'
properties:
dnsResults:
$ref: '#/definitions/DnsResults'
podResults:
type: object
additionalProperties:
$ref: '#/definitions/PodResult'
CheckAllPodResult:
type: object
properties:
@@ -83,6 +99,10 @@ definitions:
podIP:
type: string
format: ipv4
dnsResults:
type: object
additionalProperties:
$ref: '#/definitions/DnsResults'
responses:
type: object
additionalProperties: