+
+
+def FormatTimestamp(ts):
+ """Formats a given timestamp.
+
+ @type ts: timestamp
+ @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
+
+ @rtype: string
+ @return: a string with the formatted timestamp
+
+ """
+ if not isinstance (ts, (tuple, list)) or len(ts) != 2:
+ return '?'
+ sec, usec = ts
+ return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
+
+
+def ParseTimespec(value):
+ """Parse a time specification.
+
+ The following suffixed will be recognized:
+
+ - s: seconds
+ - m: minutes
+ - h: hours
+ - d: day
+ - w: weeks
+
+ Without any suffix, the value will be taken to be in seconds.
+
+ """
+ value = str(value)
+ if not value:
+ raise errors.OpPrereqError("Empty time specification passed")
+ suffix_map = {
+ 's': 1,
+ 'm': 60,
+ 'h': 3600,
+ 'd': 86400,
+ 'w': 604800,
+ }
+ if value[-1] not in suffix_map:
+ try:
+ value = int(value)
+ except ValueError:
+ raise errors.OpPrereqError("Invalid time specification '%s'" % value)
+ else:
+ multiplier = suffix_map[value[-1]]
+ value = value[:-1]
+ if not value: # no data left after stripping the suffix
+ raise errors.OpPrereqError("Invalid time specification (only"
+ " suffix passed)")
+ try:
+ value = int(value) * multiplier
+ except ValueError:
+ raise errors.OpPrereqError("Invalid time specification '%s'" % value)
+ return value
+
+
+def GetOnlineNodes(nodes, cl=None, nowarn=False):
+ """Returns the names of online nodes.
+
+ This function will also log a warning on stderr with the names of
+ the online nodes.
+
+ @param nodes: if not empty, use only this subset of nodes (minus the
+ offline ones)
+ @param cl: if not None, luxi client to use
+ @type nowarn: boolean
+ @param nowarn: by default, this function will output a note with the
+ offline nodes that are skipped; if this parameter is True the
+ note is not displayed
+
+ """
+ if cl is None:
+ cl = GetClient()
+
+ result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
+ use_locking=False)
+ offline = [row[0] for row in result if row[1]]
+ if offline and not nowarn:
+ ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
+ return [row[0] for row in result if not row[1]]
+
+
+def _ToStream(stream, txt, *args):
+ """Write a message to a stream, bypassing the logging system
+
+ @type stream: file object
+ @param stream: the file to which we should write
+ @type txt: str
+ @param txt: the message
+
+ """
+ if args:
+ args = tuple(args)
+ stream.write(txt % args)
+ else:
+ stream.write(txt)
+ stream.write('\n')
+ stream.flush()
+
+
+def ToStdout(txt, *args):
+ """Write a message to stdout only, bypassing the logging system
+
+ This is just a wrapper over _ToStream.
+
+ @type txt: str
+ @param txt: the message
+
+ """
+ _ToStream(sys.stdout, txt, *args)
+
+
+def ToStderr(txt, *args):
+ """Write a message to stderr only, bypassing the logging system
+
+ This is just a wrapper over _ToStream.
+
+ @type txt: str
+ @param txt: the message
+
+ """
+ _ToStream(sys.stderr, txt, *args)
+
+
+class JobExecutor(object):
+ """Class which manages the submission and execution of multiple jobs.
+
+ Note that instances of this class should not be reused between
+ GetResults() calls.
+
+ """
+ def __init__(self, cl=None, verbose=True):
+ self.queue = []
+ if cl is None:
+ cl = GetClient()
+ self.cl = cl
+ self.verbose = verbose
+ self.jobs = []
+
+ def QueueJob(self, name, *ops):
+ """Record a job for later submit.
+
+ @type name: string
+ @param name: a description of the job, will be used in WaitJobSet
+ """
+ self.queue.append((name, ops))
+
+
+ def SubmitPending(self):
+ """Submit all pending jobs.
+
+ """
+ results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
+ for ((status, data), (name, _)) in zip(results, self.queue):
+ self.jobs.append((status, data, name))
+
+ def GetResults(self):
+ """Wait for and return the results of all jobs.
+
+ @rtype: list
+ @return: list of tuples (success, job results), in the same order
+ as the submitted jobs; if a job has failed, instead of the result
+ there will be the error message
+
+ """
+ if not self.jobs:
+ self.SubmitPending()
+ results = []
+ if self.verbose:
+ ok_jobs = [row[1] for row in self.jobs if row[0]]
+ if ok_jobs:
+ ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
+ for submit_status, jid, name in self.jobs:
+ if not submit_status:
+ ToStderr("Failed to submit job for %s: %s", name, jid)
+ results.append((False, jid))
+ continue
+ if self.verbose:
+ ToStdout("Waiting for job %s for %s...", jid, name)
+ try:
+ job_result = PollJob(jid, cl=self.cl)
+ success = True
+ except (errors.GenericError, luxi.ProtocolError), err:
+ _, job_result = FormatError(err)
+ success = False
+ # the error message will always be shown, verbose or not
+ ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
+
+ results.append((success, job_result))
+ return results
+
+ def WaitOrShow(self, wait):
+ """Wait for job results or only print the job IDs.
+
+ @type wait: boolean
+ @param wait: whether to wait or not
+
+ """
+ if wait:
+ return self.GetResults()
+ else:
+ if not self.jobs:
+ self.SubmitPending()
+ for status, result, name in self.jobs:
+ if status:
+ ToStdout("%s: %s", result, name)
+ else:
+ ToStderr("Failure for %s: %s", name, result)