From 037ba71678fae90c1a5be746eef57cc74aa2ad5b Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 17:52:40 -0700 Subject: [PATCH 01/24] ips parsed from config.json --- collector/server.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/collector/server.py b/collector/server.py index 17d64126..118943e7 100644 --- a/collector/server.py +++ b/collector/server.py @@ -3,6 +3,7 @@ from threading import Thread import enum import logging +import json from fastapi import FastAPI, Response from fastapi.middleware.cors import CORSMiddleware @@ -140,10 +141,18 @@ async def metrics(): ) args = parser.parse_args() - ip_list = args.ips.split(',') + try: + with open("../config/config.json", "r") as f: + config = json.load(f) + left_ip = config["PRINTING"]["LEFT"]["IP"] + right_ip = config["PRINTING"]["RIGHT"]["IP"] + ip_list=[left_ip, right_ip] + + thread = Thread(target = scrape_snmp, args=(ip_list,), daemon=True) + thread.start() + except Exception as e: + logging.error(f"error opening config file: {e}") - thread = Thread(target = scrape_snmp, args=(ip_list,), daemon=True) - thread.start() uvicorn.run( app, host=args.host, From 63a55e64e422038aaea0af5ed8881d10342a3073 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 18:08:16 -0700 Subject: [PATCH 02/24] added logs for debugging --- collector/server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/collector/server.py b/collector/server.py index 118943e7..85f2e1a4 100644 --- a/collector/server.py +++ b/collector/server.py @@ -148,6 +148,8 @@ async def metrics(): right_ip = config["PRINTING"]["RIGHT"]["IP"] ip_list=[left_ip, right_ip] + logging.info(f"connected to left printer ip: {left_ip}") + logging.info(f"connected to right printer ip: {right_ip}") thread = Thread(target = scrape_snmp, args=(ip_list,), daemon=True) thread.start() except Exception as e: From a28577f4bd3ba27389497059820d94ab7664fad0 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:38:35 -0700 Subject: [PATCH 03/24] absolute path for config --- collector/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector/server.py b/collector/server.py index 85f2e1a4..02340133 100644 --- a/collector/server.py +++ b/collector/server.py @@ -142,7 +142,7 @@ async def metrics(): args = parser.parse_args() try: - with open("../config/config.json", "r") as f: + with open("/config/config.json", "r") as f: config = json.load(f) left_ip = config["PRINTING"]["LEFT"]["IP"] right_ip = config["PRINTING"]["RIGHT"]["IP"] From f9dcc2bd2dea7d0ef6c7f5ae220232afa70400ff Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:39:54 -0700 Subject: [PATCH 04/24] mounted config as volume --- collector/server.py | 2 +- docker-compose.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/collector/server.py b/collector/server.py index 02340133..2c672072 100644 --- a/collector/server.py +++ b/collector/server.py @@ -142,7 +142,7 @@ async def metrics(): args = parser.parse_args() try: - with open("/config/config.json", "r") as f: + with open("/tmp/config.json", "r") as f: config = json.load(f) left_ip = config["PRINTING"]["LEFT"]["IP"] right_ip = config["PRINTING"]["RIGHT"]["IP"] diff --git a/docker-compose.yml b/docker-compose.yml index a5e6b173..15339709 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,9 @@ services: build: context: . dockerfile: ./collector/Dockerfile + + volumes: + - ./config/config.json:/tmp/config.json # we attach the print container to an external docker # network called "poweredge". we do this so a prometheus # container can pull metrics from the server over HTTP From d5bdc86e662dc2d2b16a3d73996ccce5d5a31c7a Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 22:18:51 -0700 Subject: [PATCH 05/24] turned config path as required command argument --- collector/Dockerfile | 4 +++- collector/server.py | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/collector/Dockerfile b/collector/Dockerfile index 67bda78a..33ece497 100644 --- a/collector/Dockerfile +++ b/collector/Dockerfile @@ -10,4 +10,6 @@ COPY ./collector/server.py /app EXPOSE 5000 -ENTRYPOINT [ "python", "server.py" ] +ENTRYPOINT [ "python", "server.py"] + +CMD [ "--config-file-path", "/tmp/config.json"] diff --git a/collector/server.py b/collector/server.py index 2c672072..506843a6 100644 --- a/collector/server.py +++ b/collector/server.py @@ -116,7 +116,12 @@ async def metrics(): if __name__ == "__main__": parser = argparse.ArgumentParser("snmp coolness") - + parser.add_argument( + "--config-file-path", + required=True, + help="Path to config file that stores IPs", + ) + parser.add_argument( "--ips", help="List of IP addresses of snmp agent (default: 192.168.69.208,192.168.69.149)", @@ -142,7 +147,7 @@ async def metrics(): args = parser.parse_args() try: - with open("/tmp/config.json", "r") as f: + with open(args.config_file_path, "r") as f: config = json.load(f) left_ip = config["PRINTING"]["LEFT"]["IP"] right_ip = config["PRINTING"]["RIGHT"]["IP"] From 20927d61d2600c3d9a011bff41cdddf8e38437bd Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:28:22 -0700 Subject: [PATCH 06/24] combined printer & collector dockerfiles (attempt #1) --- printer/Dockerfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index 37a2360f..6b1f67b1 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -14,18 +14,23 @@ RUN python3 -m venv /opt/venv # Set the virtual environment as the default Python environment ENV PATH="/opt/venv/bin:$PATH" -COPY ./printer/requirements.txt /app/printer/requirements.txt +COPY ./printer/requirements.txt /app/printer/requirements.txt +COPY ../collector/requirements.txt /app/collector/requirements.txt RUN /opt/venv/bin/pip install -r /app/printer/requirements.txt +RUN /opt/venv/bin/pip install -r /app/collector/requirements.txt COPY ./config/config.json /app/config/config.json COPY ./printer/what.sh /app/printer/what.sh COPY ./printer/*.py /app/printer/ +COPY ./collector/server.py /app/collector/server.py -EXPOSE 9000 +EXPOSE 5000 9000 # The below command runs the bash script that sets up the connection to the # printers and runs PrintHandler.js -ENTRYPOINT [ "./printer/what.sh" ] +ENTRYPOINT [ "sh", "-c", "./printer/what.sh & python3 /app/collector/server.py \"$@\"" ] +CMD ["--config-file-path", "/app/config/config.json"] + From 49a17bff4db6f9c1b52d0edda54823b8f0c4b1ca Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Tue, 10 Jun 2025 23:33:04 -0700 Subject: [PATCH 07/24] changed collector path --- printer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index 6b1f67b1..ae4f2a15 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -15,7 +15,7 @@ RUN python3 -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" COPY ./printer/requirements.txt /app/printer/requirements.txt -COPY ../collector/requirements.txt /app/collector/requirements.txt +COPY ./collector/requirements.txt /app/collector/requirements.txt RUN /opt/venv/bin/pip install -r /app/printer/requirements.txt RUN /opt/venv/bin/pip install -r /app/collector/requirements.txt From 2541df6bfce30767b3758639cb6ca3227f545f45 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:05:55 -0700 Subject: [PATCH 08/24] merged into one container merged with dev rebased to dev --- collector/Dockerfile | 15 --- collector/requirements.txt | 5 - docker-compose.dev.yml | 8 +- docker-compose.yml | 6 - printer/Dockerfile | 6 +- collector/server.py => printer/collector.py | 115 ++++---------------- printer/metrics.py | 26 +++++ printer/requirements.txt | 2 + printer/server.py | 21 +++- 9 files changed, 71 insertions(+), 133 deletions(-) delete mode 100644 collector/Dockerfile delete mode 100644 collector/requirements.txt rename collector/server.py => printer/collector.py (55%) diff --git a/collector/Dockerfile b/collector/Dockerfile deleted file mode 100644 index 33ece497..00000000 --- a/collector/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM python:3.10.2-slim-buster - -WORKDIR /app - -COPY ./collector/requirements.txt /app - -RUN pip install -r /app/requirements.txt - -COPY ./collector/server.py /app - -EXPOSE 5000 - -ENTRYPOINT [ "python", "server.py"] - -CMD [ "--config-file-path", "/tmp/config.json"] diff --git a/collector/requirements.txt b/collector/requirements.txt deleted file mode 100644 index 3e7d6aa2..00000000 --- a/collector/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -fastapi==0.84.0 -uvicorn==0.18.3 -py-grpc-prometheus==0.7.0 -pysnmp==4.4.12 -pyasn1==0.4.8 \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 99eda4a4..3342d2e6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -17,10 +17,4 @@ services: - --development - --port=14000 - --dont-delete-pdfs - snmp-collector: - build: - context: . - dockerfile: ./collector/Dockerfile - ports: - - 5000:5000 - + - --config-json-path=/app/config/config.json diff --git a/docker-compose.yml b/docker-compose.yml index 15339709..3863f65f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,13 +13,7 @@ services: - ~/.ssh/known_hosts:/app/known_hosts - "/etc/cups/ppd/:/etc/cups/ppd" tty: true - snmp-collector: - build: - context: . - dockerfile: ./collector/Dockerfile - volumes: - - ./config/config.json:/tmp/config.json # we attach the print container to an external docker # network called "poweredge". we do this so a prometheus # container can pull metrics from the server over HTTP diff --git a/printer/Dockerfile b/printer/Dockerfile index ae4f2a15..bfc66313 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -15,22 +15,18 @@ RUN python3 -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" COPY ./printer/requirements.txt /app/printer/requirements.txt -COPY ./collector/requirements.txt /app/collector/requirements.txt RUN /opt/venv/bin/pip install -r /app/printer/requirements.txt -RUN /opt/venv/bin/pip install -r /app/collector/requirements.txt COPY ./config/config.json /app/config/config.json COPY ./printer/what.sh /app/printer/what.sh COPY ./printer/*.py /app/printer/ -COPY ./collector/server.py /app/collector/server.py EXPOSE 5000 9000 # The below command runs the bash script that sets up the connection to the # printers and runs PrintHandler.js -ENTRYPOINT [ "sh", "-c", "./printer/what.sh & python3 /app/collector/server.py \"$@\"" ] -CMD ["--config-file-path", "/app/config/config.json"] +ENTRYPOINT [ "./printer/what.sh" ] diff --git a/collector/server.py b/printer/collector.py similarity index 55% rename from collector/server.py rename to printer/collector.py index 506843a6..2259384a 100644 --- a/collector/server.py +++ b/printer/collector.py @@ -1,6 +1,4 @@ import time -import argparse -from threading import Thread import enum import logging import json @@ -11,36 +9,9 @@ from pysnmp.hlapi import * import uvicorn -snmp_metric = prometheus_client.Gauge( - "snmp_metric", - "ex: Number of pages printed", - ["name", "ip"], -) - -snmp_error = prometheus_client.Gauge( - "snmp_error", - "Error metrics", - ["name", "ip"], -) - -snmp_req_duration = prometheus_client.Summary( - "snmp_request_duration", - "Time it took for SNMP request", -) +from metrics import MetricsHandler +metrics_handler = MetricsHandler.instance() -device_unreachable = prometheus_client.Gauge( - "device_unreachable", - "set to 1 when error", -) - -app = FastAPI() - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_methods=["*"], - allow_headers=["*"], -) logging.basicConfig( # in mondo we trust @@ -66,15 +37,27 @@ def __init__(self, metric_name, metric_value, is_error=False): self.metric_value = metric_value self.is_error = is_error -def scrape_snmp(ip_list): +def fetch_ips_from_config(config_file_path): + try: + with open(config_file_path, "r") as f: + config = json.load(f) + right_ip = config["PRINTING"]["RIGHT"]["IP"] + ip_list=[right_ip] + logging.info(f"connected to right printer ip: {right_ip}") + return ip_list + except Exception as e: + logging.error(f"error opening config file: {e}") + + +def scrape_snmp(ip_list, sleep_duration_minutes=5): while True: for ip in ip_list: get_snmp_data(ip) - time.sleep(args.sleep_duration_minutes * 60) + time.sleep(sleep_duration_minutes * 60) def get_snmp_data(ip): for oid in SnmpOid: - with snmp_req_duration.time(): + with metrics_handler.snmp_request_duration.time(): errorIndication, errorStatus, errorIndex, varBinds = next( getCmd(SnmpEngine(), CommunityData('public', mpModel=0), @@ -84,7 +67,7 @@ def get_snmp_data(ip): ) if errorIndication: logging.error(f"Error indication from {ip} for metric {oid.metric_value}: {errorIndication}") - device_unreachable.set(1) + metrics_handler.device_unreachable.set(1) continue if errorStatus: logging.error(f"Error status from {ip} for metric {oid.metric_value}: {errorStatus.prettyPrint()}") @@ -95,74 +78,20 @@ def get_snmp_data(ip): # which would create a false positive, set the metric # to zero if the associated SNMP OID was not found if oid.is_error: - snmp_error.labels(name=oid.metric_name, ip=ip).set(0) + metrics_handler.snmp_error.labels(name=oid.metric_name, ip=ip).set(0) continue - device_unreachable.set(0) + metrics_handler.device_unreachable.set(0) if not varBinds: continue res = varBinds[0] if oid.is_error: - snmp_error.labels(name=oid.metric_name, ip=ip).set(1) + metrics_handler.snmp_error.labels(name=oid.metric_name, ip=ip).set(1) continue - snmp_metric.labels(name=oid.metric_name, ip=ip).set(res[1]) + metrics_handler.snmp_metric.labels(name=oid.metric_name, ip=ip).set(res[1]) -@app.get("/metrics") async def metrics(): return Response( content=prometheus_client.generate_latest(), media_type="text/plain", ) - -if __name__ == "__main__": - parser = argparse.ArgumentParser("snmp coolness") - parser.add_argument( - "--config-file-path", - required=True, - help="Path to config file that stores IPs", - ) - - parser.add_argument( - "--ips", - help="List of IP addresses of snmp agent (default: 192.168.69.208,192.168.69.149)", - default="192.168.69.208,192.168.69.149" - ) - parser.add_argument( - "--host", - help="ip address to listen for requests on, i.e. 0.0.0.0", - default='0.0.0.0', - ) - parser.add_argument( - "--port", - type=int, - help="port for the server to listen on, default is 5000", - default=5000 - ) - parser.add_argument( - "--sleep-duration-minutes", - type=int, - help="update sleepy time, default is 2mins", - default=2 - ) - - args = parser.parse_args() - try: - with open(args.config_file_path, "r") as f: - config = json.load(f) - left_ip = config["PRINTING"]["LEFT"]["IP"] - right_ip = config["PRINTING"]["RIGHT"]["IP"] - ip_list=[left_ip, right_ip] - - logging.info(f"connected to left printer ip: {left_ip}") - logging.info(f"connected to right printer ip: {right_ip}") - thread = Thread(target = scrape_snmp, args=(ip_list,), daemon=True) - thread.start() - except Exception as e: - logging.error(f"error opening config file: {e}") - - uvicorn.run( - app, - host=args.host, - port=args.port, - # reload=True, - ) diff --git a/printer/metrics.py b/printer/metrics.py index 7cada02e..6dcd0812 100644 --- a/printer/metrics.py +++ b/printer/metrics.py @@ -23,6 +23,32 @@ class Metrics(enum.Enum): "total bytes of files pointed to by cache", prometheus_client.Gauge, ) + SNMP_METRIC = ( + "snmp_metric", + "ex: Number of pages printed", + prometheus_client.Gauge, + ["name", "ip"], + ) + + SNMP_ERROR = ( + "snmp_error", + "Error metrics", + prometheus_client.Gauge, + ["name", "ip"], + ) + + SNMP_REQ_DURATION = ( + "snmp_request_duration", + "Time it took for SNMP request", + prometheus_client.Summary + ) + + DEVICE_UNREACHABLE = ( + "device_unreachable", + "set to 1 when error", + prometheus_client.Gauge + ) + def __init__(self, title, description, prometheus_type, labels=()): # we use the above default value for labels because it matches what's used diff --git a/printer/requirements.txt b/printer/requirements.txt index 5da669ca..56bd4bf9 100644 --- a/printer/requirements.txt +++ b/printer/requirements.txt @@ -4,3 +4,5 @@ py-grpc-prometheus==0.7.0 python-multipart==0.0.9 httpx==0.28.1 requests==2.32.3 +pysnmp==4.4.12 +pyasn1==0.4.8 diff --git a/printer/server.py b/printer/server.py index e71bc9de..490eaec4 100644 --- a/printer/server.py +++ b/printer/server.py @@ -6,6 +6,7 @@ import threading import time import uuid +import collector from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware @@ -50,6 +51,7 @@ def get_args() -> argparse.Namespace: "--config-json-path", default="/app/config/config.json", help="path to config json path", + required=True, ) parser.add_argument( "--development", @@ -63,7 +65,12 @@ def get_args() -> argparse.Namespace: default=False, help="specify if server should delete pdfs after printing", ) - + parser.add_argument( + "--sleep-duration-minutes", + type=int, + help="update sleepy time, default is 2mins", + default=2 + ) return parser.parse_args() @@ -211,8 +218,18 @@ async def read_item( t = threading.Thread( target=maybe_reopen_ssh_tunnel, daemon=True, - ) + ) t.start() + thread = threading.Thread( + target = collector.scrape_snmp, + args=( + collector.fetch_ips_from_config(args.config_json_path), + args.sleep_duration_minutes, + ), + daemon=True + ) + thread.start() + if __name__ == "__main__": uvicorn.run("server:app", host=args.host, port=args.port, reload=True) From b242905322d83a313099f6946406e63abf553e10 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Sat, 14 Jun 2025 16:03:23 -0700 Subject: [PATCH 09/24] cleaned up, changed parsing method, removed metrics, and moved thread merged --- printer/collector.py | 47 +++++++++++++++++++++++++------------------- printer/metrics.py | 15 +++++++------- printer/server.py | 22 ++++++++++----------- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/printer/collector.py b/printer/collector.py index 2259384a..fba9eb91 100644 --- a/printer/collector.py +++ b/printer/collector.py @@ -3,13 +3,10 @@ import logging import json -from fastapi import FastAPI, Response -from fastapi.middleware.cors import CORSMiddleware -import prometheus_client from pysnmp.hlapi import * -import uvicorn from metrics import MetricsHandler + metrics_handler = MetricsHandler.instance() @@ -20,6 +17,7 @@ level=logging.INFO, ) + class SnmpOid(enum.Enum): INK_LEVEL = ("ink_level", "1.3.6.1.2.1.43.11.1.1.9.1.1") INK_CAPACITY = ("ink_capacity", "1.3.6.1.2.1.43.11.1.1.8.1.1") @@ -37,14 +35,22 @@ def __init__(self, metric_name, metric_value, is_error=False): self.metric_value = metric_value self.is_error = is_error + def fetch_ips_from_config(config_file_path): try: with open(config_file_path, "r") as f: config = json.load(f) - right_ip = config["PRINTING"]["RIGHT"]["IP"] - ip_list=[right_ip] - logging.info(f"connected to right printer ip: {right_ip}") + printer_configs = config.get("PRINTING") + if not printer_configs: + raise Exception("No printers defined in config file") + + ip_list = [] + for printer in printer_configs: + ip = printer_configs[printer]["IP"] + logging.info(f"Adding printer {printer} with IP {ip}") + ip_list.append(ip) return ip_list + except Exception as e: logging.error(f"error opening config file: {e}") @@ -55,22 +61,29 @@ def scrape_snmp(ip_list, sleep_duration_minutes=5): get_snmp_data(ip) time.sleep(sleep_duration_minutes * 60) + def get_snmp_data(ip): for oid in SnmpOid: with metrics_handler.snmp_request_duration.time(): errorIndication, errorStatus, errorIndex, varBinds = next( - getCmd(SnmpEngine(), - CommunityData('public', mpModel=0), - UdpTransportTarget((ip, 161)), - ContextData(), - ObjectType(ObjectIdentity(oid.metric_value))) + getCmd( + SnmpEngine(), + CommunityData("public", mpModel=0), + UdpTransportTarget((ip, 161)), + ContextData(), + ObjectType(ObjectIdentity(oid.metric_value)), + ) ) if errorIndication: - logging.error(f"Error indication from {ip} for metric {oid.metric_value}: {errorIndication}") + logging.error( + f"Error indication from {ip} for metric {oid.metric_value}: {errorIndication}" + ) metrics_handler.device_unreachable.set(1) continue if errorStatus: - logging.error(f"Error status from {ip} for metric {oid.metric_value}: {errorStatus.prettyPrint()}") + logging.error( + f"Error status from {ip} for metric {oid.metric_value}: {errorStatus.prettyPrint()}" + ) # SNMP OIDs related to errors often dissappear when # the associated issue that the metric refers to is # no longer present (i.e. an empty tray now has @@ -89,9 +102,3 @@ def get_snmp_data(ip): metrics_handler.snmp_error.labels(name=oid.metric_name, ip=ip).set(1) continue metrics_handler.snmp_metric.labels(name=oid.metric_name, ip=ip).set(res[1]) - -async def metrics(): - return Response( - content=prometheus_client.generate_latest(), - media_type="text/plain", - ) diff --git a/printer/metrics.py b/printer/metrics.py index 6dcd0812..2b544fda 100644 --- a/printer/metrics.py +++ b/printer/metrics.py @@ -26,30 +26,29 @@ class Metrics(enum.Enum): SNMP_METRIC = ( "snmp_metric", "ex: Number of pages printed", - prometheus_client.Gauge, - ["name", "ip"], + prometheus_client.Gauge, + ["name", "ip"], ) SNMP_ERROR = ( "snmp_error", "Error metrics", prometheus_client.Gauge, - ["name", "ip"], + ["name", "ip"], ) SNMP_REQ_DURATION = ( "snmp_request_duration", "Time it took for SNMP request", - prometheus_client.Summary + prometheus_client.Summary, ) DEVICE_UNREACHABLE = ( "device_unreachable", "set to 1 when error", - prometheus_client.Gauge + prometheus_client.Gauge, ) - def __init__(self, title, description, prometheus_type, labels=()): # we use the above default value for labels because it matches what's used # in the prometheus_client library's metrics constructor, see @@ -64,8 +63,8 @@ class MetricsHandler: _instance = None def __init__(self): - raise RuntimeError('Call MetricsHandler.instance() instead') - + raise RuntimeError("Call MetricsHandler.instance() instead") + def init(self) -> None: for metric in Metrics: setattr( diff --git a/printer/server.py b/printer/server.py index 490eaec4..1a4e2690 100644 --- a/printer/server.py +++ b/printer/server.py @@ -69,7 +69,7 @@ def get_args() -> argparse.Namespace: "--sleep-duration-minutes", type=int, help="update sleepy time, default is 2mins", - default=2 + default=2, ) return parser.parse_args() @@ -218,18 +218,18 @@ async def read_item( t = threading.Thread( target=maybe_reopen_ssh_tunnel, daemon=True, - ) + ) t.start() - thread = threading.Thread( - target = collector.scrape_snmp, - args=( - collector.fetch_ips_from_config(args.config_json_path), - args.sleep_duration_minutes, - ), - daemon=True - ) - thread.start() + thread = threading.Thread( + target=collector.scrape_snmp, + args=( + collector.fetch_ips_from_config(args.config_json_path), + args.sleep_duration_minutes, + ), + daemon=True, + ) + thread.start() if __name__ == "__main__": uvicorn.run("server:app", host=args.host, port=args.port, reload=True) From e57384e8b91a52c497c37e0d8d95c6a844bfb5b0 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:13:24 -0700 Subject: [PATCH 10/24] made config-json not required --- printer/server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/printer/server.py b/printer/server.py index 1a4e2690..53657d4f 100644 --- a/printer/server.py +++ b/printer/server.py @@ -51,7 +51,6 @@ def get_args() -> argparse.Namespace: "--config-json-path", default="/app/config/config.json", help="path to config json path", - required=True, ) parser.add_argument( "--development", From 748a6a4fdeee1276391359a0b9b58684bdab1d10 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:15:08 -0700 Subject: [PATCH 11/24] add args to test_server --- printer/server.py | 19 ++++++++++--------- printer/test_server.py | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/printer/server.py b/printer/server.py index 53657d4f..c602d01b 100644 --- a/printer/server.py +++ b/printer/server.py @@ -220,15 +220,16 @@ async def read_item( ) t.start() - thread = threading.Thread( - target=collector.scrape_snmp, - args=( - collector.fetch_ips_from_config(args.config_json_path), - args.sleep_duration_minutes, - ), - daemon=True, - ) - thread.start() + if args.args.config_json_path: + thread = threading.Thread( + target=collector.scrape_snmp, + args=( + collector.fetch_ips_from_config(args.config_json_path), + args.sleep_duration_minutes, + ), + daemon=True, + ) + thread.start() if __name__ == "__main__": uvicorn.run("server:app", host=args.host, port=args.port, reload=True) diff --git a/printer/test_server.py b/printer/test_server.py index dd651d74..3cc77c60 100644 --- a/printer/test_server.py +++ b/printer/test_server.py @@ -13,7 +13,7 @@ class TestFastAPI(unittest.TestCase): def load_server_with_args(self, argv=[]): - argv_to_use = ["server.py"] + argv_to_use = ["server.py", "--config-json-path=config/config.example.json", ] argv_to_use.extend(argv) os.environ["RIGHT_PRINTER_NAME"] = "HP_P2015_DN" From 1da907a39fd6549771d2f9fb943ae2314646b710 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:16:51 -0700 Subject: [PATCH 12/24] typo --- printer/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/server.py b/printer/server.py index c602d01b..784d482c 100644 --- a/printer/server.py +++ b/printer/server.py @@ -220,7 +220,7 @@ async def read_item( ) t.start() - if args.args.config_json_path: + if args.config_json_path: thread = threading.Thread( target=collector.scrape_snmp, args=( From 21ffbc945805453c65da6ba619991ba0e10a2537 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:17:59 -0700 Subject: [PATCH 13/24] more typo fixes --- printer/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/test_server.py b/printer/test_server.py index 3cc77c60..dd651d74 100644 --- a/printer/test_server.py +++ b/printer/test_server.py @@ -13,7 +13,7 @@ class TestFastAPI(unittest.TestCase): def load_server_with_args(self, argv=[]): - argv_to_use = ["server.py", "--config-json-path=config/config.example.json", ] + argv_to_use = ["server.py"] argv_to_use.extend(argv) os.environ["RIGHT_PRINTER_NAME"] = "HP_P2015_DN" From 5d4d5718d3fdd204a9b6ee3d2abcbe9fa6ad7294 Mon Sep 17 00:00:00 2001 From: evan Date: Mon, 23 Jun 2025 20:22:57 -0700 Subject: [PATCH 14/24] os.path.exists --- printer/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/server.py b/printer/server.py index 784d482c..6109b35f 100644 --- a/printer/server.py +++ b/printer/server.py @@ -220,7 +220,7 @@ async def read_item( ) t.start() - if args.config_json_path: + if args.config_json_path and os.path.exists(args.config_json_path): thread = threading.Thread( target=collector.scrape_snmp, args=( From 8e268c14bbbfe64f14f1810afc260abd5b851df1 Mon Sep 17 00:00:00 2001 From: evan Date: Mon, 23 Jun 2025 20:34:12 -0700 Subject: [PATCH 15/24] RUN apt-get install -y software-properties-common && add-apt-repository ppa:deadsnakes/ppa && apt-get update && apt-get install -y python3.11 python3.11-venv python3.11-dev jq ssh # Create the virtual environment with Python 3.11 RUN python3.11 -m venv /opt/venv --- printer/Dockerfile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index bfc66313..5a8faef5 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -6,10 +6,13 @@ WORKDIR /app RUN apt-get update -RUN apt install -y python3 python3-pip python3-venv jq ssh +RUN apt-get install -y software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y python3.11 python3.11-venv python3.11-dev jq ssh -# Create a virtual environment -RUN python3 -m venv /opt/venv +# Create the virtual environment with Python 3.11 +RUN python3.11 -m venv /opt/venv # Set the virtual environment as the default Python environment ENV PATH="/opt/venv/bin:$PATH" @@ -24,7 +27,7 @@ COPY ./printer/what.sh /app/printer/what.sh COPY ./printer/*.py /app/printer/ -EXPOSE 5000 9000 +EXPOSE 9000 # The below command runs the bash script that sets up the connection to the # printers and runs PrintHandler.js From aef9517351cffa249687d8374ef2c6bf2b6a6902 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:46:07 -0700 Subject: [PATCH 16/24] changed requirements for newer snmp --- printer/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/requirements.txt b/printer/requirements.txt index 56bd4bf9..8406cd0b 100644 --- a/printer/requirements.txt +++ b/printer/requirements.txt @@ -4,5 +4,5 @@ py-grpc-prometheus==0.7.0 python-multipart==0.0.9 httpx==0.28.1 requests==2.32.3 -pysnmp==4.4.12 +pysnmp>=7.1.0,<8.0.0 pyasn1==0.4.8 From 6368e4861dd5dc5f6ad4fc735b2679e0702d2097 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:52:28 -0700 Subject: [PATCH 17/24] Reverted dockerfile --- printer/Dockerfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index 5a8faef5..b733591c 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -3,13 +3,9 @@ FROM drpsychick/airprint-bridge:latest WORKDIR /app - RUN apt-get update -RUN apt-get install -y software-properties-common \ - && add-apt-repository ppa:deadsnakes/ppa \ - && apt-get update \ - && apt-get install -y python3.11 python3.11-venv python3.11-dev jq ssh +RUN apt install -y python3 python3-pip python3-venv jq ssh # Create the virtual environment with Python 3.11 RUN python3.11 -m venv /opt/venv From dea06bd2b0429372be88f926cb455d2999afecc9 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 20:58:12 -0700 Subject: [PATCH 18/24] typos in dockerfile --- printer/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index b733591c..4826a120 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -5,10 +5,10 @@ FROM drpsychick/airprint-bridge:latest WORKDIR /app RUN apt-get update -RUN apt install -y python3 python3-pip python3-venv jq ssh +RUN apt install -y python3 python3-pip python3-venv jq sshW -# Create the virtual environment with Python 3.11 -RUN python3.11 -m venv /opt/venv +# Create the virtual environment with Python +RUN python3 -m venv /opt/venv # Set the virtual environment as the default Python environment ENV PATH="/opt/venv/bin:$PATH" From ebf1dbe261c3052627f340f7d259c589fff8b45f Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:00:11 -0700 Subject: [PATCH 19/24] more typos --- printer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index 4826a120..6d83c69a 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -5,7 +5,7 @@ FROM drpsychick/airprint-bridge:latest WORKDIR /app RUN apt-get update -RUN apt install -y python3 python3-pip python3-venv jq sshW +RUN apt install -y python3 python3-pip python3-venv jq ssh # Create the virtual environment with Python RUN python3 -m venv /opt/venv From cbbca8ceedc9d041209a57c45212cbb8c4a7c343 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:11:32 -0700 Subject: [PATCH 20/24] config parsing bug --- printer/collector.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/printer/collector.py b/printer/collector.py index fba9eb91..8af000d8 100644 --- a/printer/collector.py +++ b/printer/collector.py @@ -46,9 +46,10 @@ def fetch_ips_from_config(config_file_path): ip_list = [] for printer in printer_configs: - ip = printer_configs[printer]["IP"] - logging.info(f"Adding printer {printer} with IP {ip}") - ip_list.append(ip) + if printer.isinstance(printer_configs[printer], dict): + ip = printer_configs[printer]["IP"] + logging.info(f"Adding printer {printer} with IP {ip}") + ip_list.append(ip) return ip_list except Exception as e: From 8ec394151712e4e1c1f8b28c9e2787cbbcd2992d Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:12:48 -0700 Subject: [PATCH 21/24] isinstance fix --- printer/collector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/collector.py b/printer/collector.py index 8af000d8..53f269cf 100644 --- a/printer/collector.py +++ b/printer/collector.py @@ -46,7 +46,7 @@ def fetch_ips_from_config(config_file_path): ip_list = [] for printer in printer_configs: - if printer.isinstance(printer_configs[printer], dict): + if isinstance(printer_configs[printer], dict): ip = printer_configs[printer]["IP"] logging.info(f"Adding printer {printer} with IP {ip}") ip_list.append(ip) From 9c7bcff84a5806e1071420a00e634d0a32b42b70 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Mon, 23 Jun 2025 21:21:18 -0700 Subject: [PATCH 22/24] first implementation of pysnmp 4.x -> 7.x --- printer/collector.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/printer/collector.py b/printer/collector.py index 53f269cf..0403e1ee 100644 --- a/printer/collector.py +++ b/printer/collector.py @@ -2,8 +2,13 @@ import enum import logging import json +import asyncio -from pysnmp.hlapi import * +from pysnmp.hlapi.asyncio import ( + SnmpEngine, CommunityData, UdpTransportTarget, + ContextData, ObjectType, ObjectIdentity, + getCmd +) from metrics import MetricsHandler @@ -63,11 +68,11 @@ def scrape_snmp(ip_list, sleep_duration_minutes=5): time.sleep(sleep_duration_minutes * 60) -def get_snmp_data(ip): +async def get_snmp_data(ip): for oid in SnmpOid: with metrics_handler.snmp_request_duration.time(): errorIndication, errorStatus, errorIndex, varBinds = next( - getCmd( + await getCmd( SnmpEngine(), CommunityData("public", mpModel=0), UdpTransportTarget((ip, 161)), From 3f97bd0e920221574110f5cb5be90f31a6896050 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Thu, 3 Jul 2025 20:11:55 -0700 Subject: [PATCH 23/24] changed docker image to support python 3.10 --- printer/Dockerfile | 2 +- printer/collector.py | 10 +++------- printer/requirements.txt | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/printer/Dockerfile b/printer/Dockerfile index 6d83c69a..ae411029 100644 --- a/printer/Dockerfile +++ b/printer/Dockerfile @@ -1,6 +1,6 @@ # Base image from https://github.com/DrPsychick/docker-cups-airprint # Docker images are here https://hub.docker.com/r/drpsychick/airprint-bridge/tags -FROM drpsychick/airprint-bridge:latest +FROM drpsychick/airprint-bridge:jammy WORKDIR /app RUN apt-get update diff --git a/printer/collector.py b/printer/collector.py index 0403e1ee..117648dd 100644 --- a/printer/collector.py +++ b/printer/collector.py @@ -4,11 +4,7 @@ import json import asyncio -from pysnmp.hlapi.asyncio import ( - SnmpEngine, CommunityData, UdpTransportTarget, - ContextData, ObjectType, ObjectIdentity, - getCmd -) +from pysnmp.hlapi import * from metrics import MetricsHandler @@ -68,11 +64,11 @@ def scrape_snmp(ip_list, sleep_duration_minutes=5): time.sleep(sleep_duration_minutes * 60) -async def get_snmp_data(ip): +def get_snmp_data(ip): for oid in SnmpOid: with metrics_handler.snmp_request_duration.time(): errorIndication, errorStatus, errorIndex, varBinds = next( - await getCmd( + getCmd( SnmpEngine(), CommunityData("public", mpModel=0), UdpTransportTarget((ip, 161)), diff --git a/printer/requirements.txt b/printer/requirements.txt index 8406cd0b..56bd4bf9 100644 --- a/printer/requirements.txt +++ b/printer/requirements.txt @@ -4,5 +4,5 @@ py-grpc-prometheus==0.7.0 python-multipart==0.0.9 httpx==0.28.1 requests==2.32.3 -pysnmp>=7.1.0,<8.0.0 +pysnmp==4.4.12 pyasn1==0.4.8 From 34229b55bb8b52c07e2a1f9b4c7361256d3c97e0 Mon Sep 17 00:00:00 2001 From: epicgdog <91296067+epicgdog@users.noreply.github.com> Date: Sun, 6 Jul 2025 20:43:28 -0700 Subject: [PATCH 24/24] Update printer/server.py Co-authored-by: Evan Ugarte <36345325+evanugarte@users.noreply.github.com> --- printer/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/printer/server.py b/printer/server.py index 6109b35f..980e09c2 100644 --- a/printer/server.py +++ b/printer/server.py @@ -220,7 +220,7 @@ async def read_item( ) t.start() - if args.config_json_path and os.path.exists(args.config_json_path): + if not args.development and os.path.exists(args.config_json_path): thread = threading.Thread( target=collector.scrape_snmp, args=(