Salome HOME
First version of the xml/xsl display of the jobs for the 'sat jobs' command
authorSerge Rehbinder <serge.rehbinder@cea.fr>
Mon, 6 Jun 2016 12:51:46 +0000 (14:51 +0200)
committerSerge Rehbinder <serge.rehbinder@cea.fr>
Mon, 6 Jun 2016 12:51:46 +0000 (14:51 +0200)
commands/jobs.py
src/xmlManager.py
src/xsl/job_report.xsl

index 05c6147882ce697dd67f511b6afab8b140b2cc56..cda3516cdd88edd19464bf0ad496f5dd043522aa 100644 (file)
@@ -146,7 +146,6 @@ class machine(object):
         logger.write("host : " + self.host + "\n")
         logger.write("port : " + str(self.port) + "\n")
         logger.write("user : " + str(self.user) + "\n")
-        logger.write("password : " + str(self.password) + "\n")
         if self.successfully_connected(logger):
             status = src.OK_STATUS
         else:
@@ -157,18 +156,21 @@ class machine(object):
 class job(object):
     '''Class to manage one job
     '''
-    def __init__(self, name, machine, commands, timeout, logger, after=None):
+    def __init__(self, name, machine, application, distribution, commands, timeout, logger, after=None):
 
         self.name = name
         self.machine = machine
         self.after = after
         self.timeout = timeout
+        self.application = application
+        self.distribution = distribution
         self.logger = logger
         
         self._T0 = -1
         self._Tf = -1
         self._has_begun = False
         self._has_finished = False
+        self._has_timouted = False
         self._stdin = None # Store the command inputs field
         self._stdout = None # Store the command outputs field
         self._stderr = None # Store the command errors field
@@ -243,7 +245,15 @@ class job(object):
         :rtype: bool
         '''
         return self.has_begun() and not self.has_finished()
-    
+
+    def is_timeout(self):
+        '''Returns True if the job commands has finished with timeout 
+        
+        :return: True if the job has finished with timeout
+        :rtype: bool
+        '''
+        return self._has_timouted
+
     def time_elapsed(self):
         if not self.has_begun():
             return -1
@@ -255,6 +265,7 @@ class job(object):
             return
         if self.time_elapsed() > self.timeout:
             self._has_finished = True
+            self._has_timouted = True
             self._Tf = time.time()
             self.get_pids()
             (out_kill, _) = self.kill_remote_process()
@@ -317,7 +328,17 @@ class job(object):
         else:
             logger.write(self.err + "\n")
         
-        
+    def get_status(self):
+        if not self.machine.successfully_connected(self.logger):
+            return "SSH connection KO"
+        if not self.has_begun():
+            return "Not launched"
+        if self.is_running():
+            return "running since " + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self._T0))        
+        if self.has_finished():
+            if self.is_timeout():
+                return "Timeout since " + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self._Tf))
+            return "Finished since " + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self._Tf))
     
 class Jobs(object):
     '''Class to manage the jobs to be run
@@ -363,8 +384,14 @@ class Jobs(object):
         after = None
         if 'after' in job_def:
             after = job_def.after
+        application = None
+        if 'application' in job_def:
+            application = job_def.application
+        distribution = None
+        if 'distribution' in job_def:
+            distribution = job_def.distribution
             
-        return job(name, machine, cmmnds, timeout, self.logger, after = after)
+        return job(name, machine, application, distribution, cmmnds, timeout, self.logger, after = after)
     
     def determine_products_and_machines(self):
         '''Function that reads the pyconf jobs definition and instantiates all
@@ -469,8 +496,15 @@ class Jobs(object):
                 jb.check_time()
             if jb.has_finished():
                 jobs_finished_list.append(jb)
+        
+        nb_job_finished_before = len(self._l_jobs_finished)
         self._l_jobs_finished = jobs_finished_list
         self._l_jobs_running = jobs_running_list
+        
+        nb_job_finished_now = len(self._l_jobs_finished)
+        
+        return nb_job_finished_now > nb_job_finished_before
+            
     
     def findJobThatHasName(self, name):
         '''Returns the job by its name.
@@ -564,6 +598,7 @@ class Jobs(object):
         # The infinite loop that runs the jobs
         l_jobs_not_started = self.dic_job_machine.keys()
         while len(self._l_jobs_finished) != len(self.dic_job_machine.keys()):
+            new_job_start = False
             for host_port in self.lhosts:
                 
                 if self.is_occupied(host_port):
@@ -575,18 +610,22 @@ class Jobs(object):
                     if jb.after == None:
                         jb.run(self.logger)
                         l_jobs_not_started.remove(jb)
+                        new_job_start = True
                         break
                     else:
                         jb_before = self.findJobThatHasName(jb.after) 
                         if jb_before.has_finished():
                             jb.run(self.logger)
                             l_jobs_not_started.remove(jb)
+                            new_job_start = True
                             break
             
-            self.update_jobs_states_list()
+            new_job_finished = self.update_jobs_states_list()
             
-            # Display the current status     
-            self.display_status(self.len_columns)
+            if new_job_start or new_job_finished:
+                self.gui.update_xml_file(self.ljobs)            
+                # Display the current status     
+                self.display_status(self.len_columns)
             
             # Make sure that the proc is not entirely busy
             time.sleep(0.001)
@@ -594,6 +633,9 @@ class Jobs(object):
         self.logger.write("\n")    
         self.logger.write(tiret_line)                   
         self.logger.write("\n\n")
+        
+        self.gui.update_xml_file(self.ljobs)
+        self.gui.last_update()
 
     def write_all_results(self):
         '''Display all the jobs outputs.
@@ -612,39 +654,153 @@ class Gui(object):
     '''Class to manage the the xml data that can be displayed in a browser to
        see the jobs states
     '''
-    def __init__(self, xml_file_path, l_jobs):
+    
+    """
+    <?xml version='1.0' encoding='utf-8'?>
+    <?xml-stylesheet type='text/xsl' href='job_report.xsl'?>
+    <JobsReport>
+      <infos>
+        <info name="generated" value="2016-06-02 07:06:45"/>
+      </infos>
+      <hosts>
+          <host name=is221553 port=22 distribution=UB12.04/>
+          <host name=is221560 port=22/>
+          <host name=is221553 port=22 distribution=FD20/>
+      </hosts>
+      <applications>
+          <application name=SALOME-7.8.0/>
+          <application name=SALOME-master/>
+          <application name=MED-STANDALONE-master/>
+          <application name=CORPUS/>
+      </applications>
+      
+      <jobs>
+          <job name="7.8.0 FD22">
+                <host>is228809</host>
+                <port>2200</port>
+                <application>SALOME-7.8.0</application>
+                <user>adminuser</user>
+                <timeout>240</timeout>
+                <commands>
+                    export DISPLAY=is221560
+                    scp -p salome@is221560.intra.cea.fr:/export/home/salome/SALOME-7.7.1p1-src.tgz /local/adminuser         
+                    tar xf /local/adminuser/SALOME-7.7.1p1-src.tgz -C /local/adminuser
+                </commands>
+                <state>Not launched</state>
+          </job>
+
+          <job name="master MG05">
+                <host>is221560</host>
+                <port>22</port>
+                <application>SALOME-master</application>
+                <user>salome</user>
+                <timeout>240</timeout>
+                <commands>
+                    export DISPLAY=is221560
+                    scp -p salome@is221560.intra.cea.fr:/export/home/salome/SALOME-7.7.1p1-src.tgz /local/adminuser         
+                    sat prepare SALOME-master
+                    sat compile SALOME-master
+                    sat check SALOME-master
+                    sat launcher SALOME-master
+                    sat test SALOME-master
+                </commands>
+                <state>Running since 23 min</state>
+                <!-- <state>time out</state> -->
+                <!-- <state>OK</state> -->
+                <!-- <state>KO</state> -->
+                <begin>10/05/2016 20h32</begin>
+                <end>10/05/2016 22h59</end>
+          </job>
+
+      </jobs>
+    </JobsReport>
+    
+    """
+    
+    def __init__(self, xml_file_path, l_jobs, stylesheet):
         # The path of the xml file
         self.xml_file_path = xml_file_path
+        # The stylesheet
+        self.stylesheet = stylesheet
         # Open the file in a writing stream
         self.xml_file = src.xmlManager.XmlLogFile(xml_file_path, "JobsReport")
         # Create the lines and columns
         self.initialize_array(l_jobs)
         # Write the wml file
-        self.update_xml_file()
+        self.update_xml_file(l_jobs)
     
     def initialize_array(self, l_jobs):
-        l_hosts = []
-        l_job_names_status = []
+        l_dist = []
+        l_applications = []
         for job in l_jobs:
-            host = (job.machine.host, job.machine.port)
-            if host not in l_hosts:
-                l_hosts.append(host)
-            l_job_names_status.append((job.name, host, "Not launched"))
-        self.l_hosts = l_hosts
-        self.l_job_names_status = l_job_names_status
-        
-    def update_xml_file(self):
+            distrib = job.distribution
+            if distrib not in l_dist:
+                l_dist.append(distrib)
+            
+            application = job.application
+            if application not in l_applications:
+                l_applications.append(application)
+                    
+        self.l_dist = l_dist
+        self.l_applications = l_applications
+        
         # Update the hosts node
-        self.xml_file.add_simple_node("hosts")
-        for host_name, host_port in self.l_hosts:
-            self.xml_file.append_node_attrib("hosts", {host_name : host_port})
+        self.xmldists = self.xml_file.add_simple_node("distributions")
+        for dist_name in self.l_dist:
+            src.xmlManager.add_simple_node(self.xmldists, "dist", attrib={"name" : dist_name})
+            
+        # Update the applications node
+        self.xmlapplications = self.xml_file.add_simple_node("applications")
+        for application in self.l_applications:
+            src.xmlManager.add_simple_node(self.xmlapplications, "application", attrib={"name" : application})
+        
+        # Initialize the jobs node
+        self.xmljobs = self.xml_file.add_simple_node("jobs")
+        
+        # Initialize the info node (when generated)
+        self.xmlinfos = self.xml_file.add_simple_node("infos", attrib={"name" : "last update", "JobsCommandStatus" : "running"})
+        
+    def update_xml_file(self, l_jobs):      
         
         # Update the job names and status node
-        for jname, jhost, jstatus in self.l_job_names_status:
-            self.xml_file.add_simple_node("job", jstatus, {"name" : jname, "host" : jhost[0] + ":" + str(jhost[1])})
+        for job in l_jobs:
+            # Find the node corresponding to the job and delete it
+            # in order to recreate it
+            for xmljob in self.xmljobs.findall('job'):
+                if xmljob.attrib['name'] == job.name:
+                    self.xmljobs.remove(xmljob)
+                
+            # recreate the job node
+            xmlj = src.xmlManager.add_simple_node(self.xmljobs, "job", attrib={"name" : job.name})
+            src.xmlManager.add_simple_node(xmlj, "host", job.machine.host)
+            src.xmlManager.add_simple_node(xmlj, "port", str(job.machine.port))
+            src.xmlManager.add_simple_node(xmlj, "user", job.machine.user)
+            src.xmlManager.add_simple_node(xmlj, "application", job.application)
+            src.xmlManager.add_simple_node(xmlj, "distribution", job.distribution)
+            src.xmlManager.add_simple_node(xmlj, "timeout", str(job.timeout))
+            src.xmlManager.add_simple_node(xmlj, "commands", job.commands)
+            src.xmlManager.add_simple_node(xmlj, "state", job.get_status())
+            src.xmlManager.add_simple_node(xmlj, "begin", str(job._T0))
+            src.xmlManager.add_simple_node(xmlj, "end", str(job._Tf))
+            src.xmlManager.add_simple_node(xmlj, "out", job.out)
+            src.xmlManager.add_simple_node(xmlj, "err", job.err)
+        
+        # Update the date
+        src.xmlManager.append_node_attrib(self.xmlinfos,
+                    attrib={"value" : 
+                    datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")})
+               
         # Write the file
-        self.xml_file.write_tree("job_report.xsl")
-        
+        self.write_xml_file()
+    
+    def last_update(self):
+        src.xmlManager.append_node_attrib(self.xmlinfos,
+                    attrib={"JobsCommandStatus" : "finished"})
+        # Write the file
+        self.write_xml_file()
+    
+    def write_xml_file(self):
+        self.xml_file.write_tree(self.stylesheet)
         
 def print_info(logger, arch, JobsFilePath):
     '''Prints information header..
@@ -743,8 +899,11 @@ def run(args, runner, logger):
     if options.test_connection:
         return 0
     
+    gui = None
     if options.publish:
-        Gui("/export/home/serioja/LOGS/test.xml", today_jobs.ljobs)
+        gui = Gui("/export/home/serioja/LOGS/test.xml", today_jobs.ljobs, "job_report.xsl")
+    
+    today_jobs.gui = gui
     
     try:
         # Run all the jobs contained in config_jobs
index fa175ff1c94518a73f81621397e3308c3558b9f6..3ef103eafc5bc7e35c6475fba56be620ccf50a6f 100644 (file)
@@ -137,3 +137,26 @@ class ReadXmlFile(object):
         :rtype: str
         '''
         return self.xmlroot.find(node).text
+
+def add_simple_node(root_node, node_name, text=None, attrib={}):
+    '''Add a node with some attibutes and text to the root node.
+
+    :param root_node etree.Element: the Etree element where to add the new node    
+    :param node_name str: the name of the node to add
+    :param text str: the text of the node
+    :param attrib dict: the dictionary containing the 
+                        attribute of the new node
+    '''
+    n = etree.Element(node_name, attrib=attrib)
+    n.text = text
+    root_node.append(n)
+    return n
+
+def append_node_attrib(root_node, attrib):
+    '''Append a new attributes to the node that has node_name as name
+    
+    :param root_node etree.Element: the Etree element 
+                                    where to append the new attibutes
+    :param attrib dixt: The attrib to append
+    '''
+    root_node.attrib.update(attrib)
index 7aeb02e80f9849a4b4445d4683741f813a90fdb9..dd9753ad3309f5bcb91769b9e6a79e627e6a5e82 100644 (file)
 <?xml version="1.0" encoding="utf-8"?>
+
 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
-<xsl:output method="html" />
+
 <xsl:template match="/">
-       
-<head>  
-    <title>SAlomeTools log</title>
-    <style type="text/css">
-        table       { 
-                      margin:1px;
-                      padding:1px;
-                      border-collapse:collapse;
-                      empty-cells : show;
-                    }
-        td          { vertical-align : center;}
-        h1          { text-align : center; }
-        .legend     { font-weight : bold;
-                      text-align : center;
-                    } 
-        .def        { font-family: Arial, Verdana, "Times New Roman", Times, serif;}
-        hr.note     { color: #BFBFBF; }
-        .note       { text-align : right; font-style: italic; font-size: small; }
-        div.release { -moz-column-count: 2;
-                      overflow: auto;
-                      max-height: 250px;
-                    }
-    </style>
+<html>
+<head>
+<title>Jobs Report</title>
+<style type="text/css">
+    <!-- styles for commands results -->
+    .OKday        { background-color:#20FF20; }
+    .OKweek       { background-color:#90EE90; font-size: 11px; }
+    .OKmonth      { background-color:#90EE90; font-size: 10px; }
+    .OKold        { background-color:#90EE90; font-size: 9px; }
+
+    .KOday        { background-color:#F20000; }
+    .KOweek       { background-color:#FFC0CB; font-size: 11px; }
+    .KOmonth      { background-color:#FFC0CB; font-size: 10px; }
+    .KOold        { background-color:#FFC0CB; font-size: 9px; }
+
+    .KFday        { background-color:#FFA500; }
+    .KFweek       { background-color:#FAC86D; font-size: 11px; }
+    .KFmonth      { background-color:#FAC86D; font-size: 10px; }
+    .KFold        { background-color:#FAC86D; font-size: 9px; }
+
+    .NAday        { background-color:#BBBBBB; }
+    .NAweek       { background-color:#BFBFBF; font-size: 11px; }
+    .NAmonth      { background-color:#CACACA; font-size: 10px; }
+    .NAold        { background-color:#CFCFCF; font-size: 9px; }
+
+    .label        { font-weight: bold; }
+
+    <!-- styles for links in matrix -->
+    .OK2          { color:#00AA00; }
+    .KO2          { color:#FF0000; }
+    .KF2          { color:#509050; }
+    .NA2          { color:#BBBBBB; }
+    .OK2day       { color:#00AA00; font-weight: bold; }
+    .KO2day       { color:#FF0000; font-weight: bold; }
+    .KF2day       { color:#FF8000; font-weight: bold; }
+    .NA2day       { color:#BBBBBB; font-weight: bold; }
+
+    .new          { background-color:#FF5500; }
+    .day          { background-color:#F0E25A; font-size: small; }
+    .week         { background-color:#E0E0E0; font-size: small; }
+    .month        { background-color:#C0C0C0; font-size: small; }
+    .old          { background-color:#A0A0A0; font-size: small; }
+    .lnk          { font-size: 12px; }
+    .lnk a        { text-decoration: none; }
+    .note         { text-align : right; font-style: italic; font-size: small; }
+    table.legend  { margin:0px;
+                    padding:5px;
+                    border-collapse:collapse;
+                    empty-cells : show;
+                    border : solid 1px;
+                  }
+    table.summary { width : 100%;
+                    margin:0px;
+                    padding:0px;
+                    border-collapse:collapse;
+                    empty-cells : show;
+                    border : solid 1px;
+                  }
+    td.summary    { border : solid 0px; font-size: medium; }
+    td            { border : solid 1px; }
+    td.small      { border : solid 1px; font-size: small; }
+    th            { font-size: small; border: solid 1px;  }
+    h2            { text-align : center; }
+    h3            { text-align : left; font-size: small; font-weight: normal; }
+    h4            { text-align : left; font-size: small; font-weight: bold; display: inline; }
+    h_err         { text-align : left; font-size: small; font-weight: normal; display: inline;         color: red; }
+    .legend       { text-align : center; } 
+    .def        { font-family: Arial, Verdana, "Times New Roman", Times, serif;}
+   
+</style>
+
+<xsl:if test="//JobsReport/infos/@JobsCommandStatus='running'">
+  <meta http-equiv="refresh" content="1"></meta>
+</xsl:if>
+
+<script language="JavaScript"><![CDATA[
+      function Toggle(id) {
+        collapseall();
+        var element = document.getElementById(id);
+
+         if ( element.style.display == "none" )
+            element.style.display = "block";
+         else 
+            element.style.display = "none";
+      }
+
+      function collapseall() {
+        var x=document.getElementsByTagName("div");
+        for (i=0;i<x.length;i++)
+        {
+            if ( x[i].id != "matrix" )
+                x[i].style.display = "none";
+        }
+      }
+    ]]>
+</script>
+
 </head>
-       <body class="def" bgcolor="aliceblue">
-               <table border="1">
-                       <xsl:for-each select="JobsReport/hosts/@*">
-                               <tr bgcolor="aliceblue" width="2">
-                                       <td><xsl:value-of select="concat('host ', name(), ' / port ', .)"/></td>
-                                       <xsl:for-each select="JobsReport/job">
-                                               <xsl:choose>
-                                                   <xsl:when test="host=concat(name(), ':', .)">
-                                                           <td>XXX</td>
-                                                   </xsl:when>
-                                                   <xsl:otherwise>
-                                                           <td>YYY</td>
-                                                   </xsl:otherwise>
-                                               </xsl:choose>
-                                       </xsl:for-each>
-                               </tr>
-                       </xsl:for-each>
-               </table>
-       </body>
+
+<body class="def">
+    <table width="100%">
+       <tr>
+           <td class="summary">
+               <h2>Jobs Report</h2>
+           </td>
+           <td class="summary" align="right" valign="bottom" width="300">
+               <xsl:for-each select="//JobsReport/infos">
+                 <span class="note"><xsl:value-of select="@name" />: <xsl:value-of select="@value" /></span>
+               </xsl:for-each>
+           </td>
+       </tr>
+    </table>
+       
+    <div id="matrix">
+    <table class="summary">
+      <!-- header -->
+      <tr bgcolor="#9acd32">
+      <th></th>
+      <xsl:for-each select="//JobsReport/applications/application">
+        <xsl:sort select="@name" />
+       <th><xsl:value-of select="@name" /></th>
+      </xsl:for-each>
+      </tr>
+      
+      <!-- for all hosts -->
+      <xsl:for-each select="//JobsReport/distributions/dist">
+        <xsl:sort select="@name" />
+       <xsl:variable name="curr_distname" select="@name" />
+       <tr>
+       <td align="center"><xsl:value-of select="$curr_distname" /></td>
+       <!-- for all jobs -->
+       <xsl:for-each select="//JobsReport/applications/application">
+         <xsl:sort select="@name" />
+         <xsl:variable name="curr_appli" select="@name" />
+         <td align="center" class="small">
+             <!-- get the job for current host and current appli -->
+             <xsl:for-each select="//JobsReport/jobs/job">
+                 <xsl:sort select="@name" />
+                 <xsl:if test="application/.=$curr_appli and distribution/.=$curr_distname">
+                     <a href="#"><xsl:attribute name="onclick">javascript:Toggle('<xsl:value-of select="@name"/>')</xsl:attribute>
+                     <xsl:value-of select="@name"/></a>&#160; : 
+                     <xsl:value-of select="state/." />
+                     <br/>
+                 </xsl:if> 
+                 
+             </xsl:for-each>
+         </td>
+       </xsl:for-each>
+       </tr>
+      </xsl:for-each>
+    </table>
+    
+    <h3>
+    <xsl:choose>
+       <xsl:when test="//JobsReport/infos/@JobsCommandStatus='running'">
+           Command status : running <img src="running.gif"></img>
+       </xsl:when>
+       
+       <xsl:otherwise>
+           Command status : finished
+       </xsl:otherwise>
+    </xsl:choose>
+    </h3>
+    </div>
+
+    
+    <!-- Loop over the jobs in order to find what job was called in the link "onclick". Display information about that job -->
+    <xsl:for-each select="//JobsReport/jobs/job">
+      <xsl:variable name="curr_job_name" select="@name" />
+      <div style="display:none"><xsl:attribute name="id"><xsl:value-of select="@name"/></xsl:attribute>
+         <!-- Display job name -->
+         <h4>Name : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/@name"/>
+         <br/>
+         <!-- Display the job attributes -->
+         <h4>Hostname/port : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/host"/>/<xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/port"/>
+         <br/>
+         <h4>User : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/user"/>
+         <br/>
+         <h4>Timeout : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/timeout"/>
+         <br/>
+         <h4>Begin : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/begin"/>
+         <br/>
+         <h4>End : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/end"/>
+         <br/>
+         <h4>Commands : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/commands"/>
+         <br/>
+         <h4>Out : </h4><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/out"/>
+         <br/>
+         <h4>Err : </h4><h_err><xsl:value-of select="//JobsReport/jobs/job[@name=$curr_job_name]/err"/></h_err>
+      </div>
+    </xsl:for-each>
+
+</body>
+
+</html>
 </xsl:template>
-</xsl:stylesheet>
+</xsl:stylesheet>
\ No newline at end of file