Remove two hlint overrides
[ganeti-local] / lib / client / gnt_debug.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Debugging commands"""
22
23 # pylint: disable=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-backup
27
28 import simplejson
29 import time
30 import socket
31 import logging
32
33 from ganeti.cli import *
34 from ganeti import cli
35 from ganeti import constants
36 from ganeti import opcodes
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import compat
40 from ganeti import ht
41
42
43 #: Default fields for L{ListLocks}
44 _LIST_LOCKS_DEF_FIELDS = [
45   "name",
46   "mode",
47   "owner",
48   "pending",
49   ]
50
51
52 def Delay(opts, args):
53   """Sleeps for a while
54
55   @param opts: the command line options selected by the user
56   @type args: list
57   @param args: should contain only one element, the duration
58       the sleep
59   @rtype: int
60   @return: the desired exit code
61
62   """
63   delay = float(args[0])
64   op = opcodes.OpTestDelay(duration=delay,
65                            on_master=opts.on_master,
66                            on_nodes=opts.on_nodes,
67                            repeat=opts.repeat)
68   SubmitOrSend(op, opts)
69
70   return 0
71
72
73 def GenericOpCodes(opts, args):
74   """Send any opcode to the master.
75
76   @param opts: the command line options selected by the user
77   @type args: list
78   @param args: should contain only one element, the path of
79       the file with the opcode definition
80   @rtype: int
81   @return: the desired exit code
82
83   """
84   cl = cli.GetClient()
85   jex = cli.JobExecutor(cl=cl, verbose=opts.verbose, opts=opts)
86
87   job_cnt = 0
88   op_cnt = 0
89   if opts.timing_stats:
90     ToStdout("Loading...")
91   for job_idx in range(opts.rep_job):
92     for fname in args:
93       # pylint: disable=W0142
94       op_data = simplejson.loads(utils.ReadFile(fname))
95       op_list = [opcodes.OpCode.LoadOpCode(val) for val in op_data]
96       op_list = op_list * opts.rep_op
97       jex.QueueJob("file %s/%d" % (fname, job_idx), *op_list)
98       op_cnt += len(op_list)
99       job_cnt += 1
100
101   if opts.timing_stats:
102     t1 = time.time()
103     ToStdout("Submitting...")
104
105   jex.SubmitPending(each=opts.each)
106
107   if opts.timing_stats:
108     t2 = time.time()
109     ToStdout("Executing...")
110
111   jex.GetResults()
112   if opts.timing_stats:
113     t3 = time.time()
114     ToStdout("C:op     %4d" % op_cnt)
115     ToStdout("C:job    %4d" % job_cnt)
116     ToStdout("T:submit %4.4f" % (t2 - t1))
117     ToStdout("T:exec   %4.4f" % (t3 - t2))
118     ToStdout("T:total  %4.4f" % (t3 - t1))
119   return 0
120
121
122 def TestAllocator(opts, args):
123   """Runs the test allocator opcode.
124
125   @param opts: the command line options selected by the user
126   @type args: list
127   @param args: should contain only one element, the iallocator name
128   @rtype: int
129   @return: the desired exit code
130
131   """
132   try:
133     disks = [{
134       constants.IDISK_SIZE: utils.ParseUnit(val),
135       constants.IDISK_MODE: constants.DISK_RDWR,
136       } for val in opts.disks.split(",")]
137   except errors.UnitParseError, err:
138     ToStderr("Invalid disks parameter '%s': %s", opts.disks, err)
139     return 1
140
141   nics = [val.split("/") for val in opts.nics.split(",")]
142   for row in nics:
143     while len(row) < 3:
144       row.append(None)
145     for i in range(3):
146       if row[i] == "":
147         row[i] = None
148   nic_dict = [{
149     constants.INIC_MAC: v[0],
150     constants.INIC_IP: v[1],
151     # The iallocator interface defines a "bridge" item
152     "bridge": v[2],
153     } for v in nics]
154
155   if opts.tags is None:
156     opts.tags = []
157   else:
158     opts.tags = opts.tags.split(",")
159   if opts.target_groups is None:
160     target_groups = []
161   else:
162     target_groups = opts.target_groups
163
164   op = opcodes.OpTestAllocator(mode=opts.mode,
165                                name=args[0],
166                                instances=args,
167                                memory=opts.memory,
168                                disks=disks,
169                                disk_template=opts.disk_template,
170                                nics=nic_dict,
171                                os=opts.os,
172                                vcpus=opts.vcpus,
173                                tags=opts.tags,
174                                direction=opts.direction,
175                                iallocator=opts.iallocator,
176                                evac_mode=opts.evac_mode,
177                                target_groups=target_groups,
178                                spindle_use=opts.spindle_use,
179                                count=opts.count)
180   result = SubmitOpCode(op, opts=opts)
181   ToStdout("%s" % result)
182   return 0
183
184
185 def _TestJobDependency(opts):
186   """Tests job dependencies.
187
188   """
189   ToStdout("Testing job dependencies")
190
191   cl = cli.GetClient()
192
193   try:
194     SubmitOpCode(opcodes.OpTestDelay(duration=0, depends=[(-1, None)]), cl=cl)
195   except errors.GenericError, err:
196     if opts.debug:
197       ToStdout("Ignoring error for 'wrong dependencies' test: %s", err)
198   else:
199     raise errors.OpExecError("Submitting plain opcode with relative job ID"
200                              " did not fail as expected")
201
202   # TODO: Test dependencies on errors
203   jobs = [
204     [opcodes.OpTestDelay(duration=1)],
205     [opcodes.OpTestDelay(duration=1,
206                          depends=[(-1, [])])],
207     [opcodes.OpTestDelay(duration=1,
208                          depends=[(-2, [constants.JOB_STATUS_SUCCESS])])],
209     [opcodes.OpTestDelay(duration=1,
210                          depends=[])],
211     [opcodes.OpTestDelay(duration=1,
212                          depends=[(-2, [constants.JOB_STATUS_SUCCESS])])],
213     ]
214
215   # Function for checking result
216   check_fn = ht.TListOf(ht.TAnd(ht.TIsLength(2),
217                                 ht.TItems([ht.TBool,
218                                            ht.TOr(ht.TNonEmptyString,
219                                                   ht.TJobId)])))
220
221   result = cl.SubmitManyJobs(jobs)
222   if not check_fn(result):
223     raise errors.OpExecError("Job submission doesn't match %s: %s" %
224                              (check_fn, result))
225
226   # Wait for jobs to finish
227   jex = JobExecutor(cl=cl, opts=opts)
228
229   for (status, job_id) in result:
230     jex.AddJobId(None, status, job_id)
231
232   job_results = jex.GetResults()
233   if not compat.all(row[0] for row in job_results):
234     raise errors.OpExecError("At least one of the submitted jobs failed: %s" %
235                              job_results)
236
237   # Get details about jobs
238   data = cl.QueryJobs([job_id for (_, job_id) in result],
239                       ["id", "opexec", "ops"])
240   data_job_id = [job_id for (job_id, _, _) in data]
241   data_opexec = [opexec for (_, opexec, _) in data]
242   data_op = [[opcodes.OpCode.LoadOpCode(op) for op in ops]
243              for (_, _, ops) in data]
244
245   assert compat.all(not op.depends or len(op.depends) == 1
246                     for ops in data_op
247                     for op in ops)
248
249   # Check resolved job IDs in dependencies
250   for (job_idx, res_jobdep) in [(1, data_job_id[0]),
251                                 (2, data_job_id[0]),
252                                 (4, data_job_id[2])]:
253     if data_op[job_idx][0].depends[0][0] != res_jobdep:
254       raise errors.OpExecError("Job %s's opcode doesn't depend on correct job"
255                                " ID (%s)" % (job_idx, res_jobdep))
256
257   # Check execution order
258   if not (data_opexec[0] <= data_opexec[1] and
259           data_opexec[0] <= data_opexec[2] and
260           data_opexec[2] <= data_opexec[4]):
261     raise errors.OpExecError("Jobs did not run in correct order: %s" % data)
262
263   assert len(jobs) == 5 and compat.all(len(ops) == 1 for ops in jobs)
264
265   ToStdout("Job dependency tests were successful")
266
267
268 def _TestJobSubmission(opts):
269   """Tests submitting jobs.
270
271   """
272   ToStdout("Testing job submission")
273
274   testdata = [
275     (0, 0, constants.OP_PRIO_LOWEST),
276     (0, 0, constants.OP_PRIO_HIGHEST),
277     ]
278
279   for priority in (constants.OP_PRIO_SUBMIT_VALID |
280                    frozenset([constants.OP_PRIO_LOWEST,
281                               constants.OP_PRIO_HIGHEST])):
282     for offset in [-1, +1]:
283       testdata.extend([
284         (0, 0, priority + offset),
285         (3, 0, priority + offset),
286         (0, 3, priority + offset),
287         (4, 2, priority + offset),
288         ])
289
290   cl = cli.GetClient()
291
292   for before, after, failpriority in testdata:
293     ops = []
294     ops.extend([opcodes.OpTestDelay(duration=0) for _ in range(before)])
295     ops.append(opcodes.OpTestDelay(duration=0, priority=failpriority))
296     ops.extend([opcodes.OpTestDelay(duration=0) for _ in range(after)])
297
298     try:
299       cl.SubmitJob(ops)
300     except errors.GenericError, err:
301       if opts.debug:
302         ToStdout("Ignoring error for 'wrong priority' test: %s", err)
303     else:
304       raise errors.OpExecError("Submitting opcode with priority %s did not"
305                                " fail when it should (allowed are %s)" %
306                                (failpriority, constants.OP_PRIO_SUBMIT_VALID))
307
308     jobs = [
309       [opcodes.OpTestDelay(duration=0),
310        opcodes.OpTestDelay(duration=0, dry_run=False),
311        opcodes.OpTestDelay(duration=0, dry_run=True)],
312       ops,
313       ]
314     result = cl.SubmitManyJobs(jobs)
315     if not (len(result) == 2 and
316             compat.all(len(i) == 2 for i in result) and
317             isinstance(result[0][1], int) and
318             isinstance(result[1][1], basestring) and
319             result[0][0] and not result[1][0]):
320       raise errors.OpExecError("Submitting multiple jobs did not work as"
321                                " expected, result %s" % result)
322     assert len(result) == 2
323
324   ToStdout("Job submission tests were successful")
325
326
327 class _JobQueueTestReporter(cli.StdioJobPollReportCb):
328   def __init__(self):
329     """Initializes this class.
330
331     """
332     cli.StdioJobPollReportCb.__init__(self)
333     self._expected_msgcount = 0
334     self._all_testmsgs = []
335     self._testmsgs = None
336     self._job_id = None
337
338   def GetTestMessages(self):
339     """Returns all test log messages received so far.
340
341     """
342     return self._all_testmsgs
343
344   def GetJobId(self):
345     """Returns the job ID.
346
347     """
348     return self._job_id
349
350   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
351     """Handles a log message.
352
353     """
354     if self._job_id is None:
355       self._job_id = job_id
356     elif self._job_id != job_id:
357       raise errors.ProgrammerError("The same reporter instance was used for"
358                                    " more than one job")
359
360     if log_type == constants.ELOG_JQUEUE_TEST:
361       (sockname, test, arg) = log_msg
362       return self._ProcessTestMessage(job_id, sockname, test, arg)
363
364     elif (log_type == constants.ELOG_MESSAGE and
365           log_msg.startswith(constants.JQT_MSGPREFIX)):
366       if self._testmsgs is None:
367         raise errors.OpExecError("Received test message without a preceding"
368                                  " start message")
369       testmsg = log_msg[len(constants.JQT_MSGPREFIX):]
370       self._testmsgs.append(testmsg)
371       self._all_testmsgs.append(testmsg)
372       return
373
374     return cli.StdioJobPollReportCb.ReportLogMessage(self, job_id, serial,
375                                                      timestamp, log_type,
376                                                      log_msg)
377
378   def _ProcessTestMessage(self, job_id, sockname, test, arg):
379     """Handles a job queue test message.
380
381     """
382     if test not in constants.JQT_ALL:
383       raise errors.OpExecError("Received invalid test message %s" % test)
384
385     sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
386     try:
387       sock.settimeout(30.0)
388
389       logging.debug("Connecting to %s", sockname)
390       sock.connect(sockname)
391
392       logging.debug("Checking status")
393       jobdetails = cli.GetClient().QueryJobs([job_id], ["status"])[0]
394       if not jobdetails:
395         raise errors.OpExecError("Can't find job %s" % job_id)
396
397       status = jobdetails[0]
398
399       logging.debug("Status of job %s is %s", job_id, status)
400
401       if test == constants.JQT_EXPANDNAMES:
402         if status != constants.JOB_STATUS_WAITING:
403           raise errors.OpExecError("Job status while expanding names is '%s',"
404                                    " not '%s' as expected" %
405                                    (status, constants.JOB_STATUS_WAITING))
406       elif test in (constants.JQT_EXEC, constants.JQT_LOGMSG):
407         if status != constants.JOB_STATUS_RUNNING:
408           raise errors.OpExecError("Job status while executing opcode is '%s',"
409                                    " not '%s' as expected" %
410                                    (status, constants.JOB_STATUS_RUNNING))
411
412       if test == constants.JQT_STARTMSG:
413         logging.debug("Expecting %s test messages", arg)
414         self._testmsgs = []
415       elif test == constants.JQT_LOGMSG:
416         if len(self._testmsgs) != arg:
417           raise errors.OpExecError("Received %s test messages when %s are"
418                                    " expected" % (len(self._testmsgs), arg))
419     finally:
420       logging.debug("Closing socket")
421       sock.close()
422
423
424 def TestJobqueue(opts, _):
425   """Runs a few tests on the job queue.
426
427   """
428   _TestJobSubmission(opts)
429   _TestJobDependency(opts)
430
431   (TM_SUCCESS,
432    TM_MULTISUCCESS,
433    TM_FAIL,
434    TM_PARTFAIL) = range(4)
435   TM_ALL = frozenset([TM_SUCCESS, TM_MULTISUCCESS, TM_FAIL, TM_PARTFAIL])
436
437   for mode in TM_ALL:
438     test_messages = [
439       "Testing mode %s" % mode,
440       "Hello World",
441       "A",
442       "",
443       "B"
444       "Foo|bar|baz",
445       utils.TimestampForFilename(),
446       ]
447
448     fail = mode in (TM_FAIL, TM_PARTFAIL)
449
450     if mode == TM_PARTFAIL:
451       ToStdout("Testing partial job failure")
452       ops = [
453         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
454                              log_messages=test_messages, fail=False),
455         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
456                              log_messages=test_messages, fail=False),
457         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
458                              log_messages=test_messages, fail=True),
459         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
460                              log_messages=test_messages, fail=False),
461         ]
462       expect_messages = 3 * [test_messages]
463       expect_opstatus = [
464         constants.OP_STATUS_SUCCESS,
465         constants.OP_STATUS_SUCCESS,
466         constants.OP_STATUS_ERROR,
467         constants.OP_STATUS_ERROR,
468         ]
469       expect_resultlen = 2
470     elif mode == TM_MULTISUCCESS:
471       ToStdout("Testing multiple successful opcodes")
472       ops = [
473         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
474                              log_messages=test_messages, fail=False),
475         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
476                              log_messages=test_messages, fail=False),
477         ]
478       expect_messages = 2 * [test_messages]
479       expect_opstatus = [
480         constants.OP_STATUS_SUCCESS,
481         constants.OP_STATUS_SUCCESS,
482         ]
483       expect_resultlen = 2
484     else:
485       if mode == TM_SUCCESS:
486         ToStdout("Testing job success")
487         expect_opstatus = [constants.OP_STATUS_SUCCESS]
488       elif mode == TM_FAIL:
489         ToStdout("Testing job failure")
490         expect_opstatus = [constants.OP_STATUS_ERROR]
491       else:
492         raise errors.ProgrammerError("Unknown test mode %s" % mode)
493
494       ops = [
495         opcodes.OpTestJqueue(notify_waitlock=True,
496                              notify_exec=True,
497                              log_messages=test_messages,
498                              fail=fail),
499         ]
500       expect_messages = [test_messages]
501       expect_resultlen = 1
502
503     cl = cli.GetClient()
504     cli.SetGenericOpcodeOpts(ops, opts)
505
506     # Send job to master daemon
507     job_id = cli.SendJob(ops, cl=cl)
508
509     reporter = _JobQueueTestReporter()
510     results = None
511
512     try:
513       results = cli.PollJob(job_id, cl=cl, reporter=reporter)
514     except errors.OpExecError, err:
515       if not fail:
516         raise
517       ToStdout("Ignoring error for 'job fail' test: %s", err)
518     else:
519       if fail:
520         raise errors.OpExecError("Job didn't fail when it should")
521
522     # Check length of result
523     if fail:
524       if results is not None:
525         raise errors.OpExecError("Received result from failed job")
526     elif len(results) != expect_resultlen:
527       raise errors.OpExecError("Received %s results (%s), expected %s" %
528                                (len(results), results, expect_resultlen))
529
530     # Check received log messages
531     all_messages = [i for j in expect_messages for i in j]
532     if reporter.GetTestMessages() != all_messages:
533       raise errors.OpExecError("Received test messages don't match input"
534                                " (input %r, received %r)" %
535                                (all_messages, reporter.GetTestMessages()))
536
537     # Check final status
538     reported_job_id = reporter.GetJobId()
539     if reported_job_id != job_id:
540       raise errors.OpExecError("Reported job ID %s doesn't match"
541                                "submission job ID %s" %
542                                (reported_job_id, job_id))
543
544     jobdetails = cli.GetClient().QueryJobs([job_id], ["status", "opstatus"])[0]
545     if not jobdetails:
546       raise errors.OpExecError("Can't find job %s" % job_id)
547
548     if fail:
549       exp_status = constants.JOB_STATUS_ERROR
550     else:
551       exp_status = constants.JOB_STATUS_SUCCESS
552
553     (final_status, final_opstatus) = jobdetails
554     if final_status != exp_status:
555       raise errors.OpExecError("Final job status is %s, not %s as expected" %
556                                (final_status, exp_status))
557     if len(final_opstatus) != len(ops):
558       raise errors.OpExecError("Did not receive status for all opcodes (got %s,"
559                                " expected %s)" %
560                                (len(final_opstatus), len(ops)))
561     if final_opstatus != expect_opstatus:
562       raise errors.OpExecError("Opcode status is %s, expected %s" %
563                                (final_opstatus, expect_opstatus))
564
565   ToStdout("Job queue test successful")
566
567   return 0
568
569
570 def ListLocks(opts, args): # pylint: disable=W0613
571   """List all locks.
572
573   @param opts: the command line options selected by the user
574   @type args: list
575   @param args: should be an empty list
576   @rtype: int
577   @return: the desired exit code
578
579   """
580   selected_fields = ParseFields(opts.output, _LIST_LOCKS_DEF_FIELDS)
581
582   def _DashIfNone(fn):
583     def wrapper(value):
584       if not value:
585         return "-"
586       return fn(value)
587     return wrapper
588
589   def _FormatPending(value):
590     """Format pending acquires.
591
592     """
593     return utils.CommaJoin("%s:%s" % (mode, ",".join(threads))
594                            for mode, threads in value)
595
596   # Format raw values
597   fmtoverride = {
598     "mode": (_DashIfNone(str), False),
599     "owner": (_DashIfNone(",".join), False),
600     "pending": (_DashIfNone(_FormatPending), False),
601     }
602
603   while True:
604     ret = GenericList(constants.QR_LOCK, selected_fields, None, None,
605                       opts.separator, not opts.no_headers,
606                       format_override=fmtoverride, verbose=opts.verbose)
607
608     if ret != constants.EXIT_SUCCESS:
609       return ret
610
611     if not opts.interval:
612       break
613
614     ToStdout("")
615     time.sleep(opts.interval)
616
617   return 0
618
619
620 commands = {
621   "delay": (
622     Delay, [ArgUnknown(min=1, max=1)],
623     [cli_option("--no-master", dest="on_master", default=True,
624                 action="store_false", help="Do not sleep in the master code"),
625      cli_option("-n", dest="on_nodes", default=[],
626                 action="append", help="Select nodes to sleep on"),
627      cli_option("-r", "--repeat", type="int", default="0", dest="repeat",
628                 help="Number of times to repeat the sleep"),
629      DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT,
630      ],
631     "[opts...] <duration>", "Executes a TestDelay OpCode"),
632   "submit-job": (
633     GenericOpCodes, [ArgFile(min=1)],
634     [VERBOSE_OPT,
635      cli_option("--op-repeat", type="int", default="1", dest="rep_op",
636                 help="Repeat the opcode sequence this number of times"),
637      cli_option("--job-repeat", type="int", default="1", dest="rep_job",
638                 help="Repeat the job this number of times"),
639      cli_option("--timing-stats", default=False,
640                 action="store_true", help="Show timing stats"),
641      cli_option("--each", default=False, action="store_true",
642                 help="Submit each job separately"),
643      DRY_RUN_OPT, PRIORITY_OPT,
644      ],
645     "<op_list_file...>", "Submits jobs built from json files"
646     " containing a list of serialized opcodes"),
647   "iallocator": (
648     TestAllocator, [ArgUnknown(min=1)],
649     [cli_option("--dir", dest="direction", default=constants.IALLOCATOR_DIR_IN,
650                 choices=list(constants.VALID_IALLOCATOR_DIRECTIONS),
651                 help="Show allocator input (in) or allocator"
652                 " results (out)"),
653      IALLOCATOR_OPT,
654      cli_option("-m", "--mode", default="relocate",
655                 choices=list(constants.VALID_IALLOCATOR_MODES),
656                 help=("Request mode (one of %s)" %
657                       utils.CommaJoin(constants.VALID_IALLOCATOR_MODES))),
658      cli_option("--memory", default=128, type="unit",
659                 help="Memory size for the instance (MiB)"),
660      cli_option("--disks", default="4096,4096",
661                 help="Comma separated list of disk sizes (MiB)"),
662      DISK_TEMPLATE_OPT,
663      cli_option("--nics", default="00:11:22:33:44:55",
664                 help="Comma separated list of nics, each nic"
665                 " definition is of form mac/ip/bridge, if"
666                 " missing values are replace by None"),
667      OS_OPT,
668      cli_option("-p", "--vcpus", default=1, type="int",
669                 help="Select number of VCPUs for the instance"),
670      cli_option("--tags", default=None,
671                 help="Comma separated list of tags"),
672      cli_option("--evac-mode", default=constants.IALLOCATOR_NEVAC_ALL,
673                 choices=list(constants.IALLOCATOR_NEVAC_MODES),
674                 help=("Node evacuation mode (one of %s)" %
675                       utils.CommaJoin(constants.IALLOCATOR_NEVAC_MODES))),
676      cli_option("--target-groups", help="Target groups for relocation",
677                 default=[], action="append"),
678      cli_option("--spindle-use", help="How many spindles to use",
679                 default=1, type="int"),
680      cli_option("--count", help="How many instances to allocate",
681                 default=2, type="int"),
682      DRY_RUN_OPT, PRIORITY_OPT,
683      ],
684     "{opts...} <instance>", "Executes a TestAllocator OpCode"),
685   "test-jobqueue": (
686     TestJobqueue, ARGS_NONE, [PRIORITY_OPT],
687     "", "Test a few aspects of the job queue"),
688   "locks": (
689     ListLocks, ARGS_NONE,
690     [NOHDR_OPT, SEP_OPT, FIELDS_OPT, INTERVAL_OPT, VERBOSE_OPT],
691     "[--interval N]", "Show a list of locks in the master daemon"),
692   }
693
694 #: dictionary with aliases for commands
695 aliases = {
696   "allocator": "iallocator",
697   }
698
699
700 def Main():
701   return GenericMain(commands, aliases=aliases)