Search code examples
pythonpickle

Python pickly ListIndexOut of Range


want to create a python script which checks if a server on a server is up or not. Rather if a server is up or not I want to catch that result in a file. In the end if a server is down then I want to receive an email.

I use the tutorial https://linuxhint.com/python-server-monitoring-script/ to start creating the python file with the below result.

When I execute the python file I get the below error. I not really familiar with working classes. So I hope you can help me out here.

When execute the below code i get the error you see below. And only the first server is checked. Can you please help me ?

PREVIOUS RESULT

$ C:/Users/.../AppData/Local/Programs/Python/Python38/python.exe c:/Users/.../Documents/git/CV%20Reader%20FastAPI/CheckServer.py
1
('MyServernamae1 is up. On Port 80 with plain', True, <built-in method now of type object at 0x00007FFB92A8B530>)
0
Traceback (most recent call last):
  File "c:/Users/.../Documents/git/CV%20Reader%20FastAPI/CheckServer.py", line 84, in <module>
    print(server.history[-1])
IndexError: list index out of range

CURRENT RESULT Based on the given solution of 'Kite' i update the code and get the following result. I get for some of the servers result and some are skipped. I expect for every server a result.

AzureAD+....@ToolingDesktop MINGW64 ~/Documents/git/CV%20Reader%20FastAPI (master)
$ C:/Users/.../AppData/Local/Programs/Python/Python38/python.exe c:/Users/.../Documents/git/CV%20Reader%20FastAPI/CheckServer.py
5
('185.104.29.80 is up. On Port 80 with plain', True, datetime.datetime(2021, 10, 17, 16, 9, 31, 955477))
3
('No Clue??: [WinError 10061] No connection could be made because the target machine actively refused it', False, datetime.datetime(2021, 10, 17, 16, 9, 31, 985540))

Expected Result

I expect for every server a result (good or bad)!

This is the result from the https://linuxhint.com/python-server-monitoring-script/ site. I expect from the a response if the server is up or not.

enter image description here

CODE UPDATE (BASED ON GIVEN SOLUTION)

  from os import system
import socket
import ssl
import pickle
from datetime import datetime, time
import subprocess
import platform

class Server():
    def __init__(self, name, port, connection, priority):
        self.name = name
        self.port = port
        self.connection = connection
        self.priority = priority

        self.history = []
        self.alert =  False

    def checkConnection(self):
        msg = ""
        succes = False
        now = datetime.now()

        try:
            if self.connection == "plain":
                socket.create_connection((self.name,self.port),timeout=10)
                msg = f"{self.name} is up. On Port {self.port} with {self.connection}"
                succes = True
                self.alert = False
            elif self.connection == "ssl":
                ssl.wrap_socket(socket.create_connection((self.name,self.port),timeout=10))
                msg = f"{self.name} is up. On Port {self.port} with {self.connection}"
                succes = True
                self.alert = False
            else:
                if self.ping():
                    msg = f"{self.name} Timout on Port {self.port} with {self.connection}"
                    succes = True
                    self.alert = False

        except socket.timeout:
            msg = f"{self.name} is Down. On Port {self.port}"
            succes = False
            alert = True

        except Exception as e:
            msg = f"No Clue??: {e}"

        if succes == False and self.alert == False:
            # Send Alert
            self.alert = True
            # email_alert(self.name,f"{msg}\n{now}","[email protected]")
        self.create_history(msg,succes,now)

    def ping(self):
        try:
            output = subprocess.check_output("ping - {} 1 {}".format('n' if platform.system.lower() == 'windowns' else 'c', self.name), shell = True, universal_newlines = True)
            if 'unreachable'  in output:
                return False

            else:
                return True

        except Exception:
            return False

    def create_history(self,msg,succes,now):
        history_max = 100
        self.history.append((msg,succes,now))

        while len(self.history) > history_max:
            self.history.pop(0)

if __name__ == "__main__":
    try:
        servers = pickle.load(open("servers.pickle","rb"))
    except:
        servers = [
            Server("185.104.29.80",80,"plain","high"),
            Server("34.91.11.178",5001,"plain","high"),
            Server("reddit.com",80,"plain","high"),
            Server("msn.com",80,"plain","high"),
            Server("smtp.gmail.com",465,"ssl","high")
        ]
    for server in servers:
        server.checkConnection()
        print(len(server.history))
        print(server.history[-1])

    pickle.dump(servers,open("servers.pickle","wb"))

Solution

  • There are few issues with your code, I thought you had followed the tutorial as is but missed few minute things like below.

    Firstly, the loading from pickle is not done properly. As it is with try/except block, it doesn't throw any error and it is the main issue.

    #this is wrong
    servers.pickle.load(open("servers.pickle","rb"))
    #it should be like below
    servers = pickle.load(open("servers.pickle","rb"))
    

    Secondly, if you want the output to have visible datetime

    #this just says as some object
    now = datetime.now
    #this gives the proper output
    now = datetime.now()
    

    Thirdly, why did you combine the email alert logic with other if/elif blocks only in try block. It should be added at the end of try/except blocks. (Logical issue)

    Fourthly, This is what makes the list index out of range issue. In the checkConnection() last except block just says pass, which is very bad and if anything hits there, nothing is added in history too. So, I added the self.create_history() call as common for all try/except blocks and updated the last except to catch the other exceptions like Connection refused, Temporary failure in name resolution etc., instead of just pass and this adds something or the other in the history.

    Please find my updated code below.

    from os import system
    import socket
    import ssl
    import pickle
    from datetime import datetime, time
    import subprocess
    import platform
    
    class Server():
        def __init__(self, name, port, connection, priority):
            self.name = name
            self.port = port
            self.connection = connection
            self.priority = priority
    
            self.history = []
            self.alert =  False
    
        def checkConnection(self):
            msg = ""
            succes = False
            now = datetime.now()
    
            try:
                if self.connection == "plain":
                    socket.create_connection((self.name,self.port),timeout=10)
                    msg = f"{self.name} is up. On Port {self.port} with {self.connection}"
                    succes = True
                    self.alert = False
                elif self.connection == "ssl":
                    ssl.wrap_socket(socket.create_connection((self.name,self.port),timeout=10))
                    msg = f"{self.name} is up. On Port {self.port} with {self.connection}"
                    succes = True
                    self.alert = False
                else:
                    if self.ping():
                        msg = f"{self.name} Timout on Port {self.port} with {self.connection}"
                        succes = True
                        self.alert = False
    
            except socket.timeout:
                msg = f"{self.name} is Down. On Port {self.port}"
                succes = False
                alert = True
    
            except Exception as e:
                msg = f"No Clue??: {e}"
    
            if succes == False and self.alert == False:
                # Send Alert
                self.alert = True
                # email_alert(self.name,f"{msg}\n{now}","[email protected]")
            self.create_history(msg,succes,now)
    
        def ping(self):
            try:
                output = subprocess.check_output("ping - {} 1 {}".format('n' if platform.system.lower() == 'windowns' else 'c', self.name), shell = True, universal_newlines = True)
                if 'unreachable'  in output:
                    return False
    
                else:
                    return True
    
            except Exception:
                return False
    
        def create_history(self,msg,succes,now):
            history_max = 100
            self.history.append((msg,succes,now))
    
            while len(self.history) > history_max:
                self.history.pop(0)
    
    if __name__ == "__main__":
        try:
            servers = pickle.load(open("servers.pickle","rb"))
        except:
            servers = [
                Server("185.104.29.80",80,"plain","high"),
                Server("34.91.11.178",5001,"plain","high"),
                Server("reddit.com",80,"plain","high"),
                Server("msn.com",80,"plain","high"),
                Server("smtp.gmail.com",465,"ssl","high")
            ]
        for server in servers:
            server.checkConnection()
            print(len(server.history))
            print(server.history[-1])
    
        pickle.dump(servers,open("servers.pickle","wb"))
    

    After fixing these above issues, you will have the output like below:

    1
    ('185.104.29.80 is up. On Port 80 with plain', True, datetime.datetime(2021, 10, 17, 15, 3, 47, 153334))
    1
    ('No Clue??: [Errno 111] Connection refused', False, datetime.datetime(2021, 10, 17, 15, 3, 47, 425893))
    1
    ('reddit.com is up. On Port 80 with plain', True, datetime.datetime(2021, 10, 17, 15, 3, 51, 216740))
    1
    ('msn.com is up. On Port 80 with plain', True, datetime.datetime(2021, 10, 17, 15, 3, 51, 342316))
    1
    ('smtp.gmail.com is up. On Port 465 with ssl', True, datetime.datetime(2021, 10, 17, 15, 3, 51, 670267))