Skip to content

Commit

Permalink
Merge pull request #6 from mraspaud/feature-op5-tests
Browse files Browse the repository at this point in the history
Add tests for op5 handler and use requests
  • Loading branch information
mraspaud committed Jul 18, 2022
2 parents 8322d4a + 08bb35e commit fb7b584
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 98 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
fail-fast: true
matrix:
os: ["ubuntu-latest", "macos-latest"]
python-version: ["3.7", "3.8", "3.9"]
python-version: ["3.8", "3.9", "3.10"]
experimental: [false]
include:
- python-version: "3.9"
- python-version: "3.10"
os: "ubuntu-latest"
experimental: true

Expand Down
2 changes: 2 additions & 0 deletions continuous_integration/environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dependencies:
- fsspec
- appdirs
- setuptools_scm
- requests
- requests-mock
- pip
- pip:
- trollsift
Expand Down
2 changes: 1 addition & 1 deletion examples/log_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ handlers:
stream: ext://sys.stdout
monitor:
(): pytroll_monitor.op5_logger.AsyncOP5Handler
auth: ['mylogin', 'securepassword']
service: that_stuff_we_should_monitor
server: monitoring_server.myworkplace.com
host: server_we_run_stuff_on
auth: ['mylogin', 'securepassword']
root:
level: DEBUG
handlers: [console, monitor]
40 changes: 16 additions & 24 deletions pytroll_monitor/monitor_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,15 @@

LOG = logging.getLogger(__name__)

MODE = os.getenv("SMHI_MODE")
if MODE is None:
MODE = "offline"


class OP5Monitor(object):
"""A class to trigger the sending of a notifcation to the SMHI monitor server."""
"""A class to trigger the sending of a notification to an Op5 monitor server."""

def __init__(self, monitor_auth, monitor_service, monitor_server, monitor_host):
def __init__(self, monitor_service, monitor_server, monitor_host, monitor_auth=None):
"""Init the monitor."""
# __init__ is not run when created from yaml
# See http://pyyaml.org/ticket/48
self.monitor_auth = monitor_auth
if self.monitor_auth:
self.monitor_auth = tuple(monitor_auth)
self.monitor_service = monitor_service
self.monitor_server = monitor_server
self.monitor_host = monitor_host
Expand All @@ -57,7 +53,9 @@ def __getstate__(self):

def __setstate__(self, mydict):
"""Set state."""
self.monitor_auth = mydict['monitor_auth']
monitor_auth = mydict.get('monitor_auth')
if monitor_auth:
self.monitor_auth = tuple(monitor_auth)
self.monitor_service = mydict['monitor_service']
self.monitor_server = mydict['monitor_server']
self.monitor_host = mydict['monitor_host']
Expand All @@ -74,18 +72,12 @@ def __call__(self, status, msg):

def send_message(self, status, msg):
"""Send the message to the monitor server."""
api_command = urljoin(self.monitor_server, '/api/command/PROCESS_SERVICE_CHECK_RESULT')
# LOG.debug("API command: <%s>", api_command)
# LOG.debug("monitor host: %s", self.monitor_host)
# LOG.debug("monitor service: %s", self.monitor_service)
# LOG.debug("status code: %d", status)
# LOG.debug("Message: %s", msg)
jsondata = json.dumps({"host_name": self.monitor_host,
"service_description": self.monitor_service,
"status_code": status,
"plugin_output": msg})
retv = requests.post(api_command,
headers={'content-type': 'application/json'},
auth=tuple(self.monitor_auth),
data=jsondata)
return retv
json_data = {"host_name": self.monitor_host,
"service_description": self.monitor_service,
"status_code": status,
"plugin_output": msg}
with requests.post(self.monitor_server,
auth=self.monitor_auth,
json=json_data) as response:
response.raise_for_status()
return response
84 changes: 15 additions & 69 deletions pytroll_monitor/op5_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,94 +22,40 @@
"""A logger sending statuses to monitor."""

import logging
from six.moves.urllib.parse import urlparse
import json
from threading import Thread
import sys
from socket import gaierror
from queue import Queue
from socket import gaierror
from threading import Thread

logger = logging.getLogger(__name__)


class RequestOP5Handler(logging.Handler):
"""Monitoring handler."""

def __init__(self, auth, service, server, host):
"""Init the handler."""
from pytroll_monitor.monitor_hook import OP5Monitor
logging.StreamHandler.__init__(self)
self.auth = auth
self.service = service
self.server = server
self.host = host
self.monitor = OP5Monitor(auth, service, server, host)
from pytroll_monitor.monitor_hook import OP5Monitor

def emit(self, record):
"""Emit the message."""
if record.levelno < logging.INFO:
return
if record.levelno >= logging.INFO:
status = 0
if record.levelno >= logging.WARNING:
status = 1
if record.levelno >= logging.ERROR:
status = 2
self.monitor.send_message(status, self.format(record))
logger = logging.getLogger(__name__)


class OP5Handler(logging.Handler):
"""Monitoring handler."""

def __init__(self, auth, service, server, host):
def __init__(self, service, server, host, auth=None):
"""Init the handler."""
logging.Handler.__init__(self)
super().__init__()

self.auth = tuple(auth)
self.service = service
self.host = host
self.server = server
self.monitor = OP5Monitor(service, server, host, auth)

def emit(self, record):
"""Emit a record."""
if record.levelno < logging.INFO:
return
if record.levelno >= logging.INFO:
status = 0
if record.levelno >= logging.WARNING:
status = 1
if record.levelno >= logging.ERROR:
status = 2
jsondata = json.dumps({"host_name": self.host,
"service_description": self.service,
"status_code": status,
"plugin_output": self.format(record)})

elif record.levelno >= logging.WARNING:
status = 1
elif record.levelno >= logging.INFO:
status = 0
else:
return
try:
import http.client
url = urlparse(self.server)
if url.scheme == 'https':
h = http.client.HTTPSConnection(url.netloc)
elif url.scheme == "http":
h = http.client.HTTPConnection(url.netloc)
else:
raise NotImplementedError(
"Can't create an OP5 logger with scheme {}".format(
url.scheme))
h.putrequest('POST', url.path)
h.putheader("Content-type",
"application/json")
h.putheader("Content-length", str(len(jsondata)))
if self.auth:
import base64
s = ('%s:%s' % self.auth).encode('utf-8')
s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
h.putheader('Authorization', s)
h.endheaders()
h.send(jsondata.encode('utf-8'))
h.getresponse() # nothing to do with the result
self.monitor.send_message(status, self.format(record))
except gaierror:
sys.stderr.write("Can't reach %s !\n" % url.netloc)
sys.stderr.write("Can't reach %s !\n" % self.server)
self.handleError(record)
except Exception:
self.handleError(record)
Expand Down
79 changes: 79 additions & 0 deletions pytroll_monitor/tests/test_op5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pytroll_monitor.monitor_hook import OP5Monitor
from pytroll_monitor.op5_logger import OP5Handler
import yaml
import logging
import logging.config
import requests_mock


yaml_config = """version: 1
disable_existing_loggers: false
formatters:
pytroll:
format: '[%(asctime)s %(levelname)-8s %(name)s] %(message)s'
handlers:
monitor:
(): pytroll_monitor.op5_logger.OP5Handler
auth: ['username', 'password']
service: that_service_we_should_monitor
server: http://myop5server.com/some/service
host: server_we_run_stuff_on
root:
level: DEBUG
handlers: [monitor]
"""


class TestOp5Interfaces:
"""Test the Op5 monitor and handler."""
def setup(self):
self.service = "that_service_we_should_monitor"
self.server = "http://myop5server.com/some/service"
self.auth = ("username", "password")
self.host = "server_we_run_stuff_on"
self.message = "I didn't expect a kind of Spanish Inquisition."
self.response_text = "Nobody expects the Spanish Inquisition!"
self.status = 1

self.expected_json = {"host_name": self.host,
"service_description": self.service,
"status_code": self.status,
"plugin_output": self.message}
self.expected_auth = 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='

def test_op5monitor_without_auth(self):
"""Test the monitor without authentication."""

op5m = OP5Monitor(self.service, self.server, self.host, monitor_auth=None)

with requests_mock.Mocker() as m:
m.post(self.server, text=self.response_text)
op5m.send_message(self.status, self.message)
assert m.last_request.json() == self.expected_json
assert m.last_request.url == self.server
assert m.last_request.method == "POST"
assert "Authorization" not in m.last_request.headers

def test_op5monitor_with_auth(self):
"""Test the monitor with authentication."""
op5m = OP5Monitor(self.service, self.server, self.host, self.auth)

with requests_mock.Mocker() as m:
m.post(self.server, text=self.response_text)
op5m.send_message(self.status, self.message)
assert m.last_request.headers["Authorization"] == self.expected_auth

def test_op5handler(self):
logger = logging.getLogger("test_loggin")

log_dict = yaml.safe_load(yaml_config)
logging.config.dictConfig(log_dict)

with requests_mock.Mocker() as m:
m.post(self.server, text=self.response_text)
logger.warning(self.message)
assert m.last_request.json() == self.expected_json
assert m.last_request.headers["Authorization"] == self.expected_auth
3 changes: 1 addition & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[install]
prefix=/usr/local


[bdist_rpm]
provides=pytroll-monitor
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@
zip_safe=False,
scripts=[],
data_files=[],
extras_require={
"Op5": ["requests"],
}
)

0 comments on commit fb7b584

Please sign in to comment.