Skip to content

Commit c33964c

Browse files
author
Anze
committed
Initial commit
0 parents  commit c33964c

File tree

8 files changed

+236
-0
lines changed

8 files changed

+236
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

.gitlab-ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
image: docker:stable
2+
3+
services:
4+
- docker:dind
5+
6+
variables:
7+
DOCKER_DRIVER: overlay2
8+
9+
stages:
10+
- deploy
11+
12+
13+
deploy_to_docker_hub:
14+
stage: deploy
15+
when: manual
16+
only:
17+
# We only want master branch AND when tag looks like 'vX.Y.Z', however GitLab doesn't support conjunctive conditions yet:
18+
# https://gitlab.com/gitlab-org/gitlab-ce/issues/27818
19+
# refs:
20+
# - master # Yeah, that doesn't work... The job for a commit with a tag and on a master branch is not being created.
21+
#
22+
# However we can mark tags 'v*.*.*' as protected, which also allows us to (somewhat) safely use Private-Token as protected
23+
# CI variable.
24+
variables:
25+
- $CI_COMMIT_TAG =~ /^v[0-9]+[.][0-9]+[.][0-9]+$/
26+
script:
27+
- apk add --no-cache git
28+
# check that we are deploying the latest version:
29+
- export LAST_KNOWN_VERSION=`git tag -l --sort=-version:refname "v*.*.*" | head -n 1 | tr -d '[:space:]'`
30+
- '[ "$LAST_KNOWN_VERSION" == "$CI_COMMIT_TAG" ] || (echo "Tag does not denote latest known version (which is $LAST_KNOWN_VERSION), aborting!" && exit 1)'
31+
- echo "Deploying..."
32+
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
33+
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG" -t "$CI_REGISTRY_IMAGE:latest" --build-arg VERSION=$CI_COMMIT_TAG --build-arg VCS_REF=$CI_COMMIT_SHA --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') .
34+
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG"
35+
- docker push "$CI_REGISTRY_IMAGE:latest"
36+
- docker rmi grafolean/grafolean:$CI_COMMIT_TAG
37+
- docker rmi grafolean/grafolean:latest
38+

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM python:3.6-alpine
2+
3+
COPY requirements.txt grafolean-worker-ping.py /opt/
4+
RUN pip install -r /opt/requirements.txt
5+
RUN echo -e "* * * * * source /etc/environment; export BACKEND_URL BOT_TOKEN; python /opt/grafolean-worker-ping.py > /proc/1/fd/1 2> /proc/1/fd/2\n" > /etc/crontabs/root
6+
7+
# https://stackoverflow.com/a/47960145/593487
8+
ENTRYPOINT ["crond", "-f", "-d", "8"]

Pipfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[[source]]
2+
name = "pypi"
3+
url = "https://pypi.org/simple"
4+
verify_ssl = true
5+
6+
[dev-packages]
7+
8+
[packages]
9+
requests = "*"
10+
multiping = "*"
11+
12+
[requires]
13+
python_version = "3.6"

Pipfile.lock

Lines changed: 66 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker-compose.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: '2.1'
2+
services:
3+
4+
# To run this image, you must create a .env file with following content:
5+
# BACKEND_URL=... # backend API base url (the part before "/api/...")
6+
# BOT_TOKEN=... # bot token
7+
8+
grafolean-worker-ping:
9+
# If you wish to load an explicit version, change the next line. For example:
10+
# image: grafolean/grafolean-worker-ping:v1.0.0
11+
image: grafolean/grafolean-worker-ping
12+
build:
13+
context: .
14+
dockerfile: Dockerfile
15+
volumes:
16+
- .env:/etc/environment

grafolean-worker-ping.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from collections import defaultdict
2+
from multiping import MultiPing
3+
import os
4+
import requests
5+
import socket
6+
import time
7+
8+
N_PINGS = 3
9+
10+
# This is copy-pasted from multiping package; the reason is that we need to get MultiPing
11+
# instance, because it holds the IP addresses which correspond to the addresses we wanted
12+
# pinged - and the key in ping results is the IP.
13+
def multi_ping(dest_addrs, timeout, retry=0, ignore_lookup_errors=False):
14+
retry_timeout = float(timeout) / (retry + 1)
15+
16+
mp = MultiPing(dest_addrs, ignore_lookup_errors=ignore_lookup_errors)
17+
18+
results = {}
19+
retry_count = 0
20+
while retry_count <= retry:
21+
# Send a batch of pings
22+
mp.send()
23+
single_results, no_results = mp.receive(retry_timeout)
24+
# Add the results from the last sending of pings to the overall results
25+
results.update(single_results)
26+
if not no_results:
27+
# No addresses left? We are done.
28+
break
29+
retry_count += 1
30+
31+
return results, no_results, mp
32+
33+
def get_addr_for_ip_dict(addrs, mp):
34+
# This is complicated, and a hack. Still... mp (MultiPing instance) holds two lists,
35+
# self._dest_addrs and self._unprocessed_targets. List _unprocessed_targets has the addresses
36+
# that couldn't be resolved. Others were resolved, and _dest_addrs has the IPs in the same
37+
# order as original addresses.
38+
resolved_addrs = [a for a in addrs if a not in mp._unprocessed_targets]
39+
ip_to_addr = {k: v for k, v in zip(mp._dest_addrs, resolved_addrs)}
40+
return ip_to_addr
41+
42+
def do_ping(addrs):
43+
results = defaultdict(list)
44+
mp = None
45+
ip_to_addr = {}
46+
for i in range(N_PINGS):
47+
print(".")
48+
responses, no_responses, mp = multi_ping(addrs, timeout=2, retry=3, ignore_lookup_errors=True)
49+
50+
# Some addresses (like demo.grafolean.com) resolve to multiple IPs, so each call to multi_ping will
51+
# resolve differently - we must find the new IP addresses every time:
52+
ip_to_addr = get_addr_for_ip_dict(addrs, mp)
53+
54+
for no_resp in no_responses:
55+
addr = ip_to_addr.get(no_resp, no_resp)
56+
results[addr].append(None)
57+
for resp, t in responses.items():
58+
addr = ip_to_addr.get(resp, resp)
59+
results[addr].append(t)
60+
61+
if i < N_PINGS - 1:
62+
time.sleep(1)
63+
return dict(results)
64+
65+
def send_results_to_grafolean(base_url, account_id, bot_token, results):
66+
url = '{}/api/accounts/{}/values/?b={}'.format(base_url, account_id, bot_token)
67+
values = []
68+
for ip in results:
69+
for ping_index, ping_time in enumerate(results[ip]):
70+
values.append({
71+
'p': 'ping.{}.{}.success'.format(ip.replace('.', '_'), ping_index),
72+
'v': 0 if ping_time is None else 1,
73+
})
74+
if ping_time is not None:
75+
values.append({
76+
'p': 'ping.{}.{}.rtt'.format(ip.replace('.', '_'), ping_index),
77+
'v': ping_time,
78+
})
79+
print("Sending results to Grafolean")
80+
r = requests.post(url, json=values)
81+
print(r.text)
82+
r.raise_for_status()
83+
84+
if __name__ == "__main__":
85+
addrs = ["8.8.8.8", "youtube.com", "127.0.0.1", "demo.grafolean.com", "grafolean.com", "whateverdoeesndfexist.com"]
86+
results = do_ping(addrs)
87+
send_results_to_grafolean(os.environ.get('BACKEND_URL'), 1, os.environ.get('BOT_TOKEN'), results)

requirements.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-i https://pypi.org/simple
2+
certifi==2018.11.29
3+
chardet==3.0.4
4+
idna==2.8
5+
multiping==1.1.2
6+
requests==2.21.0
7+
urllib3==1.24.1

0 commit comments

Comments
 (0)