(2.9) Make gnt-debug locks display fake job locks properly
[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 = compat.UniqueFrozenset([
436     TM_SUCCESS,
437     TM_MULTISUCCESS,
438     TM_FAIL,
439     TM_PARTFAIL,
440     ])
441
442   for mode in TM_ALL:
443     test_messages = [
444       "Testing mode %s" % mode,
445       "Hello World",
446       "A",
447       "",
448       "B"
449       "Foo|bar|baz",
450       utils.TimestampForFilename(),
451       ]
452
453     fail = mode in (TM_FAIL, TM_PARTFAIL)
454
455     if mode == TM_PARTFAIL:
456       ToStdout("Testing partial job failure")
457       ops = [
458         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
459                              log_messages=test_messages, fail=False),
460         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
461                              log_messages=test_messages, fail=False),
462         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
463                              log_messages=test_messages, fail=True),
464         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
465                              log_messages=test_messages, fail=False),
466         ]
467       expect_messages = 3 * [test_messages]
468       expect_opstatus = [
469         constants.OP_STATUS_SUCCESS,
470         constants.OP_STATUS_SUCCESS,
471         constants.OP_STATUS_ERROR,
472         constants.OP_STATUS_ERROR,
473         ]
474       expect_resultlen = 2
475     elif mode == TM_MULTISUCCESS:
476       ToStdout("Testing multiple successful opcodes")
477       ops = [
478         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
479                              log_messages=test_messages, fail=False),
480         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
481                              log_messages=test_messages, fail=False),
482         ]
483       expect_messages = 2 * [test_messages]
484       expect_opstatus = [
485         constants.OP_STATUS_SUCCESS,
486         constants.OP_STATUS_SUCCESS,
487         ]
488       expect_resultlen = 2
489     else:
490       if mode == TM_SUCCESS:
491         ToStdout("Testing job success")
492         expect_opstatus = [constants.OP_STATUS_SUCCESS]
493       elif mode == TM_FAIL:
494         ToStdout("Testing job failure")
495         expect_opstatus = [constants.OP_STATUS_ERROR]
496       else:
497         raise errors.ProgrammerError("Unknown test mode %s" % mode)
498
499       ops = [
500         opcodes.OpTestJqueue(notify_waitlock=True,
501                              notify_exec=True,
502                              log_messages=test_messages,
503                              fail=fail),
504         ]
505       expect_messages = [test_messages]
506       expect_resultlen = 1
507
508     cl = cli.GetClient()
509     cli.SetGenericOpcodeOpts(ops, opts)
510
511     # Send job to master daemon
512     job_id = cli.SendJob(ops, cl=cl)
513
514     reporter = _JobQueueTestReporter()
515     results = None
516
517     try:
518       results = cli.PollJob(job_id, cl=cl, reporter=reporter)
519     except errors.OpExecError, err:
520       if not fail:
521         raise
522       ToStdout("Ignoring error for 'job fail' test: %s", err)
523     else:
524       if fail:
525         raise errors.OpExecError("Job didn't fail when it should")
526
527     # Check length of result
528     if fail:
529       if results is not None:
530         raise errors.OpExecError("Received result from failed job")
531     elif len(results) != expect_resultlen:
532       raise errors.OpExecError("Received %s results (%s), expected %s" %
533                                (len(results), results, expect_resultlen))
534
535     # Check received log messages
536     all_messages = [i for j in expect_messages for i in j]
537     if reporter.GetTestMessages() != all_messages:
538       raise errors.OpExecError("Received test messages don't match input"
539                                " (input %r, received %r)" %
540                                (all_messages, reporter.GetTestMessages()))
541
542     # Check final status
543     reported_job_id = reporter.GetJobId()
544     if reported_job_id != job_id:
545       raise errors.OpExecError("Reported job ID %s doesn't match"
546                                "submission job ID %s" %
547                                (reported_job_id, job_id))
548
549     jobdetails = cli.GetClient().QueryJobs([job_id], ["status", "opstatus"])[0]
550     if not jobdetails:
551       raise errors.OpExecError("Can't find job %s" % job_id)
552
553     if fail:
554       exp_status = constants.JOB_STATUS_ERROR
555     else:
556       exp_status = constants.JOB_STATUS_SUCCESS
557
558     (final_status, final_opstatus) = jobdetails
559     if final_status != exp_status:
560       raise errors.OpExecError("Final job status is %s, not %s as expected" %
561                                (final_status, exp_status))
562     if len(final_opstatus) != len(ops):
563       raise errors.OpExecError("Did not receive status for all opcodes (got %s,"
564                                " expected %s)" %
565                                (len(final_opstatus), len(ops)))
566     if final_opstatus != expect_opstatus:
567       raise errors.OpExecError("Opcode status is %s, expected %s" %
568                                (final_opstatus, expect_opstatus))
569
570   ToStdout("Job queue test successful")
571
572   return 0
573
574
575 def ListLocks(opts, args): # pylint: disable=W0613
576   """List all locks.
577
578   @param opts: the command line options selected by the user
579   @type args: list
580   @param args: should be an empty list
581   @rtype: int
582   @return: the desired exit code
583
584   """
585   selected_fields = ParseFields(opts.output, _LIST_LOCKS_DEF_FIELDS)
586
587   def _DashIfNone(fn):
588     def wrapper(value):
589       if not value:
590         return "-"
591       return fn(value)
592     return wrapper
593
594   def _FormatPending(value):
595     """Format pending acquires.
596
597     """
598     return utils.CommaJoin("%s:%s" % (mode, ",".join(map(str, threads)))
599                            for mode, threads in value)
600
601   # Format raw values
602   fmtoverride = {
603     "mode": (_DashIfNone(str), False),
604     "owner": (_DashIfNone(",".join), False),
605     "pending": (_DashIfNone(_FormatPending), False),
606     }
607
608   while True:
609     ret = GenericList(constants.QR_LOCK, selected_fields, None, None,
610                       opts.separator, not opts.no_headers,
611                       format_override=fmtoverride, verbose=opts.verbose)
612
613     if ret != constants.EXIT_SUCCESS:
614       return ret
615
616     if not opts.interval:
617       break
618
619     ToStdout("")
620     time.sleep(opts.interval)
621
622   return 0
623
624
625 commands = {
626   "delay": (
627     Delay, [ArgUnknown(min=1, max=1)],
628     [cli_option("--no-master", dest="on_master", default=True,
629                 action="store_false", help="Do not sleep in the master code"),
630      cli_option("-n", dest="on_nodes", default=[],
631                 action="append", help="Select nodes to sleep on"),
632      cli_option("-r", "--repeat", type="int", default="0", dest="repeat",
633                 help="Number of times to repeat the sleep"),
634      DRY_RUN_OPT, PRIORITY_OPT, SUBMIT_OPT,
635      ],
636     "[opts...] <duration>", "Executes a TestDelay OpCode"),
637   "submit-job": (
638     GenericOpCodes, [ArgFile(min=1)],
639     [VERBOSE_OPT,
640      cli_option("--op-repeat", type="int", default="1", dest="rep_op",
641                 help="Repeat the opcode sequence this number of times"),
642      cli_option("--job-repeat", type="int", default="1", dest="rep_job",
643                 help="Repeat the job this number of times"),
644      cli_option("--timing-stats", default=False,
645                 action="store_true", help="Show timing stats"),
646      cli_option("--each", default=False, action="store_true",
647                 help="Submit each job separately"),
648      DRY_RUN_OPT, PRIORITY_OPT,
649      ],
650     "<op_list_file...>", "Submits jobs built from json files"
651     " containing a list of serialized opcodes"),
652   "iallocator": (
653     TestAllocator, [ArgUnknown(min=1)],
654     [cli_option("--dir", dest="direction", default=constants.IALLOCATOR_DIR_IN,
655                 choices=list(constants.VALID_IALLOCATOR_DIRECTIONS),
656                 help="Show allocator input (in) or allocator"
657                 " results (out)"),
658      IALLOCATOR_OPT,
659      cli_option("-m", "--mode", default="relocate",
660                 choices=list(constants.VALID_IALLOCATOR_MODES),
661                 help=("Request mode (one of %s)" %
662                       utils.CommaJoin(constants.VALID_IALLOCATOR_MODES))),
663      cli_option("--memory", default=128, type="unit",
664                 help="Memory size for the instance (MiB)"),
665      cli_option("--disks", default="4096,4096",
666                 help="Comma separated list of disk sizes (MiB)"),
667      DISK_TEMPLATE_OPT,
668      cli_option("--nics", default="00:11:22:33:44:55",
669                 help="Comma separated list of nics, each nic"
670                 " definition is of form mac/ip/bridge, if"
671                 " missing values are replace by None"),
672      OS_OPT,
673      cli_option("-p", "--vcpus", default=1, type="int",
674                 help="Select number of VCPUs for the instance"),
675      cli_option("--tags", default=None,
676                 help="Comma separated list of tags"),
677      cli_option("--evac-mode", default=constants.IALLOCATOR_NEVAC_ALL,
678                 choices=list(constants.IALLOCATOR_NEVAC_MODES),
679                 help=("Node evacuation mode (one of %s)" %
680                       utils.CommaJoin(constants.IALLOCATOR_NEVAC_MODES))),
681      cli_option("--target-groups", help="Target groups for relocation",
682                 default=[], action="append"),
683      cli_option("--spindle-use", help="How many spindles to use",
684                 default=1, type="int"),
685      cli_option("--count", help="How many instances to allocate",
686                 default=2, type="int"),
687      DRY_RUN_OPT, PRIORITY_OPT,
688      ],
689     "{opts...} <instance>", "Executes a TestAllocator OpCode"),
690   "test-jobqueue": (
691     TestJobqueue, ARGS_NONE, [PRIORITY_OPT],
692     "", "Test a few aspects of the job queue"),
693   "locks": (
694     ListLocks, ARGS_NONE,
695     [NOHDR_OPT, SEP_OPT, FIELDS_OPT, INTERVAL_OPT, VERBOSE_OPT],
696     "[--interval N]", "Show a list of locks in the master daemon"),
697   }
698
699 #: dictionary with aliases for commands
700 aliases = {
701   "allocator": "iallocator",
702   }
703
704
705 def Main():
706   return GenericMain(commands, aliases=aliases)