What is this?

This knowledgebase contains questions and answers about PRTG Network Monitor and network monitoring in general.

Learn more

PRTG Network Monitor

Intuitive to Use. Easy to manage.
More than 500,000 users rely on Paessler PRTG every day. Find out how you can reduce cost, increase QoS and ease planning, as well.

Free Download

Top Tags


View all Tags

Monitoring MailStore using a python script

Votes:

0

Hi everyone, I found this page for monitoring PRTG using Nagios/Icinga, and I'd love to try and get this working with PRTG.

It includes a bunch of Python scripts, and as luck would have it, PRTG has a Python sensor. Great! I dropped the files into the Python custom sensors folder and I was able to select check_mailstore.py from the dropdown when creating the sensor.

My question is, how do you specify the parameters? It does not give any examples of how to correctly structure the Additional Parameters field. Would it be like param1=value1,param2=value2,param3=value3, and so on? The python script is looking for arguments starting with two dashes, so I made the Additional Parameters string like this:

--host=mailstore.domain.com,--username=admin,--password=MAILSTOREPASS,--start=since:12H,--warning=10,--critical=10

However I am getting a PE231 error:

XML: Structural error in xml file, 1 open items. -- JSON: The returned JSON does not match the expected structure (Invalid JSON.). (code: PE231)

How do I see what the script is returning, so I can work on debugging this? I'm not really finding anything useful in the various C:\programdata\paessler\PRTG Network Monitor\ log folders.

The check_mailstore.py script is below:

#!/usr/bin/env python3

# check_mailstore.py - Nagios/Icinga plugin that checks MailStore worker results
#
# Copyright (c) 2012 - 2018 MailStore Software GmbH
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import sys
import time
import datetime
import mailstore
import argparse


def convertRange(value):
    """Convert value into timestamp expected by the API"""

    # If a range was given do some maths
    if value.startswith("since:"):
        value = value[len("since:"):]
        t = datetime.datetime(*list(time.localtime())[:6])

        # Years
        if "Y" in value:
            t -= datetime.timedelta(days=365*int(value[:value.find("Y")]))
            value = value.split("Y")[1]
            if value and value[0] == "-":
                value = value[1:]
        
        # Months
        if "m" in value:
            t -= datetime.timedelta(days=30*int(value[:value.find("m")]))
            value = value.split("m")[1]
            if value and value[0] == "-":
                value = value[1:]
               
        # Days
        if "d" in value:
            t -= datetime.timedelta(days=int(value[:value.find("d")]))
            value = value.split("d")[1]
            if value and value[0] == "T":
                value = value[1:]
        
        # Hours
        if "H" in value:
            t -= datetime.timedelta(hours=int(value[:value.find("H")]))
            value = value.split("H")[1]
            if value and value[0] == ":":
                value = value[1:]
               
        # Minutes 
        if "M" in value:
            t -= datetime.timedelta(minutes=int(value[:value.find("M")]))
            value = value.split("M")[1]
            if value and value[0] == ":":
                value = value[1:]

        # Seconds                
        if "S" in value:
            t -= datetime.timedelta(seconds=int(value[:value.find("S")]))
            
        timestamp = t.strftime("%Y-%m-%dT%H:%M:%S")
    else:
        timestamp = value

    return timestamp


def filterResults(results, machinename=None, status="succeeded"):
    """Filter results by status and machine name
  
    machinename:  Hostname of the machine where profiles have been executed
    status:       Comma seperated list of executions statuses (default: succeeded)
    """
    out = []
    
    # Iterate over all results
    for result in results["result"]:
        found = False

        # Filter results by status
        statuses = status.split(",")
        for status in statuses:
            if result["result"] == status:
                found = True
                break
            if status.startswith("#"):
                if result["result"] != status.strip("#"):
                    found = True
                    break
        if not found:
            continue
        
        # Filter by machine name
        if machinename is not None:
            if result["machineName"] != machinename:
                continue

        # Append found results to list
        out.append(result)
    return out


def main():

    cmp = lambda a, b: ((a > b) - (a < b))

    res = {"le": [0, -1],
           "ge": [0, 1],
           "lt": [-1],
           "gt": [1],
           "eq": [0]}

    # Initialize arguments parser
    parser = argparse.ArgumentParser(description="Checks the recent results of a profile in the given period.",
                                     formatter_class=argparse.RawTextHelpFormatter)

    parser.add_argument("--host", help="Hostname or IP address of MailStore Server")
    parser.add_argument("--port", default=8463, type=int, help="Port on which MailStore Server provides API access")
    parser.add_argument("--username", "-user", help="MailStore user name for accessing API")
    parser.add_argument("--password", "-pass", help="MailStore password for accessing API")
    parser.add_argument("--start", "-s", required=True, help="""Timestamp to begin the search.
Exact timestamp (%%Y-%%m-%%dT%%H:%%M:%%S) or periods (since:[%%Y-][%%m-][%%dT][%%H:][%%M:][%%S]) can be specified.

Examples: 
    2014-01-01T00:00:00  returns profiles since January 1st 2014
    since:1Y             returns profiles since 1 year ago,
    since:12H            returns profiles since 12 hours ago)
    since:12:00:00       same as above
    
Note: When using 'since:x', one month counts as 30 days""")
    parser.add_argument("--end", "-e", default=time.strftime("%Y-%m-%dT%H:%M:%S"), help="Timestamp to end search")
    parser.add_argument("--timezone", default="$Local", help="Time zone in which the API should return timestamps")
    parser.add_argument("--machinename", default=None, help="Filter results by specified machine name")
    parser.add_argument("--profileid", default=None, help="Filter results by specified profile ID")
    parser.add_argument("--filteruser", default=None, help="Filter results by specified user name")
    parser.add_argument("--status", default="succeeded", help="Filter results by specified status. Only useful for profiles.")
    parser.add_argument("--search", default="profiles", help="Filter results either by 'profiles' or 'emails' (default: profiles)")
    parser.add_argument("--compare", default="le", help="Method for comparing result against value of --warning/--critical [le, ge, eq, gt, lt]")
    parser.add_argument("--warning", "-w", required=True, type=int, help="Warning threshold")
    parser.add_argument("--critical", "-c", required=True, type=int, help="Critical threshold")
    parser.add_argument("--DEBUG", action="store_true", help="Enable debugging")
    
    v = parser.parse_args()

    # Some generic checking of argument values, that would lead 
    # to unexpected results when evaluating result of GetWorkerResults.
    if v.compare not in res.keys():
        parser.print_help()
        raise Exception("--compare argument not acceptable.")

    if v.search not in ["profiles", "emails"]:
        parser.print_help()
        raise Exception("--search argument is not in [profiles, emails].")
     
    # Convert definition of start and end ranges to API compatible timestamp
    v.start = convertRange(v.start)
    v.end = convertRange(v.end)

    # Get the worker results
    api = mailstore.server.Client(username=v.username, password=v.password, host=v.host, port=v.port,
                                  ignoreInvalidSSLCerts=True)

    results = api.GetWorkerResults(fromIncluding=v.start, toExcluding=v.end, timeZoneID=v.timezone,
                                   profileID=v.profileid, userName=v.filteruser)

    if results["statusCode"] == "succeeded":
        # Apply machine name and status filter to received worker results 
        # as API itself does not support this type of filters.
        results = filterResults(results, v.machinename, v.status)
    else:
        print("WARNING: GetWorkerResult error:" + results["error"]["message"])
        sys.exit(1)
    
    if v.DEBUG:
        for result in sorted(results, key=lambda k: k['completeTime']):
            print(result)
   
    # Determine number of found items...
    if v.search == "profiles":
        num = len(results)
    elif v.search == "emails":
        num = sum(list(map(lambda x: x["itemsArchived"], results)))
    else:
        print("UNKNOWN: invalid 'search' parameter {0} |'{0}'=-1".format(v.search))
        sys.exit(3)

    # ...and find out which state should be returned to Nagios/Icinga.
    if cmp(num, v.critical) in res[v.compare]:
        print("CRITICAL: amount of {0} not in range|'{0}'={1}".format(v.search, num))
        sys.exit(2)
        
    elif cmp(num, v.warning) in res[v.compare]:
        print("WARNING: amount of {0} not in range|'{0}'={1}".format(v.search, num))
        sys.exit(1)
    else:
        print("OK: {1} {0}|'{0}'={1}".format(v.search, num))
        sys.exit(0)
  

if __name__ == '__main__':
    main()

mailstore prtg python-script-advanced-sensor

Created on Aug 17, 2018 4:35:01 PM

Last change on Aug 20, 2018 6:43:28 AM by  Luciano Lingnau [Paessler]



5 Replies

Votes:

0

Could you post the actual output of the script using the same parameters in the command line? :)


Kind regards,
Stephan Linke, Tech Support Team

Created on Aug 20, 2018 7:12:50 AM by  Stephan Linke [Paessler Support]



Votes:

0

I don't know much about python, but I tried opening a command window at %PRTGinstalldir%\Python34 and then ran the command:

python "..\Custom Sensors\python\check_mailstore.py"

Just to see what would happen, but it came back with ImportError: No module named 'mailstore'

Do I have to include a file somewhere? I'm not really sure what I'm doing here :)

Created on Aug 20, 2018 3:50:38 PM



Votes:

0

I read up a bit on https://docs.python-guide.org/writing/structure/ and figured out I need to make a "mailstore" folder in the "python" Custom Sensors folder. Then, I extracted the files from https://help.mailstore.com/en/spe/images/c/cc/Python-api-wrapper.zip into that folder.

Then I was able to call check_mailstore.py from the command line with those arguments I specified at the beginning, but with spaces instead of commas. It returned OK: 85 profiles|'profiles'=85

Created on Aug 20, 2018 4:00:56 PM



Votes:

0

It keeps returning this in the Custom Sensor log file:

8/20/2018 4:21:12 PM Script File: check_mailstore.py
8/20/2018 4:21:12 PM Script Parameters: {"accesskey":"","addomainpass":"","authtoken":"","authuserpwd":"","awssk":"","blockedsens":"","canlinux":"0","dbpassword":"","elevationpass":"","esxpassword":"","evapassword":"","fastcount":"0","ftppassword":"","host":"papa.diamond.trucks","hostv6":"","httppassword":"","imappassword":"","inerror":"1","ipversion":"0","isexesensor":"0","lastmsg":"#Y2 @#O233 @#O231[Invalid JSON.]","lastuptime":"0","linuxloginpassword":"","mailpassword":"","mutexname":"","notonpod":"0","oauthrefreshtoken":"","oauthtoken":"","params":"--host=mailstore --username=admin --password=PASSWORD --start=since:12H --warning=10 --critical=10","password":"","pingdompassword":"","pop3password":"","privatekey":"","proxypass":"","proxypassword":"","prtg_linuxpassword":"","prtg_windowspassword":"","pythonscript":"check_mailstore.py","reboot":"43331.6775262268","reqmsginterval":"45","restpassword":"","resttoken":"","secret":"","secretaccesskeyid":"","sensorid":"3823","simulate":"0","smspassword":"","smtppassword":"","smtppassword2":"","snmpauthpass":"","snmpcommv1":"","snmpcommv2":"","snmpencpass":"","socksproxypass":"","sshelevationpass":"","timeout":"44","tlsexplicit_default":"","tlsexplicit_ftp":"","tlsexplicit_imap":"","tlsexplicit_pop3":"","tlsexplicit_port":"","tlsexplicit_smtp":"","uptimecount":"0","usednstime":"0","usewindowsauthentication":"0","windowsloginpassword":"","writeresult":"1","xmlhttppassword":""}
8/20/2018 4:21:12 PM Script Parameters (escaped): {\"accesskey\":\"\",\"addomainpass\":\"\",\"authtoken\":\"\",\"authuserpwd\":\"\",\"awssk\":\"\",\"blockedsens\":\"\",\"canlinux\":\"0\",\"dbpassword\":\"\",\"elevationpass\":\"\",\"esxpassword\":\"\",\"evapassword\":\"\",\"fastcount\":\"0\",\"ftppassword\":\"\",\"host\":\"mailstore.domain.com\",\"hostv6\":\"\",\"httppassword\":\"\",\"imappassword\":\"\",\"inerror\":\"1\",\"ipversion\":\"0\",\"isexesensor\":\"0\",\"lastmsg\":\"#Y2 @#O233 @#O231[Invalid JSON.]\",\"lastuptime\":\"0\",\"linuxloginpassword\":\"\",\"mailpassword\":\"\",\"mutexname\":\"\",\"notonpod\":\"0\",\"oauthrefreshtoken\":\"\",\"oauthtoken\":\"\",\"params\":\"--host=papa --username=admin --password=PASSWORD --start=since:12H --warning=10 --critical=10\",\"password\":\"\",\"pingdompassword\":\"\",\"pop3password\":\"\",\"privatekey\":\"\",\"proxypass\":\"\",\"proxypassword\":\"\",\"prtg_linuxpassword\":\"\",\"prtg_windowspassword\":\"\",\"pythonscript\":\"check_mailstore.py\",\"reboot\":\"43331.6775262268\",\"reqmsginterval\":\"45\",\"restpassword\":\"\",\"resttoken\":\"\",\"secret\":\"\",\"secretaccesskeyid\":\"\",\"sensorid\":\"3823\",\"simulate\":\"0\",\"smspassword\":\"\",\"smtppassword\":\"\",\"smtppassword2\":\"\",\"snmpauthpass\":\"\",\"snmpcommv1\":\"\",\"snmpcommv2\":\"\",\"snmpencpass\":\"\",\"socksproxypass\":\"\",\"sshelevationpass\":\"\",\"timeout\":\"44\",\"tlsexplicit_default\":\"\",\"tlsexplicit_ftp\":\"\",\"tlsexplicit_imap\":\"\",\"tlsexplicit_pop3\":\"\",\"tlsexplicit_port\":\"\",\"tlsexplicit_smtp\":\"\",\"uptimecount\":\"0\",\"usednstime\":\"0\",\"usewindowsauthentication\":\"0\",\"windowsloginpassword\":\"\",\"writeresult\":\"1\",\"xmlhttppassword\":\"\"}
8/20/2018 4:21:12 PM Script Path: C:\Program Files (x86)\PRTG Network Monitor\custom sensors\python\check_mailstore.py
8/20/2018 4:21:12 PM Command Line: "C:\Program Files (x86)\PRTG Network Monitor\Python34\python.exe" -E "C:\Program Files (x86)\PRTG Network Monitor\custom sensors\python\check_mailstore.py" "{\"accesskey\":\"\",\"addomainpass\":\"\",\"authtoken\":\"\",\"authuserpwd\":\"\",\"awssk\":\"\",\"blockedsens\":\"\",\"canlinux\":\"0\",\"dbpassword\":\"\",\"elevationpass\":\"\",\"esxpassword\":\"\",\"evapassword\":\"\",\"fastcount\":\"0\",\"ftppassword\":\"\",\"host\":\"papa.diamond.trucks\",\"hostv6\":\"\",\"httppassword\":\"\",\"imappassword\":\"\",\"inerror\":\"1\",\"ipversion\":\"0\",\"isexesensor\":\"0\",\"lastmsg\":\"#Y2 @#O233 @#O231[Invalid JSON.]\",\"lastuptime\":\"0\",\"linuxloginpassword\":\"\",\"mailpassword\":\"\",\"mutexname\":\"\",\"notonpod\":\"0\",\"oauthrefreshtoken\":\"\",\"oauthtoken\":\"\",\"params\":\"--host=mailstore --username=admin --password=PASSWORD --start=since:12H --warning=10 --critical=10\",\"password\":\"\",\"pingdompassword\":\"\",\"pop3password\":\"\",\"privatekey\":\"\",\"proxypass\":\"\",\"proxypassword\":\"\",\"prtg_linuxpassword\":\"\",\"prtg_windowspassword\":\"\",\"pythonscript\":\"check_mailstore.py\",\"reboot\":\"43331.6775262268\",\"reqmsginterval\":\"45\",\"restpassword\":\"\",\"resttoken\":\"\",\"secret\":\"\",\"secretaccesskeyid\":\"\",\"sensorid\":\"3823\",\"simulate\":\"0\",\"smspassword\":\"\",\"smtppassword\":\"\",\"smtppassword2\":\"\",\"snmpauthpass\":\"\",\"snmpcommv1\":\"\",\"snmpcommv2\":\"\",\"snmpencpass\":\"\",\"socksproxypass\":\"\",\"sshelevationpass\":\"\",\"timeout\":\"44\",\"tlsexplicit_default\":\"\",\"tlsexplicit_ftp\":\"\",\"tlsexplicit_imap\":\"\",\"tlsexplicit_pop3\":\"\",\"tlsexplicit_port\":\"\",\"tlsexplicit_smtp\":\"\",\"uptimecount\":\"0\",\"usednstime\":\"0\",\"usewindowsauthentication\":\"0\",\"windowsloginpassword\":\"\",\"writeresult\":\"1\",\"xmlhttppassword\":\"\"}"
8/20/2018 4:21:12 PM Script Output (OEMCP Encoding): usage: check_mailstore.py [-h] [--host HOST] [--port PORT][CR][LF]                          [--username USERNAME] [--password PASSWORD] --start[CR][LF]                          START [--end END] [--timezone TIMEZONE][CR][LF]                          [--machinename MACHINENAME] [--profileid PROFILEID][CR][LF]                          [--filteruser FILTERUSER] [--status STATUS][CR][LF]                          [--search SEARCH] [--compare COMPARE] --warning[CR][LF]                          WARNING --critical CRITICAL [--DEBUG][CR][LF]check_mailstore.py: error: the following arguments are required: --start/-s, --warning/-w, --critical/-c[CR][LF]
8/20/2018 4:21:12 PM Exit Code: 2
8/20/2018 4:21:12 PM RawStream Size: 602
8/20/2018 4:21:12 PM OutputStream Size: 602
8/20/2018 4:21:12 PM Script Output (UTF8 Encoding): usage: check_mailstore.py [-h] [--host HOST] [--port PORT][CR][LF]                          [--username USERNAME] [--password PASSWORD] --start[CR][LF]                          START [--end END] [--timezone TIMEZONE][CR][LF]                          [--machinename MACHINENAME] [--profileid PROFILEID][CR][LF]                          [--filteruser FILTERUSER] [--status STATUS][CR][LF]                          [--search SEARCH] [--compare COMPARE] --warning[CR][LF]                          WARNING --critical CRITICAL [--DEBUG][CR][LF]check_mailstore.py: error: the following arguments are required: --start/-s, --warning/-w, --critical/-c[CR][LF]

Basically it's saying we aren't passing any parameters in PRTG. I changed the Additional Parameters field in the PRTG Sensor to replace the commas with spaces, like it would be in the command line, and still get the same message.

I did read that PRTG won't play nice if Python uses the "print" function. So in check_mailstore.py I changed the last few lines:

    else:
        #instead of printing, lets dump json
        mailstorestatus = "OK"
        json.dumps(mailstorestatus)
        return mailstorestatus
        #print("OK: {1} {0}|'{0}'={1}".format(v.search, num))
        sys.exit(0)
  

if __name__ == '__main__':
    main()

and added import json at the top of the script.

When I run it in the command line I'm not getting any errors, just a blank line and then back to the prompt, so I "think" it's working properly from the command line. Issue is the parameters aren't passing correctly I think.

Created on Aug 20, 2018 4:26:51 PM



Votes:

0

Okay, let's see :) Since mailstore is missing, is there any client that you can install for mailstore that may contain the Python module? Other than this, it's a nagios monitoring script which you would need to modify anyway. Perhaps going PowerShell right away would be the better option:

https://help.mailstore.com/de/server/PowerShell_API-Wrapper_Tutorial



The guide for custom Sensors should also help in getting you started :)


Kind regards,
Stephan Linke, Tech Support Team

Created on Aug 21, 2018 6:03:27 AM by  Stephan Linke [Paessler Support]




Disclaimer: The information in the Paessler Knowledge Base comes without warranty of any kind. Use at your own risk. Before applying any instructions please exercise proper system administrator housekeeping. You must make sure that a proper backup of all your data is available.