Salome HOME
05c6147882ce697dd67f511b6afab8b140b2cc56
[tools/sat.git] / commands / jobs.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2013  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import os
20 import sys
21 import datetime
22 import time
23 import paramiko
24
25 import src
26
27
28 parser = src.options.Options()
29
30 parser.add_option('j', 'jobs_config', 'string', 'jobs_cfg', 
31                   _('The name of the config file that contains'
32                   ' the jobs configuration'))
33 parser.add_option('o', 'only_jobs', 'list2', 'only_jobs',
34                   _('The list of jobs to launch, by their name. '))
35 parser.add_option('l', 'list', 'boolean', 'list', 
36                   _('list all available config files.'))
37 parser.add_option('n', 'no_label', 'boolean', 'no_label',
38                   _("do not print labels, Works only with --list."), False)
39 parser.add_option('t', 'test_connection', 'boolean', 'test_connection',
40                   _("Try to connect to the machines. Not executing the jobs."),
41                   False)
42 parser.add_option('p', 'publish', 'boolean', 'publish',
43                   _("Generate an xml file that can be read in a browser to "
44                     "display the jobs status."),
45                   False)
46
47 class machine(object):
48     '''Class to manage a ssh connection on a machine
49     '''
50     def __init__(self, host, user, port=22, passwd=None):
51         self.host = host
52         self.port = port
53         self.user = user
54         self.password = passwd
55         self.ssh = paramiko.SSHClient()
56         self._connection_successful = None
57     
58     def connect(self, logger):
59         '''Initiate the ssh connection to the remote machine
60         
61         :param logger src.logger.Logger: The logger instance 
62         :return: Nothing
63         :rtype: N\A
64         '''
65
66         self._connection_successful = False
67         self.ssh.load_system_host_keys()
68         self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
69         try:
70             self.ssh.connect(self.host,
71                              port=self.port,
72                              username=self.user,
73                              password = self.password)
74         except paramiko.AuthenticationException:
75             message = src.KO_STATUS + _(": authentication failed\n")
76             logger.write( src.printcolors.printcError(message))
77         except paramiko.BadHostKeyException:
78             message = (src.KO_STATUS + 
79                        _(": the server's host key could not be verified\n"))
80             logger.write( src.printcolors.printcError(message))
81         except paramiko.SSHException:
82             message = (src.KO_STATUS + 
83                     _(": error connecting or establishing an SSH session\n"))
84             logger.write( src.printcolors.printcError(message))
85         except:
86             logger.write( src.printcolors.printcError(src.KO_STATUS + '\n'))
87         else:
88             self._connection_successful = True
89             logger.write( src.printcolors.printcSuccess(src.OK_STATUS) + '\n')
90     
91     def successfully_connected(self, logger):
92         '''Verify if the connection to the remote machine has succeed
93         
94         :param logger src.logger.Logger: The logger instance 
95         :return: True if the connection has succeed, False if not
96         :rtype: bool
97         '''
98         if self._connection_successful == None:
99             message = "Warning : trying to ask if the connection to "
100             "(host: %s, port: %s, user: %s) is OK whereas there were"
101             " no connection request" % \
102             (machine.host, machine.port, machine.user)
103             logger.write( src.printcolors.printcWarning(message))
104         return self._connection_successful
105   
106     
107     def close(self):
108         '''Close the ssh connection
109         
110         :rtype: N\A
111         '''
112         self.ssh.close()
113     
114     def exec_command(self, command, logger):
115         '''Execute the command on the remote machine
116         
117         :param command str: The command to be run
118         :param logger src.logger.Logger: The logger instance 
119         :return: the stdin, stdout, and stderr of the executing command,
120                  as a 3-tuple
121         :rtype: (paramiko.channel.ChannelFile, paramiko.channel.ChannelFile, 
122                 paramiko.channel.ChannelFile)
123         '''
124         try:        
125             # Does not wait the end of the command
126             (stdin, stdout, stderr) = self.ssh.exec_command(command)
127         except paramiko.SSHException:
128             message = src.KO_STATUS + _(
129                             ": the server failed to execute the command\n")
130             logger.write( src.printcolors.printcError(message))
131             return (None, None, None)
132         except:
133             logger.write( src.printcolors.printcError(src.KO_STATUS + '\n'))
134             return (None, None, None)
135         else:
136             return (stdin, stdout, stderr)
137        
138     def write_info(self, logger):
139         '''Prints the informations relative to the machine in the logger 
140            (terminal traces and log file)
141         
142         :param logger src.logger.Logger: The logger instance
143         :return: Nothing
144         :rtype: N\A
145         '''
146         logger.write("host : " + self.host + "\n")
147         logger.write("port : " + str(self.port) + "\n")
148         logger.write("user : " + str(self.user) + "\n")
149         logger.write("password : " + str(self.password) + "\n")
150         if self.successfully_connected(logger):
151             status = src.OK_STATUS
152         else:
153             status = src.KO_STATUS
154         logger.write("Connection : " + status + "\n\n") 
155
156
157 class job(object):
158     '''Class to manage one job
159     '''
160     def __init__(self, name, machine, commands, timeout, logger, after=None):
161
162         self.name = name
163         self.machine = machine
164         self.after = after
165         self.timeout = timeout
166         self.logger = logger
167         
168         self._T0 = -1
169         self._Tf = -1
170         self._has_begun = False
171         self._has_finished = False
172         self._stdin = None # Store the command inputs field
173         self._stdout = None # Store the command outputs field
174         self._stderr = None # Store the command errors field
175
176         self.out = None # Contains something only if the job is finished
177         self.err = None # Contains something only if the job is finished    
178                
179         self.commands = " ; ".join(commands)
180     
181     def get_pids(self):
182         pids = []
183         for cmd in self.commands.split(" ; "):
184             cmd_pid = 'ps aux | grep "' + cmd + '" | awk \'{print $2}\''
185             (_, out_pid, _) = self.machine.exec_command(cmd_pid, self.logger)
186             pids_cmd = out_pid.readlines()
187             pids_cmd = [str(src.only_numbers(pid)) for pid in pids_cmd]
188             pids+=pids_cmd
189         return pids
190     
191     def kill_remote_process(self):
192         '''Kills the process on the remote machine.
193         
194         :return: (the output of the kill, the error of the kill)
195         :rtype: (str, str)
196         '''
197         
198         pids = self.get_pids()
199         cmd_kill = " ; ".join([("kill -9 " + pid) for pid in pids])
200         (_, out_kill, err_kill) = self.machine.exec_command(cmd_kill, 
201                                                             self.logger)
202         return (out_kill, err_kill)
203             
204     def has_begun(self):
205         '''Returns True if the job has already begun
206         
207         :return: True if the job has already begun
208         :rtype: bool
209         '''
210         return self._has_begun
211     
212     def has_finished(self):
213         '''Returns True if the job has already finished 
214            (i.e. all the commands have been executed)
215            If it is finished, the outputs are stored in the fields out and err.  
216         
217         :return: True if the job has already finished
218         :rtype: bool
219         '''
220         
221         # If the method has already been called and returned True
222         if self._has_finished:
223             return True
224         
225         # If the job has not begun yet
226         if not self.has_begun():
227             return False
228         
229         if self._stdout.channel.closed:
230             self._has_finished = True
231             # And store the result outputs
232             self.out = self._stdout.read()
233             self.err = self._stderr.read()
234             # And put end time
235             self._Tf = time.time()
236         
237         return self._has_finished
238     
239     def is_running(self):
240         '''Returns True if the job commands are running 
241         
242         :return: True if the job is running
243         :rtype: bool
244         '''
245         return self.has_begun() and not self.has_finished()
246     
247     def time_elapsed(self):
248         if not self.has_begun():
249             return -1
250         T_now = time.time()
251         return T_now - self._T0
252     
253     def check_time(self):
254         if not self.has_begun():
255             return
256         if self.time_elapsed() > self.timeout:
257             self._has_finished = True
258             self._Tf = time.time()
259             self.get_pids()
260             (out_kill, _) = self.kill_remote_process()
261             self.out = "TIMEOUT \n" + out_kill.read()
262             self.err = "TIMEOUT : %s seconds elapsed\n" % str(self.timeout)
263     
264     def total_duration(self):
265         return self._Tf - self._T0
266         
267     def run(self, logger):
268         if self.has_begun():
269             print("Warn the user that a job can only be launched one time")
270             return
271         
272         if not self.machine.successfully_connected(logger):
273             self._has_finished = True
274             self.out = "N\A"
275             self.err = ("Connection to machine (host: %s, port: %s, user: %s) has failed" 
276                         % (self.machine.host, self.machine.port, self.machine.user))
277         else:
278             self._T0 = time.time()
279             self._stdin, self._stdout, self._stderr = self.machine.exec_command(
280                                                         self.commands, logger)
281             if (self._stdin, self._stdout, self._stderr) == (None, None, None):
282                 self._has_finished = True
283                 self._Tf = time.time()
284                 self.out = "N\A"
285                 self.err = "The server failed to execute the command"
286         
287         self._has_begun = True
288     
289     def write_results(self, logger):
290         logger.write("name : " + self.name + "\n")
291         if self.after:
292             logger.write("after : %s\n" % self.after)
293         logger.write("Time elapsed : %4imin %2is \n" % 
294                      (self.total_duration()/60 , self.total_duration()%60))
295         if self._T0 != -1:
296             logger.write("Begin time : %s\n" % 
297                          time.strftime('%Y-%m-%d %H:%M:%S', 
298                                        time.localtime(self._T0)) )
299         if self._Tf != -1:
300             logger.write("End time   : %s\n\n" % 
301                          time.strftime('%Y-%m-%d %H:%M:%S', 
302                                        time.localtime(self._Tf)) )
303         
304         machine_head = "Informations about connection :\n"
305         underline = (len(machine_head) - 2) * "-"
306         logger.write(src.printcolors.printcInfo(machine_head + underline + "\n"))
307         self.machine.write_info(logger)
308         
309         logger.write(src.printcolors.printcInfo("out : \n"))
310         if self.out is None:
311             logger.write("Unable to get output\n")
312         else:
313             logger.write(self.out + "\n")
314         logger.write(src.printcolors.printcInfo("err : \n"))
315         if self.err is None:
316             logger.write("Unable to get error\n")
317         else:
318             logger.write(self.err + "\n")
319         
320         
321     
322 class Jobs(object):
323     '''Class to manage the jobs to be run
324     '''
325     def __init__(self, runner, logger, config_jobs, lenght_columns = 20):
326         # The jobs configuration
327         self.cfg_jobs = config_jobs
328         # The machine that will be used today
329         self.lmachines = []
330         # The list of machine (hosts, port) that will be used today 
331         # (a same host can have several machine instances since there 
332         # can be several ssh parameters) 
333         self.lhosts = []
334         # The jobs to be launched today 
335         self.ljobs = []     
336         self.runner = runner
337         self.logger = logger
338         # The correlation dictionary between jobs and machines
339         self.dic_job_machine = {} 
340         self.len_columns = lenght_columns
341         
342         # the list of jobs that have not been run yet
343         self._l_jobs_not_started = []
344         # the list of jobs that have already ran 
345         self._l_jobs_finished = []
346         # the list of jobs that are running 
347         self._l_jobs_running = [] 
348                 
349         self.determine_products_and_machines()
350     
351     def define_job(self, job_def, machine):
352         '''Takes a pyconf job definition and a machine (from class machine) 
353            and returns the job instance corresponding to the definition.
354         
355         :param job_def src.config.Mapping: a job definition 
356         :param machine machine: the machine on which the job will run
357         :return: The corresponding job in a job class instance
358         :rtype: job
359         '''
360         name = job_def.name
361         cmmnds = job_def.commands
362         timeout = job_def.timeout
363         after = None
364         if 'after' in job_def:
365             after = job_def.after
366             
367         return job(name, machine, cmmnds, timeout, self.logger, after = after)
368     
369     def determine_products_and_machines(self):
370         '''Function that reads the pyconf jobs definition and instantiates all
371            the machines and jobs to be done today.
372
373         :return: Nothing
374         :rtype: N\A
375         '''
376         today = datetime.date.weekday(datetime.date.today())
377         host_list = []
378         
379         for job_def in self.cfg_jobs.jobs :
380             if today in job_def.when: 
381                 if 'host' not in job_def:
382                     host = self.runner.cfg.VARS.hostname
383                 else:
384                     host = job_def.host
385                 
386                 if 'port' not in job_def:
387                     port = 22
388                 else:
389                     port = job_def.port
390                 
391                 if (host, port) not in host_list:
392                     host_list.append((host, port))
393                 
394                 if 'user' not in job_def:
395                     user = self.runner.cfg.VARS.user
396                 else:
397                     user = job_def.user
398                 
399                 if 'password' not in job_def:
400                     passwd = None
401                 else:
402                     passwd = job_def.password
403                                               
404                 a_machine = machine(host, user, port=port, passwd=passwd)
405                 
406                 self.lmachines.append(a_machine)
407                 
408                 a_job = self.define_job(job_def, a_machine)
409                 
410                 self.ljobs.append(a_job)
411                 
412                 self.dic_job_machine[a_job] = a_machine
413         
414         self.lhosts = host_list
415         
416     def ssh_connection_all_machines(self, pad=50):
417         '''Function that do the ssh connection to every machine 
418            to be used today.
419
420         :return: Nothing
421         :rtype: N\A
422         '''
423         self.logger.write(src.printcolors.printcInfo((
424                         "Establishing connection with all the machines :\n")))
425         for machine in self.lmachines:
426             # little algorithm in order to display traces
427             begin_line = ("(host: %s, port: %s, user: %s)" % 
428                           (machine.host, machine.port, machine.user))
429             if pad - len(begin_line) < 0:
430                 endline = " "
431             else:
432                 endline = (pad - len(begin_line)) * "." + " "
433             self.logger.write( begin_line + endline )
434             self.logger.flush()
435             # the call to the method that initiate the ssh connection
436             machine.connect(self.logger)
437         self.logger.write("\n")
438         
439
440     def is_occupied(self, hostname):
441         '''Function that returns True if a job is running on 
442            the machine defined by its host and its port.
443         
444         :param hostname (str, int): the pair (host, port)
445         :return: the job that is running on the host, 
446                 or false if there is no job running on the host. 
447         :rtype: job / bool
448         '''
449         host = hostname[0]
450         port = hostname[1]
451         for jb in self.dic_job_machine:
452             if jb.machine.host == host and jb.machine.port == port:
453                 if jb.is_running():
454                     return jb
455         return False
456     
457     def update_jobs_states_list(self):
458         '''Function that updates the lists that store the currently
459            running jobs and the jobs that have already finished.
460         
461         :return: Nothing. 
462         :rtype: N\A
463         '''
464         jobs_finished_list = []
465         jobs_running_list = []
466         for jb in self.dic_job_machine:
467             if jb.is_running():
468                 jobs_running_list.append(jb)
469                 jb.check_time()
470             if jb.has_finished():
471                 jobs_finished_list.append(jb)
472         self._l_jobs_finished = jobs_finished_list
473         self._l_jobs_running = jobs_running_list
474     
475     def findJobThatHasName(self, name):
476         '''Returns the job by its name.
477         
478         :param name str: a job name
479         :return: the job that has the name. 
480         :rtype: job
481         '''
482         for jb in self.ljobs:
483             if jb.name == name:
484                 return jb
485
486         # the following is executed only if the job was not found
487         msg = _('The job "%s" seems to be nonexistent') % name
488         raise src.SatException(msg)
489     
490     def str_of_length(self, text, length):
491         '''Takes a string text of any length and returns 
492            the most close string of length "length".
493         
494         :param text str: any string
495         :param length int: a length for the returned string
496         :return: the most close string of length "length"
497         :rtype: str
498         '''
499         if len(text) > length:
500             text_out = text[:length-3] + '...'
501         else:
502             diff = length - len(text)
503             before = " " * (diff/2)
504             after = " " * (diff/2 + diff%2)
505             text_out = before + text + after
506             
507         return text_out
508     
509     def display_status(self, len_col):
510         '''Takes a lenght and construct the display of the current status 
511            of the jobs in an array that has a column for each host.
512            It displays the job that is currently running on the host 
513            of the column.
514         
515         :param len_col int: the size of the column 
516         :return: Nothing
517         :rtype: N\A
518         '''
519         
520         display_line = ""
521         for host_port in self.lhosts:
522             jb = self.is_occupied(host_port)
523             if not jb: # nothing running on the host
524                 empty = self.str_of_length("empty", len_col)
525                 display_line += "|" + empty 
526             else:
527                 display_line += "|" + src.printcolors.printcInfo(
528                                         self.str_of_length(jb.name, len_col))
529         
530         self.logger.write("\r" + display_line + "|")
531         self.logger.flush()
532     
533
534     def run_jobs(self):
535         '''The main method. Runs all the jobs on every host. 
536            For each host, at a given time, only one job can be running.
537            The jobs that have the field after (that contain the job that has
538            to be run before it) are run after the previous job.
539            This method stops when all the jobs are finished.
540         
541         :return: Nothing
542         :rtype: N\A
543         '''
544
545         # Print header
546         self.logger.write(src.printcolors.printcInfo(
547                                                 _('Executing the jobs :\n')))
548         text_line = ""
549         for host_port in self.lhosts:
550             host = host_port[0]
551             port = host_port[1]
552             if port == 22: # default value
553                 text_line += "|" + self.str_of_length(host, self.len_columns)
554             else:
555                 text_line += "|" + self.str_of_length(
556                                 "("+host+", "+str(port)+")", self.len_columns)
557         
558         tiret_line = " " + "-"*(len(text_line)-1) + "\n"
559         self.logger.write(tiret_line)
560         self.logger.write(text_line + "|\n")
561         self.logger.write(tiret_line)
562         self.logger.flush()
563         
564         # The infinite loop that runs the jobs
565         l_jobs_not_started = self.dic_job_machine.keys()
566         while len(self._l_jobs_finished) != len(self.dic_job_machine.keys()):
567             for host_port in self.lhosts:
568                 
569                 if self.is_occupied(host_port):
570                     continue
571              
572                 for jb in l_jobs_not_started:
573                     if (jb.machine.host, jb.machine.port) != host_port:
574                         continue 
575                     if jb.after == None:
576                         jb.run(self.logger)
577                         l_jobs_not_started.remove(jb)
578                         break
579                     else:
580                         jb_before = self.findJobThatHasName(jb.after) 
581                         if jb_before.has_finished():
582                             jb.run(self.logger)
583                             l_jobs_not_started.remove(jb)
584                             break
585             
586             self.update_jobs_states_list()
587             
588             # Display the current status     
589             self.display_status(self.len_columns)
590             
591             # Make sure that the proc is not entirely busy
592             time.sleep(0.001)
593         
594         self.logger.write("\n")    
595         self.logger.write(tiret_line)                   
596         self.logger.write("\n\n")
597
598     def write_all_results(self):
599         '''Display all the jobs outputs.
600         
601         :return: Nothing
602         :rtype: N\A
603         '''
604         
605         for jb in self.dic_job_machine.keys():
606             self.logger.write(src.printcolors.printcLabel(
607                         "#------- Results for job %s -------#\n" % jb.name))
608             jb.write_results(self.logger)
609             self.logger.write("\n\n")
610
611 class Gui(object):
612     '''Class to manage the the xml data that can be displayed in a browser to
613        see the jobs states
614     '''
615     def __init__(self, xml_file_path, l_jobs):
616         # The path of the xml file
617         self.xml_file_path = xml_file_path
618         # Open the file in a writing stream
619         self.xml_file = src.xmlManager.XmlLogFile(xml_file_path, "JobsReport")
620         # Create the lines and columns
621         self.initialize_array(l_jobs)
622         # Write the wml file
623         self.update_xml_file()
624     
625     def initialize_array(self, l_jobs):
626         l_hosts = []
627         l_job_names_status = []
628         for job in l_jobs:
629             host = (job.machine.host, job.machine.port)
630             if host not in l_hosts:
631                 l_hosts.append(host)
632             l_job_names_status.append((job.name, host, "Not launched"))
633         self.l_hosts = l_hosts
634         self.l_job_names_status = l_job_names_status
635         
636     def update_xml_file(self):
637         # Update the hosts node
638         self.xml_file.add_simple_node("hosts")
639         for host_name, host_port in self.l_hosts:
640             self.xml_file.append_node_attrib("hosts", {host_name : host_port})
641         
642         # Update the job names and status node
643         for jname, jhost, jstatus in self.l_job_names_status:
644             self.xml_file.add_simple_node("job", jstatus, {"name" : jname, "host" : jhost[0] + ":" + str(jhost[1])})
645         # Write the file
646         self.xml_file.write_tree("job_report.xsl")
647         
648         
649 def print_info(logger, arch, JobsFilePath):
650     '''Prints information header..
651     
652     :param logger src.logger.Logger: The logger instance
653     :param arch str: a string that gives the architecture of the machine on 
654                      which the command is launched
655     :param JobsFilePath str: The path of the file 
656                              that contains the jobs configuration
657     :return: Nothing
658     :rtype: N\A
659     '''
660     info = [
661         (_("Platform"), arch),
662         (_("File containing the jobs configuration"), JobsFilePath)
663     ]
664     
665     smax = max(map(lambda l: len(l[0]), info))
666     for i in info:
667         sp = " " * (smax - len(i[0]))
668         src.printcolors.print_value(logger, sp + i[0], i[1], 2)
669     logger.write("\n", 2)
670
671 ##
672 # Describes the command
673 def description():
674     return _("The jobs command launches maintenances that are described"
675              " in the dedicated jobs configuration file.")
676
677 ##
678 # Runs the command.
679 def run(args, runner, logger):
680     (options, args) = parser.parse_args(args)
681        
682     jobs_cfg_files_dir = runner.cfg.SITE.jobs.config_path
683     
684     # Make sure the path to the jobs config files directory exists 
685     if not os.path.exists(jobs_cfg_files_dir):      
686         logger.write(_("Creating directory %s\n") % 
687                      src.printcolors.printcLabel(jobs_cfg_files_dir), 1)
688         os.mkdir(jobs_cfg_files_dir)
689
690     # list option : display all the available config files
691     if options.list:
692         lcfiles = []
693         if not options.no_label:
694             sys.stdout.write("------ %s\n" % 
695                              src.printcolors.printcHeader(jobs_cfg_files_dir))
696
697         for f in sorted(os.listdir(jobs_cfg_files_dir)):
698             if not f.endswith('.pyconf'):
699                 continue
700             cfilename = f[:-7]
701             lcfiles.append(cfilename)
702             sys.stdout.write("%s\n" % cfilename)
703         return 0
704
705     # Make sure the jobs_config option has been called
706     if not options.jobs_cfg:
707         message = _("The option --jobs_config is required\n")      
708         raise src.SatException( message )
709     
710     # Make sure the invoked file exists
711     file_jobs_cfg = os.path.join(jobs_cfg_files_dir, options.jobs_cfg)
712     if not file_jobs_cfg.endswith('.pyconf'):
713         file_jobs_cfg += '.pyconf'
714         
715     if not os.path.exists(file_jobs_cfg):
716         message = _("The file %s does not exist.\n") % file_jobs_cfg
717         logger.write(src.printcolors.printcError(message), 1)
718         message = _("The possible files are :\n")
719         logger.write( src.printcolors.printcInfo(message), 1)
720         for f in sorted(os.listdir(jobs_cfg_files_dir)):
721             if not f.endswith('.pyconf'):
722                 continue
723             jobscfgname = f[:-7]
724             sys.stdout.write("%s\n" % jobscfgname)
725         raise src.SatException( _("No corresponding file") )
726     
727     print_info(logger, runner.cfg.VARS.dist, file_jobs_cfg)
728     
729     # Read the config that is in the file
730     config_jobs = src.read_config_from_a_file(file_jobs_cfg)
731     if options.only_jobs:
732         l_jb = src.pyconf.Sequence()
733         for jb in config_jobs.jobs:
734             if jb.name in options.only_jobs:
735                 l_jb.append(jb,
736                 "Adding a job that was given in only_jobs option parameters")
737         config_jobs.jobs = l_jb
738               
739     # Initialization
740     today_jobs = Jobs(runner, logger, config_jobs)
741     # SSH connection to all machines
742     today_jobs.ssh_connection_all_machines()
743     if options.test_connection:
744         return 0
745     
746     if options.publish:
747         Gui("/export/home/serioja/LOGS/test.xml", today_jobs.ljobs)
748     
749     try:
750         # Run all the jobs contained in config_jobs
751         today_jobs.run_jobs()
752     except KeyboardInterrupt:
753         logger.write("\n\n%s\n\n" % 
754                 (src.printcolors.printcWarning(_("Forced interruption"))), 1)
755     finally:
756         # find the potential not finished jobs and kill them
757         for jb in today_jobs.ljobs:
758             if not jb.has_finished():
759                 jb.kill_remote_process()
760                 
761         # Output the results
762         today_jobs.write_all_results()