2

I have a code that will connect to several routers, using the telentlib, run some code within those, and finally write the output to a file. It runs smoothly.

However, when I had to access lots of routers (+50) the task consumes a lot of time (the code runs serially, one router at a time). I thought then on implementing threads in order to accelerate the process.

This is pretty much the code (just a snippet of it):

# We import the expect library for python
import telnetlib
import sys
import csv
import time
import threading

# --- Timers
TimeLogin = 10
TelnetWriteTimeout = 1
TelnetReadTimeout = 2
# --- CSV
FileCsv = "/home/lucas/Documents/script/file.csv"
# --- Extras
cr="\n"

# variables
IP = "A.B.C.D"
User = ["user","password"]
CliLogin = "telnet " + IP
Prompt = ["root@host.*]#"]
PromptLogin = ["login:"]
PromptLogout = ["logout"]
PromptPass = ["Password:"]
CliLine = "ls -l"

class MiThread(threading.Thread):  
    def __init__(self,num,datos):  
        threading.Thread.__init__(self)
        self.num = num
        self.datos = datos
        self.systemIP = self.datos[0]
        self.tn = telnetlib.Telnet(IP)
        self.tn.timeout = TimeLogin

        # File declaration
        self.FileOutGen = self.systemIP + "_commands"
        self.FileOutSend = self.systemIP + "_output"
        self.FileOutRx = self.systemIP + "_rx"      
        self.fg = open(self.FileOutGen,"w")
        self.fr = open(self.FileOutRx,"a")
        self.fs = open(self.FileOutSend,"w")

    def run(self):  
        print "Soy el hilo", self.num
        self.telnetLogin()
        self.runLs()
        self.telnetLogout()

    def telnetLogin(self):      
        i=self.tn.expect(PromptLogin)
        print i
        if i:
            writeTelnet(User[0],TelnetWriteTimeout)
            j=self.tn.expect(PromptPass)
            print j
            if j:
                writeTelnet(User[1],TelnetWriteTimeout)

    def telnetLogout(self):
        i=self.tn.expect(Prompt)
        if i:
            writeTelnet("exit",TelnetWriteTimeout)
            j=self.tn.expect(PromptLogout)
            if j:
                print "Logged out OK from SAM"

    def runLs(self):
        writeLog("Prueba de Ls " + self.systemIP)
        self.writeCsv(self.systemIP,1)
        i=self.tn.expect(Prompt,TimeLogin)
        print i
        if i:
            # Prompt
            CliLine = "ls -l "
            writeTelnet(CliLine,TelnetWriteTimeout)

    def writeCsv(self,inText,lastIn):
        lock.acquire(1)
        if lastIn==0:
            fc.write(inText + ",")
        elif lastIn==1:
            fc.write(inText + "\n")
        lock.release()

def writeTelnet(inText, timer):
    tn.write(inText + cr)
    time.sleep(timer)

def writeLog(inText):
    print (inText)
    t.fs.write("\n" + inText)

def printConsole(inText):
    print (inText)

oFile = csv.reader(open(FileCsv,"r"), delimiter=",", quotechar="|")

lock = threading.Lock()
routers = list(oFile)
threads_list = []

for i in range(len(routers)):
    FileOutCsv = "00_log.csv"

    # creating output file
    fc = open(FileOutCsv,"a")

    # running routine
    t = MiThread(i,routers[i])
    threads_list.append(t)
    t.start()

... everything runs nicely, but there is no gain in time since the t.join() will force a thread to complete before running the next one!

The fact of serializing the threads (meaning, using the t.join()) makes me think that some memory spaces are being shared because the problem arises when I do want to parallelize them (commenting out the t.join()).

Is there anything wrong that I'm doing? I can provide more information if needed but I really do not know what is it that I'm doing wrong so far ...

Lucas Aimaretto
  • 1,399
  • 1
  • 22
  • 34
  • looks like your threads are blocking each other when running in parallel. – Ashalynd Jan 05 '15 at 21:13
  • I guess you are using some global variables or telnet lib use global variables. You can try to use multiprocess instead of multithreaing... The interface is the same. – Michele d'Amico Jan 05 '15 at 23:33
  • Hi @Michele, yes, you're right: I'm using some global variables (ie: the username/password for login in), so I thought there was no problem in using them. Why is there a correlation between parallelizing these connections and global variables? In any case I'll try multiprocessing instead of multithreading. Question: I'm testing this code in Linux. If I do use MP, will it be easily ported to windows? – Lucas Aimaretto Jan 06 '15 at 11:51
  • Hi @Ashalynd, yes, I think they're blocking each other out. Any hint on how to prevent this? – Lucas Aimaretto Jan 06 '15 at 11:52
  • Hi @Micheled'Amico, before moving on with MP, I'd rather stay with MT. In order to avoid this problem, should I place all the global variables inside the class definition? I think it might work but I do not see it as such an elegant approach ... – Lucas Aimaretto Jan 06 '15 at 12:50
  • @LucasAimaretto too much questions ...:) First of all if you just read global variables without modify (i.e. by `global` statement) that cannot be the cause. Anyway try multiprocess is simple https://docs.python.org/2/library/multiprocessing.html and you can port it to windows without any effort. If the problem is in the telnet lib (for instance a GIL issue https://wiki.python.org/moin/GlobalInterpreterLock) multiprocess will fix it. My best guess is that the issue is in the code that you didn't post and multiprocess can be a overkill approach but it can be useful to have working code. – Michele d'Amico Jan 06 '15 at 13:25
  • @LucasAimaretto One more thing. Yes you can place global variables in class definition or, better, pass it in the constructor. But maybe that is not the root cause: if you put more code about your method maybe is simpler guess the cause: your posted example work without any issue and doesn't give any information about where the issue can be. Finally i found an example where telnetlib working in threading context http://stackoverflow.com/a/18378683/4101725 and now I'm quite sure that your issue in not posted code. – Michele d'Amico Jan 06 '15 at 13:35
  • @Micheled'Amico, ok so I think I've found the error. I've written a function (which is not included in the snippet) that would write into the telnet, that was not declared inside the class but only outside of it. Each thread, when wanting to write into its telnet connection would try to use it. Once I moved the function inside the class, everything worked fine so far. Thanks for your tips! They made me think 'bout the memory that's being shared. Question: If I want to share with you all the complete code of my program, should I modify the original post? Or should I start a new one? – Lucas Aimaretto Jan 06 '15 at 14:15
  • @LucasAimaretto I think the best is modify it: the title is correct, you just forgot to put relevant part of code. – Michele d'Amico Jan 06 '15 at 14:18
  • @LucasAimaretto Is better if you file a answer with a response instead of use *SOLVED* in the question. I'll upvoted it. – Michele d'Amico Jan 06 '15 at 15:43
  • @Micheled'Amico, got it, but, forgive me for the query: I tried to edit the post and found no way of uploading a file with the response including the solution. Which is the appropriate way of uploading files? – Lucas Aimaretto Jan 07 '15 at 12:55
  • @LucasAimaretto You should not post file. Just write the original wrong code (better if you use a simplified version of it) and then post the answer where you fix your code and explain where is the issue. – Michele d'Amico Jan 07 '15 at 13:15

3 Answers3

2

So after some digging, the error was found.

Before, the function writeTelnet() was declared outside the class. Once moved inside of it and properly being referenced by the rest (ie: self.writeTelnet()) everything worked so far as expected.

Here's the snippet of the new code:

class MiThread(threading.Thread):  
    def __init__(self,num,datos):  
        threading.Thread.__init__(self)
        self.num = num
        self.datos = datos
        self.systemIP = self.datos[0]
        self.tn = telnetlib.Telnet(IP)
        self.tn.timeout = TimeLogin

        # File declaration
        self.FileOutGen = self.systemIP + "_commands"
        self.FileOutSend = self.systemIP + "_output"
        self.FileOutRx = self.systemIP + "_rx"      
        self.fg = open(self.FileOutGen,"w")
        self.fr = open(self.FileOutRx,"a")
        self.fs = open(self.FileOutSend,"w")

    [ . . . ]

    def writeTelnet(self,inText, timer):
        self.tn.write(inText + ch_cr)
        time.sleep(timer)

oFile = csv.reader(open(FileCsv,"r"), delimiter=",", quotechar="|")
routers = list(oFile)

for i in range(len(routers)):   
    # creating output file
    fc = open(FileOutCsv,"a")

    # running routine
    t = MiThread(i,routers[i])
    t.start()

And it's now clear (at least to me): Since the different threads need at some point to write into their telnet's connection, they need unambiguously to identify it. The only way that I've found to do it is to include the function inside the class.

Thank you all,

Lucas

Lucas Aimaretto
  • 1,399
  • 1
  • 22
  • 34
1

From past experience, you need to start all of the threads and have them join after they have all started.

thread_list = []
for i in range(routers):
    t = MiThread(i, routers[i])
    threadlist.append(t)
    t.start()

for i in thread_list:
    i.join()

This will start every thread and then wait for all threads to complete before moving on.

  • Hi @nerdyKnight, I've tried your suggestion and it does not solve the issue. Hangs for ever again. Indeed, looks to me that starting all the threads at once is the problem, because if I do place a t.join() after the thread has started, the issue goes away... – Lucas Aimaretto Jan 06 '15 at 12:30
  • I think you can bunch them up. – Abhishek Dave Jan 07 '15 at 17:46
0

I think you can bunch them up. something like below. (not tried)

count=20
start=1
for i in range(len(routers)):
  if start == count :
    for _myth in range(1,i+1):
        thread_list[_myth].join()
    start+=1
FileOutCsv = "00_log.csv"

# creating output file
fc = open(FileOutCsv,"a")

# running routine
t = MiThread(i,routers[i])
threads_list.append(t)
t.start()
start+=1
Abhishek Dave
  • 699
  • 1
  • 8
  • 21