Make it possible to pass in flags using ENV variables
[ganeti-local] / lib / client / gnt_debug.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2010, 2011 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   SubmitOpCode(op, opts=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                                allocator=opts.iallocator,
176                                evac_mode=opts.evac_mode,
177                                target_groups=target_groups)
178   result = SubmitOpCode(op, opts=opts)
179   ToStdout("%s" % result)
180   return 0
181
182
183 def _TestJobDependency(opts):
184   """Tests job dependencies.
185
186   """
187   ToStdout("Testing job dependencies")
188
189   cl = cli.GetClient()
190
191   try:
192     SubmitOpCode(opcodes.OpTestDelay(duration=0, depends=[(-1, None)]), cl=cl)
193   except errors.GenericError, err:
194     if opts.debug:
195       ToStdout("Ignoring error: %s", err)
196   else:
197     raise errors.OpExecError("Submitting plain opcode with relative job ID"
198                              " did not fail as expected")
199
200   # TODO: Test dependencies on errors
201   jobs = [
202     [opcodes.OpTestDelay(duration=1)],
203     [opcodes.OpTestDelay(duration=1,
204                          depends=[(-1, [])])],
205     [opcodes.OpTestDelay(duration=1,
206                          depends=[(-2, [constants.JOB_STATUS_SUCCESS])])],
207     [opcodes.OpTestDelay(duration=1,
208                          depends=[])],
209     [opcodes.OpTestDelay(duration=1,
210                          depends=[(-2, [constants.JOB_STATUS_SUCCESS])])],
211     ]
212
213   # Function for checking result
214   check_fn = ht.TListOf(ht.TAnd(ht.TIsLength(2),
215                                 ht.TItems([ht.TBool,
216                                            ht.TOr(ht.TNonEmptyString,
217                                                   ht.TJobId)])))
218
219   result = cl.SubmitManyJobs(jobs)
220   if not check_fn(result):
221     raise errors.OpExecError("Job submission doesn't match %s: %s" %
222                              (check_fn, result))
223
224   # Wait for jobs to finish
225   jex = JobExecutor(cl=cl, opts=opts)
226
227   for (status, job_id) in result:
228     jex.AddJobId(None, status, job_id)
229
230   job_results = jex.GetResults()
231   if not compat.all(row[0] for row in job_results):
232     raise errors.OpExecError("At least one of the submitted jobs failed: %s" %
233                              job_results)
234
235   # Get details about jobs
236   data = cl.QueryJobs([job_id for (_, job_id) in result],
237                       ["id", "opexec", "ops"])
238   data_job_id = [job_id for (job_id, _, _) in data]
239   data_opexec = [opexec for (_, opexec, _) in data]
240   data_op = [[opcodes.OpCode.LoadOpCode(op) for op in ops]
241              for (_, _, ops) in data]
242
243   assert compat.all(not op.depends or len(op.depends) == 1
244                     for ops in data_op
245                     for op in ops)
246
247   # Check resolved job IDs in dependencies
248   for (job_idx, res_jobdep) in [(1, data_job_id[0]),
249                                 (2, data_job_id[0]),
250                                 (4, data_job_id[2])]:
251     if data_op[job_idx][0].depends[0][0] != res_jobdep:
252       raise errors.OpExecError("Job %s's opcode doesn't depend on correct job"
253                                " ID (%s)" % (job_idx, res_jobdep))
254
255   # Check execution order
256   if not (data_opexec[0] <= data_opexec[1] and
257           data_opexec[0] <= data_opexec[2] and
258           data_opexec[2] <= data_opexec[4]):
259     raise errors.OpExecError("Jobs did not run in correct order: %s" % data)
260
261   assert len(jobs) == 5 and compat.all(len(ops) == 1 for ops in jobs)
262
263   ToStdout("Job dependency tests were successful")
264
265
266 def _TestJobSubmission(opts):
267   """Tests submitting jobs.
268
269   """
270   ToStdout("Testing job submission")
271
272   testdata = [
273     (0, 0, constants.OP_PRIO_LOWEST),
274     (0, 0, constants.OP_PRIO_HIGHEST),
275     ]
276
277   for priority in (constants.OP_PRIO_SUBMIT_VALID |
278                    frozenset([constants.OP_PRIO_LOWEST,
279                               constants.OP_PRIO_HIGHEST])):
280     for offset in [-1, +1]:
281       testdata.extend([
282         (0, 0, priority + offset),
283         (3, 0, priority + offset),
284         (0, 3, priority + offset),
285         (4, 2, priority + offset),
286         ])
287
288   cl = cli.GetClient()
289
290   for before, after, failpriority in testdata:
291     ops = []
292     ops.extend([opcodes.OpTestDelay(duration=0) for _ in range(before)])
293     ops.append(opcodes.OpTestDelay(duration=0, priority=failpriority))
294     ops.extend([opcodes.OpTestDelay(duration=0) for _ in range(after)])
295
296     try:
297       cl.SubmitJob(ops)
298     except errors.GenericError, err:
299       if opts.debug:
300         ToStdout("Ignoring error: %s", err)
301     else:
302       raise errors.OpExecError("Submitting opcode with priority %s did not"
303                                " fail when it should (allowed are %s)" %
304                                (failpriority, constants.OP_PRIO_SUBMIT_VALID))
305
306     jobs = [
307       [opcodes.OpTestDelay(duration=0),
308        opcodes.OpTestDelay(duration=0, dry_run=False),
309        opcodes.OpTestDelay(duration=0, dry_run=True)],
310       ops,
311       ]
312     result = cl.SubmitManyJobs(jobs)
313     if not (len(result) == 2 and
314             compat.all(len(i) == 2 for i in result) and
315             compat.all(isinstance(i[1], basestring) for i in result) and
316             result[0][0] and not result[1][0]):
317       raise errors.OpExecError("Submitting multiple jobs did not work as"
318                                " expected, result %s" % result)
319     assert len(result) == 2
320
321   ToStdout("Job submission tests were successful")
322
323
324 class _JobQueueTestReporter(cli.StdioJobPollReportCb):
325   def __init__(self):
326     """Initializes this class.
327
328     """
329     cli.StdioJobPollReportCb.__init__(self)
330     self._expected_msgcount = 0
331     self._all_testmsgs = []
332     self._testmsgs = None
333     self._job_id = None
334
335   def GetTestMessages(self):
336     """Returns all test log messages received so far.
337
338     """
339     return self._all_testmsgs
340
341   def GetJobId(self):
342     """Returns the job ID.
343
344     """
345     return self._job_id
346
347   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
348     """Handles a log message.
349
350     """
351     if self._job_id is None:
352       self._job_id = job_id
353     elif self._job_id != job_id:
354       raise errors.ProgrammerError("The same reporter instance was used for"
355                                    " more than one job")
356
357     if log_type == constants.ELOG_JQUEUE_TEST:
358       (sockname, test, arg) = log_msg
359       return self._ProcessTestMessage(job_id, sockname, test, arg)
360
361     elif (log_type == constants.ELOG_MESSAGE and
362           log_msg.startswith(constants.JQT_MSGPREFIX)):
363       if self._testmsgs is None:
364         raise errors.OpExecError("Received test message without a preceding"
365                                  " start message")
366       testmsg = log_msg[len(constants.JQT_MSGPREFIX):]
367       self._testmsgs.append(testmsg)
368       self._all_testmsgs.append(testmsg)
369       return
370
371     return cli.StdioJobPollReportCb.ReportLogMessage(self, job_id, serial,
372                                                      timestamp, log_type,
373                                                      log_msg)
374
375   def _ProcessTestMessage(self, job_id, sockname, test, arg):
376     """Handles a job queue test message.
377
378     """
379     if test not in constants.JQT_ALL:
380       raise errors.OpExecError("Received invalid test message %s" % test)
381
382     sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
383     try:
384       sock.settimeout(30.0)
385
386       logging.debug("Connecting to %s", sockname)
387       sock.connect(sockname)
388
389       logging.debug("Checking status")
390       jobdetails = cli.GetClient().QueryJobs([job_id], ["status"])[0]
391       if not jobdetails:
392         raise errors.OpExecError("Can't find job %s" % job_id)
393
394       status = jobdetails[0]
395
396       logging.debug("Status of job %s is %s", job_id, status)
397
398       if test == constants.JQT_EXPANDNAMES:
399         if status != constants.JOB_STATUS_WAITING:
400           raise errors.OpExecError("Job status while expanding names is '%s',"
401                                    " not '%s' as expected" %
402                                    (status, constants.JOB_STATUS_WAITING))
403       elif test in (constants.JQT_EXEC, constants.JQT_LOGMSG):
404         if status != constants.JOB_STATUS_RUNNING:
405           raise errors.OpExecError("Job status while executing opcode is '%s',"
406                                    " not '%s' as expected" %
407                                    (status, constants.JOB_STATUS_RUNNING))
408
409       if test == constants.JQT_STARTMSG:
410         logging.debug("Expecting %s test messages", arg)
411         self._testmsgs = []
412       elif test == constants.JQT_LOGMSG:
413         if len(self._testmsgs) != arg:
414           raise errors.OpExecError("Received %s test messages when %s are"
415                                    " expected" % (len(self._testmsgs), arg))
416     finally:
417       logging.debug("Closing socket")
418       sock.close()
419
420
421 def TestJobqueue(opts, _):
422   """Runs a few tests on the job queue.
423
424   """
425   _TestJobSubmission(opts)
426   _TestJobDependency(opts)
427
428   (TM_SUCCESS,
429    TM_MULTISUCCESS,
430    TM_FAIL,
431    TM_PARTFAIL) = range(4)
432   TM_ALL = frozenset([TM_SUCCESS, TM_MULTISUCCESS, TM_FAIL, TM_PARTFAIL])
433
434   for mode in TM_ALL:
435     test_messages = [
436       "Testing mode %s" % mode,
437       "Hello World",
438       "A",
439       "",
440       "B"
441       "Foo|bar|baz",
442       utils.TimestampForFilename(),
443       ]
444
445     fail = mode in (TM_FAIL, TM_PARTFAIL)
446
447     if mode == TM_PARTFAIL:
448       ToStdout("Testing partial job failure")
449       ops = [
450         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
451                              log_messages=test_messages, fail=False),
452         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
453                              log_messages=test_messages, fail=False),
454         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
455                              log_messages=test_messages, fail=True),
456         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
457                              log_messages=test_messages, fail=False),
458         ]
459       expect_messages = 3 * [test_messages]
460       expect_opstatus = [
461         constants.OP_STATUS_SUCCESS,
462         constants.OP_STATUS_SUCCESS,
463         constants.OP_STATUS_ERROR,
464         constants.OP_STATUS_ERROR,
465         ]
466       expect_resultlen = 2
467     elif mode == TM_MULTISUCCESS:
468       ToStdout("Testing multiple successful opcodes")
469       ops = [
470         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
471                              log_messages=test_messages, fail=False),
472         opcodes.OpTestJqueue(notify_waitlock=True, notify_exec=True,
473                              log_messages=test_messages, fail=False),
474         ]
475       expect_messages = 2 * [test_messages]
476       expect_opstatus = [
477         constants.OP_STATUS_SUCCESS,
478         constants.OP_STATUS_SUCCESS,
479         ]
480       expect_resultlen = 2
481     else:
482       if mode == TM_SUCCESS:
483         ToStdout("Testing job success")
484         expect_opstatus = [constants.OP_STATUS_SUCCESS]
485       elif mode == TM_FAIL:
486         ToStdout("Testing job failure")
487         expect_opstatus = [constants.OP_STATUS_ERROR]
488       else:
489         raise errors.ProgrammerError("Unknown test mode %s" % mode)
490
491       ops = [
492         opcodes.OpTestJqueue(notify_waitlock=True,
493                              notify_exec=True,
494                              log_messages=test_messages,
495                              fail=fail)
496         ]
497       expect_messages = [test_messages]
498       expect_resultlen = 1
499
500     cl = cli.GetClient()
501     cli.SetGenericOpcodeOpts(ops, opts)
502
503     # Send job to master daemon
504     job_id = cli.SendJob(ops, cl=cl)
505
506     reporter = _JobQueueTestReporter()
507     results = None
508
509     try:
510       results = cli.PollJob(job_id, cl=cl, reporter=reporter)
511     except errors.OpExecError, err:
512       if not fail:
513         raise
514       ToStdout("Ignoring error: %s", err)
515     else:
516       if fail:
517         raise errors.OpExecError("Job didn't fail when it should")
518
519     # Check length of result
520     if fail:
521       if results is not None:
522         raise errors.OpExecError("Received result from failed job")
523     elif len(results) != expect_resultlen:
524       raise errors.OpExecError("Received %s results (%s), expected %s" %
525                                (len(results), results, expect_resultlen))
526
527     # Check received log messages
528     all_messages = [i for j in expect_messages for i in j]
529     if reporter.GetTestMessages() != all_messages:
530       raise errors.OpExecError("Received test messages don't match input"
531                                " (input %r, received %r)" %
532                                (all_messages, reporter.GetTestMessages()))
533
534     # Check final status
535     reported_job_id = reporter.GetJobId()
536     if reported_job_id != job_id:
537       raise errors.OpExecError("Reported job ID %s doesn't match"
538                                "submission job ID %s" %
539                                (reported_job_id, job_id))
540
541     jobdetails = cli.GetClient().QueryJobs([job_id], ["status", "opstatus"])[0]
542     if not jobdetails:
543       raise errors.OpExecError("Can't find job %s" % job_id)
544
545     if fail:
546       exp_status = constants.JOB_STATUS_ERROR
547     else:
548       exp_status = constants.JOB_STATUS_SUCCESS
549
550     (final_status, final_opstatus) = jobdetails
551     if final_status != exp_status:
552       raise errors.OpExecError("Final job status is %s, not %s as expected" %
553                                (final_status, exp_status))
554     if len(final_opstatus) != len(ops):
555       raise errors.OpExecError("Did not receive status for all opcodes (got %s,"
556                                " expected %s)" %
557                                (len(final_opstatus), len(ops)))
558     if final_opstatus != expect_opstatus:
559       raise errors.OpExecError("Opcode status is %s, expected %s" %
560                                (final_opstatus, expect_opstatus))
561
562   ToStdout("Job queue test successful")
563
564   return 0
565
566
567 def ListLocks(opts, args): # pylint: disable=W0613
568   """List all locks.
569
570   @param opts: the command line options selected by the user
571   @type args: list
572   @param args: should be an empty list
573   @rtype: int
574   @return: the desired exit code
575
576   """
577   selected_fields = ParseFields(opts.output, _LIST_LOCKS_DEF_FIELDS)
578
579   def _DashIfNone(fn):
580     def wrapper(value):
581       if not value:
582         return "-"
583       return fn(value)
584     return wrapper
585
586   def _FormatPending(value):
587     """Format pending acquires.
588
589     """
590     return utils.CommaJoin("%s:%s" % (mode, ",".join(threads))
591                            for mode, threads in value)
592
593   # Format raw values
594   fmtoverride = {
595     "mode": (_DashIfNone(str), False),
596     "owner": (_DashIfNone(",".join), False),
597     "pending": (_DashIfNone(_FormatPending), False),
598     }
599
600   while True:
601     ret = GenericList(constants.QR_LOCK, selected_fields, None, None,
602                       opts.separator, not opts.no_headers,
603                       format_override=fmtoverride, verbose=opts.verbose)
604
605     if ret != constants.EXIT_SUCCESS:
606       return ret
607
608     if not opts.interval:
609       break
610
611     ToStdout("")
612     time.sleep(opts.interval)
613
614   return 0
615
616
617 commands = {
618   "delay": (
619     Delay, [ArgUnknown(min=1, max=1)],
620     [cli_option("--no-master", dest="on_master", default=True,
621                 action="store_false", help="Do not sleep in the master code"),
622      cli_option("-n", dest="on_nodes", default=[],
623                 action="append", help="Select nodes to sleep on"),
624      cli_option("-r", "--repeat", type="int", default="0", dest="repeat",
625                 help="Number of times to repeat the sleep"),
626      DRY_RUN_OPT, PRIORITY_OPT,
627      ],
628     "[opts...] <duration>", "Executes a TestDelay OpCode"),
629   "submit-job": (
630     GenericOpCodes, [ArgFile(min=1)],
631     [VERBOSE_OPT,
632      cli_option("--op-repeat", type="int", default="1", dest="rep_op",
633                 help="Repeat the opcode sequence this number of times"),
634      cli_option("--job-repeat", type="int", default="1", dest="rep_job",
635                 help="Repeat the job this number of times"),
636      cli_option("--timing-stats", default=False,
637                 action="store_true", help="Show timing stats"),
638      cli_option("--each", default=False, action="store_true",
639                 help="Submit each job separately"),
640      DRY_RUN_OPT, PRIORITY_OPT,
641      ],
642     "<op_list_file...>", "Submits jobs built from json files"
643     " containing a list of serialized opcodes"),
644   "iallocator": (
645     TestAllocator, [ArgUnknown(min=1)],
646     [cli_option("--dir", dest="direction", default=constants.IALLOCATOR_DIR_IN,
647                 choices=list(constants.VALID_IALLOCATOR_DIRECTIONS),
648                 help="Show allocator input (in) or allocator"
649                 " results (out)"),
650      IALLOCATOR_OPT,
651      cli_option("-m", "--mode", default="relocate",
652                 choices=list(constants.VALID_IALLOCATOR_MODES),
653                 help=("Request mode (one of %s)" %
654                       utils.CommaJoin(constants.VALID_IALLOCATOR_MODES))),
655      cli_option("--memory", default=128, type="unit",
656                 help="Memory size for the instance (MiB)"),
657      cli_option("--disks", default="4096,4096",
658                 help="Comma separated list of disk sizes (MiB)"),
659      DISK_TEMPLATE_OPT,
660      cli_option("--nics", default="00:11:22:33:44:55",
661                 help="Comma separated list of nics, each nic"
662                 " definition is of form mac/ip/bridge, if"
663                 " missing values are replace by None"),
664      OS_OPT,
665      cli_option("-p", "--vcpus", default=1, type="int",
666                 help="Select number of VCPUs for the instance"),
667      cli_option("--tags", default=None,
668                 help="Comma separated list of tags"),
669      cli_option("--evac-mode", default=constants.IALLOCATOR_NEVAC_ALL,
670                 choices=list(constants.IALLOCATOR_NEVAC_MODES),
671                 help=("Node evacuation mode (one of %s)" %
672                       utils.CommaJoin(constants.IALLOCATOR_NEVAC_MODES))),
673      cli_option("--target-groups", help="Target groups for relocation",
674                 default=[], action="append"),
675      DRY_RUN_OPT, PRIORITY_OPT,
676      ],
677     "{opts...} <instance>", "Executes a TestAllocator OpCode"),
678   "test-jobqueue": (
679     TestJobqueue, ARGS_NONE, [PRIORITY_OPT],
680     "", "Test a few aspects of the job queue"),
681   "locks": (
682     ListLocks, ARGS_NONE,
683     [NOHDR_OPT, SEP_OPT, FIELDS_OPT, INTERVAL_OPT, VERBOSE_OPT],
684     "[--interval N]", "Show a list of locks in the master daemon"),
685   }
686
687 #: dictionary with aliases for commands
688 aliases = {
689   "allocator": "iallocator",
690   }
691
692
693 def Main():
694   return GenericMain(commands, aliases=aliases)