Compare commits

..
41 Commits
Author SHA1 Message Date
brb ece4304306 added argocd manifests
pipeline / Test (push) Successful in 38s
2023-08-22 16:05:46 +02:00
brb 3d81eee094 added jenkinsfile
pipeline / Test (push) Successful in 35s
2023-07-26 12:09:01 +02:00
brb 448f757bd1 added copying of JDK 17 into image
pipeline / Test (push) Successful in 33s
Docker / Test (push) Successful in 30s
Docker / Publish (push) Successful in 27s
2023-07-24 16:24:01 +02:00
brb 89d29492e6 removed non utf8 character
pipeline / Test (push) Successful in 32s
2023-07-24 11:56:47 +02:00
brb fa191a02d3 installed packages
pipeline / Test (push) Successful in 32s
Docker / Test (push) Successful in 31s
Docker / Publish (push) Successful in 8s
2023-07-24 10:27:54 +02:00
brb 143c885eb8 changed default values
pipeline / Test (push) Successful in 31s
Docker / Test (push) Successful in 31s
Docker / Publish (push) Successful in 13s
2023-07-24 10:22:51 +02:00
brb 193292b254 added env var loading of user_id 2023-07-24 10:13:49 +02:00
brb 706ce7be9b removed unused variables 2023-07-24 10:11:06 +02:00
brb aeee735661 added mypy ignore
Docker / Test (push) Successful in 30s
Docker / Publish (push) Successful in 13s
pipeline / Test (push) Successful in 32s
2023-07-24 10:03:29 +02:00
brb c89cdd2653 added cronjob based timing
pipeline / Test (push) Failing after 32s
2023-07-24 09:59:46 +02:00
brb 375048b959 updated default values for testing 2023-07-24 09:59:24 +02:00
brb 118933af10 updated to install mypy type 2023-07-24 09:55:13 +02:00
brb 1939449a8f renamed default variable 2023-07-24 09:53:31 +02:00
brb 89cb193fe3 updated cronjob defaults 2023-07-24 09:38:40 +02:00
brb a3a692f2be added cronjob defaults 2023-07-24 09:37:51 +02:00
brb be69763f48 added cronjob decorator code 2023-07-24 09:37:41 +02:00
Brian Bjarke Jensen d13e069287 Merge pull request 'added separate flush interval' (#13) from main into release
Docker / Test (push) Successful in 39s
Docker / Publish (push) Successful in 13s
Reviewed-on: http://192.168.1.2:3000/brian/bandwidth_probing/pulls/13
2023-07-12 14:16:02 +02:00
brb bc0d2aced7 updated flake8 outputs
pipeline / Test (push) Successful in 39s
2023-07-12 14:11:35 +02:00
brb d872cfde43 flake8 compliant 2023-07-12 14:11:05 +02:00
brb 7a396a88fb updated workflow
pipeline / Test (push) Failing after 34s
2023-07-12 14:08:51 +02:00
brb 238188f4a5 introduced flake8 error 2023-07-12 14:08:37 +02:00
brb 63975169c0 mypy compliant
pipeline / Test (push) Successful in 38s
2023-07-12 14:04:10 +02:00
brb 387986e2d8 flake8 compliant 2023-07-12 14:03:41 +02:00
brb d5bcf04e61 updated readme
pipeline / Test (push) Failing after 38s
2023-07-12 14:01:06 +02:00
brb 12ccd1e586 added extra data flushing event with separate trigger time 2023-07-12 13:59:53 +02:00
brb e3fadbc22c added data_queue flush interval 2023-07-12 13:47:56 +02:00
brb ecfddfc69e added data_queue flush interval 2023-07-12 13:47:37 +02:00
brb caeace7907 updated to check for env var USER_ID 2023-07-12 13:46:01 +02:00
brb a456ccc536 updated to check for env var USER_ID 2023-07-12 13:35:40 +02:00
brb ae26182e23 updated default values 2023-07-12 13:26:12 +02:00
brb 652fe9af3a added checking for environment variables 2023-07-12 13:17:12 +02:00
Brian Bjarke Jensen 1760fc8136 Merge pull request 'added data queue' (#9) from main into release
Docker / Publish (push) Successful in 13s
Docker / Test (push) Successful in 39s
Reviewed-on: http://192.168.1.2:3000/brian/bandwidth_probing/pulls/9
2023-07-12 12:59:05 +02:00
brb cffcac8b37 added data queue
pipeline / Test (push) Successful in 39s
2023-07-12 12:54:52 +02:00
brb 907958484c removed unused step
pipeline / Test (push) Successful in 39s
Docker / Test (push) Successful in 39s
Docker / Publish (push) Successful in 8s
2023-07-12 12:08:42 +02:00
brb 9a1724742a added code to extract version number
Docker / Test (push) Successful in 39s
Docker / Publish (push) Successful in 7s
2023-07-12 11:54:31 +02:00
brb 20023ca9a9 added code to print version number
Docker / Publish (push) Failing after 6s
Docker / Test (push) Successful in 39s
2023-07-12 11:49:00 +02:00
brb 478d258a31 added code to extract version number
Docker / Test (push) Successful in 40s
Docker / Publish (push) Failing after 7s
2023-07-12 11:45:18 +02:00
brb eb8b1456ab added code to extract version number
Docker / Test (push) Successful in 40s
Docker / Publish (push) Failing after 5s
2023-07-12 11:39:44 +02:00
brb 4c99b6a1af testing how to set env vars
Docker / Test (push) Successful in 38s
Docker / Publish (push) Failing after 4s
2023-07-12 11:32:50 +02:00
brb 0becddf81f added code to extract version number
Docker / Test (push) Successful in 38s
Docker / Publish (push) Failing after 5s
2023-07-12 11:29:26 +02:00
brb 233c0ef977 added code to extract version number
Docker / Test (push) Successful in 40s
Docker / Publish (push) Failing after 5s
2023-07-12 11:27:46 +02:00
15 changed files with 308 additions and 86 deletions
+5 -27
View File
@@ -1,5 +1,5 @@
name: pipeline name: pipeline
run-name: ${{ gitea.actor }} is running script run-name: ${{ gitea.actor }} is running script
runs-on: ubuntu-latest runs-on: ubuntu-latest
on: on:
push: push:
@@ -23,30 +23,8 @@ jobs:
pip install flake8 mypy pytest pip install flake8 mypy pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: PEP8 check - name: PEP8 check
run: flake8 ./code --benchmark --exit-zero run: flake8 ./code --benchmark
- name: Type check - name: Type check
run: mypy ./code run: |
# publish: python3 -m pip install types-python-dateutil
# name: Publish mypy ./code
# runs-on: ubuntu-latest
# needs: [test]
# if: gitea.ref == 'refs/heads/release'
# env:
# repository_host: 192.168.1.2:5000
# repository_name: ${{ gitea.repository }}
# repository_version: v1.0
# steps:
# - uses: actions/checkout@v3
# - name: Build
# run: |
# echo "> docker build . --file Dockerfile --tag ${repository_name}:${repository_version}"
# docker build . --file Dockerfile --tag ${repository_name}:${repository_version}
# - name: Tag
# run: |
# echo "> docker tag ${repository_name}:${repository_version} ${repository_host}/${repository_name}:${repository_version}"
# docker tag ${repository_name}:${repository_version} ${repository_host}/${repository_name}:${repository_version}
# - name: Push
# run: |
# echo "> docker push ${repository_host}/${repository_name}:${repository_version}"
# docker push ${repository_host}/${repository_name}:${repository_version}
+14 -9
View File
@@ -22,9 +22,11 @@ jobs:
pip install flake8 mypy pytest pip install flake8 mypy pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: PEP8 check - name: PEP8 check
run: flake8 ./code --benchmark --exit-zero run: flake8 ./code --benchmark
- name: Type check - name: Type check
run: mypy ./code run: |
python3 -m pip install types-python-dateutil
mypy ./code
publish: publish:
name: Publish name: Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -33,17 +35,20 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- -
name: Set Environment Variables name: Set Environment Variables
id: vars
env:
repository: ${{gitea.repository}}
ref_name: ${{gitea.ref_name}}
run: | run: |
echo "$GITEA_REF" echo $repository
echo "TAG=${GITEA_REF#refs/*/}" >> $gitea echo ::set-output name=OWNER::${repository#/*}
- echo ::set-output name=PROJECT::${repository#*/}
name: Show Environment Variables echo $ref_name
run: | echo ::set-output name=TAG::${ref_name#refs/*/}
echo "$gitea.TAG"
- -
name: Push Image name: Push Image
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
push: true push: true
tags: 192.168.1.2:5000/${{gitea.repository}}:${{steps.get_version.outputs.VERSION}} tags: 192.168.1.2:5000/${{gitea.repository}}:${{steps.vars.outputs.TAG}}
+5
View File
@@ -5,6 +5,11 @@
# pull official base image # pull official base image
FROM python:3.10-bookworm as BUILDER FROM python:3.10-bookworm as BUILDER
# install JDK 17
ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:17-jre $JAVA_HOME $JAVA_HOME
ENV PATH="${JAVA_HOME}/bin:${PATH}"
# install Ookla's speedtest # install Ookla's speedtest
RUN apt update && \ RUN apt update && \
apt install -y speedtest-cli apt install -y speedtest-cli
Vendored
+21
View File
@@ -0,0 +1,21 @@
pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building..'
}
}
stage('Test') {
steps {
echo 'Testing..'
}
}
stage('Deploy') {
steps {
echo 'Deploying..'
}
}
}
}
+18
View File
@@ -1,3 +1,21 @@
# bandwidth_probing # bandwidth_probing
Service that runs in a container and measures network bandwidth at a specified interval. The results are sent to a MongoDB database. Logs are sent to discord channel. Service that runs in a container and measures network bandwidth at a specified interval. The results are sent to a MongoDB database. Logs are sent to discord channel.
## Environment Variables
```python
LOGGER_LEVEL: Literal['debug', 'info', 'warning', 'error', 'critical'] = 'debug'
USER_ID: int = 0 # id number 0 is reserved for testing
REPLICATES: int = 3 # number of replicates to be measured per event
TRIGGER_INTERVAL_SECONDS: float = 3600 # seconds between events
DATA_QUEUE_FLUSH_INTERVAL_SECONDS: float = 600 # seconds between attempts to push data stuck in data queue
DB_IP_ADDRESS: str = '192.168.1.2'
DB_NAME: str = 'bandwidth_probing'
DB_COLLECTION_NAME: str = 'data'
DISCORD_SERVICE_NAME: str = 'bandwidth_probing
DISCORD_WEBHOOK_URL: str = 'https://discord.com/api/webhooks/1127970367047225354/wNkoRex4OncMw11OCJg6atR_xgHS2VhLrku4jYIUw81Kr4sutdD3Tt-XAltG0UQy14rZ'
DISCORD_LOGGER_LEVEL: Literal['debug', 'info', 'warning', 'error', 'critical'] = 'warning'
```
+8
View File
@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: bandwidth-probing
data:
USER_ID: "1"
SCHEDULE: "05 * * * *"
+37
View File
@@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bandwidth-probing
namespace: bandwidth-probing
labels:
environment: live
app: bandwidth-probing
spec:
replicas: 1
selector:
matchLabels:
app: bandwidth-probing
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app: bandwidth-probing
spec:
containers:
- envFrom:
- configMapRef:
name: app-config
image: '192.168.1.2:5000/brian/bandwidth_probing:v1.3.2'
imagePullPolicy: Always
name: bandwidth-probing
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: bandwidth-probing
+53
View File
@@ -0,0 +1,53 @@
from cron_converter import Cron
from configparser import ConfigParser
from pathlib import Path
from datetime import datetime
import dateutil
import time
import functools
import logging
# load default values
config = ConfigParser()
config.read(Path(__file__).parent / 'defaults.ini')
SCHEDULE = config.get('cron', 'schedule')
TZ = config.get('cron', 'tz')
def cronjob(
schedule: str = SCHEDULE,
tz: str = TZ
):
"""
decorator for running function as a cronjob.
"""
logging.info(f'started cronjob with schedule {schedule} and tz {tz}')
def decorator_cronjob(func):
@functools.wraps(func)
def wrapper_cronjob(*args, **kwargs):
scheduler = Cron(schedule).schedule(timezone_str=tz)
trigger_time = scheduler.next()
while True:
while trigger_time > datetime.now(tz=dateutil.tz.gettz(tz)):
# waiting loop
time.sleep(1)
# actual execution
try:
func(*args, **kwargs)
except Exception as e:
logging.warning(f'failed executing {func}: {e}')
raise
else:
# update trigger time
trigger_time = scheduler.next()
return wrapper_cronjob
return decorator_cronjob
if __name__ == '__main__':
@cronjob(schedule='* * * * *', tz=TZ)
def test_loop(msg: str):
print(msg)
test_loop('hello world')
+15
View File
@@ -3,6 +3,7 @@ from dotenv import load_dotenv
from configparser import ConfigParser from configparser import ConfigParser
from pathlib import Path from pathlib import Path
import logging import logging
import os
# load default values # load default values
@@ -21,6 +22,20 @@ def connect(
""" """
Connect to MongoDB database and return collection. Connect to MongoDB database and return collection.
""" """
# load environment variables
load_dotenv()
ip_addr = os.getenv(
'DB_IP_ADDRESS',
default=DB_IP_ADDRESS
)
db_name = os.getenv(
'DB_NAME',
default=DB_NAME
)
collection_name = os.getenv(
'DB_COLLECTION_NAME',
default=DB_COLLECTION_NAME
)
# connect to database # connect to database
client: MongoClient = MongoClient(ip_addr) client: MongoClient = MongoClient(ip_addr)
db = client[db_name] db = client[db_name]
+6 -3
View File
@@ -1,8 +1,7 @@
[main] [main]
logger_level=info logger_level=debug
user_id=0 user_id=0
replicates=3 replicates=3
trigger_interval_seconds=60
[database] [database]
db_ip_address=192.168.1.2 db_ip_address=192.168.1.2
@@ -12,4 +11,8 @@ db_collection_name=data
[discord] [discord]
webhook_url=https://discord.com/api/webhooks/1127970367047225354/wNkoRex4OncMw11OCJg6atR_xgHS2VhLrku4jYIUw81Kr4sutdD3Tt-XAltG0UQy14rZ webhook_url=https://discord.com/api/webhooks/1127970367047225354/wNkoRex4OncMw11OCJg6atR_xgHS2VhLrku4jYIUw81Kr4sutdD3Tt-XAltG0UQy14rZ
service_name=bandwidth_probing service_name=bandwidth_probing
logger_level=warning logger_level=warning
[cron]
schedule=05 * * * *
tz=Europe/Paris
+7 -1
View File
@@ -20,13 +20,18 @@ def initialise_app(
discord_service_name: str = DISCORD_SERVICE_NAME, discord_service_name: str = DISCORD_SERVICE_NAME,
discord_webhook_url: str = DISCORD_WEBHOOK_URL, discord_webhook_url: str = DISCORD_WEBHOOK_URL,
discord_logger_level: str = DISCORD_LOGGER_LEVEL discord_logger_level: str = DISCORD_LOGGER_LEVEL
): ) -> None:
""" """
Convienience function that ensures eveything is ready Convienience function that ensures eveything is ready
before running the main loop. before running the main loop.
""" """
# load environment variables # load environment variables
load_dotenv() load_dotenv()
assert (
'USER_ID' in os.environ
), (
'environment variable USER_ID must be specified'
)
logger_level = os.getenv( logger_level = os.getenv(
'LOGGER_LEVEL', 'LOGGER_LEVEL',
default=LOGGER_LEVEL default=LOGGER_LEVEL
@@ -58,6 +63,7 @@ def initialise_app(
level = getattr(logging, discord_logger_level.upper()) level = getattr(logging, discord_logger_level.upper())
discord_handler.setLevel(level=level) discord_handler.setLevel(level=level)
logger.addHandler(discord_handler) logger.addHandler(discord_handler)
logging.debug('finished')
if __name__ == '__main__': if __name__ == '__main__':
+100 -46
View File
@@ -1,9 +1,12 @@
import time import time
import logging import logging
import os import os
import json
from configparser import ConfigParser from configparser import ConfigParser
from pathlib import Path from pathlib import Path
from queue import Queue
from initialise_app import initialise_app from initialise_app import initialise_app
from cron import cronjob
from bandwidth import measure as measure_bandwidth from bandwidth import measure as measure_bandwidth
from database import connect from database import connect
@@ -11,68 +14,119 @@ from database import connect
# load default values # load default values
config = ConfigParser() config = ConfigParser()
config.read(Path(__file__).parent / 'defaults.ini') config.read(Path(__file__).parent / 'defaults.ini')
REPLICATES = int(config.get('main', 'replicates'))
USER_ID = int(config.get('main', 'user_id')) USER_ID = int(config.get('main', 'user_id'))
TRIGGER_INTERVAL_SECONDS = float( REPLICATES = int(config.get('main', 'replicates'))
config.get( SCHEDULE = config.get('cron', 'schedule')
'main', TZ = config.get('cron', 'tz')
'trigger_interval_seconds'
)
)
def event( def flush_data(
replicates: int = REPLICATES, data_queue: Queue,
user_id: int = USER_ID user_id: int
): ) -> None:
logging.debug('started event') # connect to database
# load environment variables
replicates = int(os.getenv('REPLICATES', default=REPLICATES))
user_id = int(os.getenv('USER_ID', default=USER_ID))
# setup database connection
db = connect() db = connect()
# run event # stop early
for rep_num in range(replicates): queue_size = data_queue.qsize()
logging.info(f'running replicate {rep_num}') if queue_size == 0:
# do measurement logging.debug('queue already empty')
try: logging.debug('finished')
res = measure_bandwidth() return
except Exception as e: # flush queue
logging.error(f'failed to measure bandwidth: {e}') for i in range(queue_size):
continue # get data
# upload result to database data = data_queue.get()
# prepare payload
payload_dict = { payload_dict = {
'user_id': user_id, 'user_id': user_id,
'data': res 'data': data
} }
# send to database
try: try:
db_id = db.insert_one(payload_dict).inserted_id db_id = db.insert_one(payload_dict).inserted_id
except Exception as e: except Exception as e:
logging.error(f'failed sending results to database: {e}') data_queue.put(data) # put data back in queue
logging.error('failed sending data to database')
logging.debug(
'failed pushing to database:\n'
f'{json.dumps(payload_dict, indent=4)}\n'
f'with error: {e}'
)
continue continue
logging.debug(f'data sent to database received db_id: {db_id}') else:
logging.debug(f'data sent to database received id: {db_id}')
logging.debug('finished')
def event(
data_queue: Queue,
user_id: int,
replicates: int = REPLICATES
) -> None:
# run event
for rep_num in range(replicates):
logging.debug(f'running replicate {rep_num+1} of {replicates}')
# do measurement
try:
data = measure_bandwidth()
except Exception as e:
logging.error(f'failed to measure bandwidth: {e}')
continue
# add data to queue
data_queue.put(data)
# polite pause
time.sleep(1) time.sleep(1)
# flush queue
flush_data(
data_queue=data_queue,
user_id=user_id
)
logging.debug('finished') logging.debug('finished')
if __name__ == '__main__': if __name__ == '__main__':
# setup
initialise_app() initialise_app()
trigger_time = time.time() data_queue: Queue = Queue()
while True: user_id = int(os.getenv('USER_ID', default=USER_ID))
if time.time() >= trigger_time: replicates = int(os.getenv('REPLICATES', default=REPLICATES))
# set new trigger time schedule = os.getenv(
trigger_interval = float( key='SCHEDULE',
os.getenv( default=SCHEDULE
'TRIGGER_INTERVAL_SECONDS', )
default=TRIGGER_INTERVAL_SECONDS tz = os.getenv(
) key='TZ',
) default=TZ
trigger_time += trigger_interval )
# run event
@cronjob(schedule=schedule, tz=tz)
def main_loop(
data_queue: Queue,
user_id: int,
replicates: int = REPLICATES
) -> None:
# run event
for rep_num in range(replicates):
logging.debug(f'running replicate {rep_num+1} of {replicates}')
# do measurement
try: try:
event() data = measure_bandwidth()
except Exception as e: except Exception as e:
logging.error(e) logging.error(f'failed to measure bandwidth: {e}')
else: continue
logging.info('finished event') # add data to queue
time.sleep(1) data_queue.put(data)
# polite pause
time.sleep(1)
# flush queue
flush_data(
data_queue=data_queue,
user_id=user_id
)
logging.debug('finished')
# start main loop
main_loop(
data_queue=data_queue,
user_id=user_id,
replicates=replicates
)
+3
View File
@@ -4,4 +4,7 @@
ignore_missing_imports = True ignore_missing_imports = True
[mypy-speedtest.*] [mypy-speedtest.*]
ignore_missing_imports = True
[mypy-cron_converter.*]
ignore_missing_imports = True ignore_missing_imports = True
+12
View File
@@ -3,15 +3,27 @@ async-timeout==4.0.2
attrs==23.1.0 attrs==23.1.0
certifi==2023.5.7 certifi==2023.5.7
charset-normalizer==3.2.0 charset-normalizer==3.2.0
cron-converter==1.0.2
discord-webhook==1.1.0 discord-webhook==1.1.0
dnspython==2.3.0 dnspython==2.3.0
flake8==6.0.0
frozenlist==1.3.3 frozenlist==1.3.3
idna==3.4 idna==3.4
mccabe==0.7.0
multidict==6.0.4 multidict==6.0.4
mypy==1.4.1
mypy-extensions==1.0.0
pycodestyle==2.10.0
pyflakes==3.0.1
pymongo==4.4.0 pymongo==4.4.0
python-dateutil==2.8.2
python-dotenv==1.0.0 python-dotenv==1.0.0
python-logging-discord-handler==0.1.4 python-logging-discord-handler==0.1.4
requests==2.31.0 requests==2.31.0
six==1.16.0
speedtest-cli==2.1.3 speedtest-cli==2.1.3
tomli==2.0.1
types-python-dateutil==2.8.19.14
typing_extensions==4.7.1
urllib3==2.0.3 urllib3==2.0.3
yarl==1.9.2 yarl==1.9.2