1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Instance related commands"""
22
23
24
25
26
27
28 import os
29 import itertools
30 import simplejson
31 from cStringIO import StringIO
32
33 from ganeti.cli import *
34 from ganeti import opcodes
35 from ganeti import constants
36 from ganeti import compat
37 from ganeti import utils
38 from ganeti import errors
39 from ganeti import netutils
40
41
42 _SHUTDOWN_CLUSTER = "cluster"
43 _SHUTDOWN_NODES_BOTH = "nodes"
44 _SHUTDOWN_NODES_PRI = "nodes-pri"
45 _SHUTDOWN_NODES_SEC = "nodes-sec"
46 _SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
47 _SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
48 _SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
49 _SHUTDOWN_INSTANCES = "instances"
50 _SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
51
52 _SHUTDOWN_NODES_TAGS_MODES = (
53 _SHUTDOWN_NODES_BOTH_BY_TAGS,
54 _SHUTDOWN_NODES_PRI_BY_TAGS,
55 _SHUTDOWN_NODES_SEC_BY_TAGS)
56
57
58 _VALUE_TRUE = "true"
59
60
61 _LIST_DEF_FIELDS = [
62 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
63 ]
64
65
67 """Expand the given names using the passed mode.
68
69 For _SHUTDOWN_CLUSTER, all instances will be returned. For
70 _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71 primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72 instances having those nodes as either primary or secondary will be
73 returned. For _SHUTDOWN_INSTANCES, the given instances will be
74 returned.
75
76 @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77 L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78 L{_SHUTDOWN_INSTANCES}
79 @param names: a list of names; for cluster, it must be empty,
80 and for node and instance it must be a list of valid item
81 names (short names are valid as usual, e.g. node1 instead of
82 node1.example.com)
83 @rtype: list
84 @return: the list of names after the expansion
85 @raise errors.ProgrammerError: for unknown selection type
86 @raise errors.OpPrereqError: for invalid input parameters
87
88 """
89
90
91 if client is None:
92 client = GetClient()
93 if mode == _SHUTDOWN_CLUSTER:
94 if names:
95 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
96 errors.ECODE_INVAL)
97 idata = client.QueryInstances([], ["name"], False)
98 inames = [row[0] for row in idata]
99
100 elif mode in (_SHUTDOWN_NODES_BOTH,
101 _SHUTDOWN_NODES_PRI,
102 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103 if mode in _SHUTDOWN_NODES_TAGS_MODES:
104 if not names:
105 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106 ndata = client.QueryNodes([], ["name", "pinst_list",
107 "sinst_list", "tags"], False)
108 ndata = [row for row in ndata if set(row[3]).intersection(names)]
109 else:
110 if not names:
111 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
113 False)
114
115 ipri = [row[1] for row in ndata]
116 pri_names = list(itertools.chain(*ipri))
117 isec = [row[2] for row in ndata]
118 sec_names = list(itertools.chain(*isec))
119 if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120 inames = pri_names + sec_names
121 elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
122 inames = pri_names
123 elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
124 inames = sec_names
125 else:
126 raise errors.ProgrammerError("Unhandled shutdown type")
127 elif mode == _SHUTDOWN_INSTANCES:
128 if not names:
129 raise errors.OpPrereqError("No instance names passed",
130 errors.ECODE_INVAL)
131 idata = client.QueryInstances(names, ["name"], False)
132 inames = [row[0] for row in idata]
133 elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
134 if not names:
135 raise errors.OpPrereqError("No instance tags passed",
136 errors.ECODE_INVAL)
137 idata = client.QueryInstances([], ["name", "tags"], False)
138 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
139 else:
140 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
141
142 return inames
143
144
146 """Ask the user to confirm an operation on a list of instances.
147
148 This function is used to request confirmation for doing an operation
149 on a given list of instances.
150
151 @type inames: list
152 @param inames: the list of names that we display when
153 we ask for confirmation
154 @type text: str
155 @param text: the operation that the user should confirm
156 (e.g. I{shutdown} or I{startup})
157 @rtype: boolean
158 @return: True or False depending on user's confirmation.
159
160 """
161 count = len(inames)
162 msg = ("The %s will operate on %d instances.\n%s"
163 "Do you want to continue?" % (text, count, extra))
164 affected = ("\nAffected instances:\n" +
165 "\n".join([" %s" % name for name in inames]))
166
167 choices = [('y', True, 'Yes, execute the %s' % text),
168 ('n', False, 'No, abort the %s' % text)]
169
170 if count > 20:
171 choices.insert(1, ('v', 'v', 'View the list of affected instances'))
172 ask = msg
173 else:
174 ask = msg + affected
175
176 choice = AskUser(ask, choices)
177 if choice == 'v':
178 choices.pop(1)
179 choice = AskUser(msg + affected, choices)
180 return choice
181
182
184 """Check for and ensure the given instance names exist.
185
186 This function will raise an OpPrereqError in case they don't
187 exist. Otherwise it will exit cleanly.
188
189 @type client: L{ganeti.luxi.Client}
190 @param client: the client to use for the query
191 @type names: list
192 @param names: the list of instance names to query
193 @raise errors.OpPrereqError: in case any instance is missing
194
195 """
196
197
198 result = client.QueryInstances(names, ["name"], False)
199 for orig_name, row in zip(names, result):
200 if row[0] is None:
201 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
202 errors.ECODE_NOENT)
203
204
206 """Generic multi-instance operations.
207
208 The will return a wrapper that processes the options and arguments
209 given, and uses the passed function to build the opcode needed for
210 the specific operation. Thus all the generic loop/confirmation code
211 is abstracted into this function.
212
213 """
214 def realfn(opts, args):
215 if opts.multi_mode is None:
216 opts.multi_mode = _SHUTDOWN_INSTANCES
217 cl = GetClient()
218 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
219 if not inames:
220 raise errors.OpPrereqError("Selection filter does not match"
221 " any instances", errors.ECODE_INVAL)
222 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
223 if not (opts.force_multi or not multi_on
224 or _ConfirmOperation(inames, operation)):
225 return 1
226 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
227 for name in inames:
228 op = fn(name, opts)
229 jex.QueueJob(name, op)
230 results = jex.WaitOrShow(not opts.submit_only)
231 rcode = compat.all(row[0] for row in results)
232 return int(not rcode)
233 return realfn
234
235
237 """List instances and their properties.
238
239 @param opts: the command line options selected by the user
240 @type args: list
241 @param args: should be an empty list
242 @rtype: int
243 @return: the desired exit code
244
245 """
246 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
247
248 output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
249
250 if not opts.no_headers:
251 headers = {
252 "name": "Instance", "os": "OS", "pnode": "Primary_node",
253 "snodes": "Secondary_Nodes", "admin_state": "Autostart",
254 "oper_state": "Running",
255 "oper_ram": "Memory", "disk_template": "Disk_template",
256 "oper_vcpus": "VCPUs",
257 "ip": "IP_address", "mac": "MAC_address",
258 "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
259 "bridge": "Bridge",
260 "sda_size": "Disk/0", "sdb_size": "Disk/1",
261 "disk_usage": "DiskUsage",
262 "status": "Status", "tags": "Tags",
263 "network_port": "Network_port",
264 "hv/kernel_path": "Kernel_path",
265 "hv/initrd_path": "Initrd_path",
266 "hv/boot_order": "Boot_order",
267 "hv/acpi": "ACPI",
268 "hv/pae": "PAE",
269 "hv/cdrom_image_path": "CDROM_image_path",
270 "hv/nic_type": "NIC_type",
271 "hv/disk_type": "Disk_type",
272 "hv/vnc_bind_address": "VNC_bind_address",
273 "serial_no": "SerialNo", "hypervisor": "Hypervisor",
274 "hvparams": "Hypervisor_parameters",
275 "be/memory": "Configured_memory",
276 "be/vcpus": "VCPUs",
277 "vcpus": "VCPUs",
278 "be/auto_balance": "Auto_balance",
279 "disk.count": "Disks", "disk.sizes": "Disk_sizes",
280 "nic.count": "NICs", "nic.ips": "NIC_IPs",
281 "nic.modes": "NIC_modes", "nic.links": "NIC_links",
282 "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
283 "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
284 }
285 else:
286 headers = None
287
288 unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
289 numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
290 "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
291
292 list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
293 "nic.modes", "nic.links", "nic.bridges")
294
295 for row in output:
296 for idx, field in enumerate(selected_fields):
297 val = row[idx]
298 if field == "snodes":
299 val = ",".join(val) or "-"
300 elif field == "admin_state":
301 if val:
302 val = "yes"
303 else:
304 val = "no"
305 elif field == "oper_state":
306 if val is None:
307 val = "(node down)"
308 elif val:
309 val = "running"
310 else:
311 val = "stopped"
312 elif field == "oper_ram":
313 if val is None:
314 val = "(node down)"
315 elif field == "oper_vcpus":
316 if val is None:
317 val = "(node down)"
318 elif field == "sda_size" or field == "sdb_size":
319 if val is None:
320 val = "N/A"
321 elif field == "ctime" or field == "mtime":
322 val = utils.FormatTime(val)
323 elif field in list_type_fields:
324 val = ",".join(str(item) for item in val)
325 elif val is None:
326 val = "-"
327 if opts.roman_integers and isinstance(val, int):
328 val = compat.TryToRoman(val)
329 row[idx] = str(val)
330
331 data = GenerateTable(separator=opts.separator, headers=headers,
332 fields=selected_fields, unitfields=unitfields,
333 numfields=numfields, data=output, units=opts.units)
334
335 for line in data:
336 ToStdout(line)
337
338 return 0
339
340
348
349
351 """Create instances using a definition file.
352
353 This function reads a json file with instances defined
354 in the form::
355
356 {"instance-name":{
357 "disk_size": [20480],
358 "template": "drbd",
359 "backend": {
360 "memory": 512,
361 "vcpus": 1 },
362 "os": "debootstrap",
363 "primary_node": "firstnode",
364 "secondary_node": "secondnode",
365 "iallocator": "dumb"}
366 }
367
368 Note that I{primary_node} and I{secondary_node} have precedence over
369 I{iallocator}.
370
371 @param opts: the command line options selected by the user
372 @type args: list
373 @param args: should contain one element, the json filename
374 @rtype: int
375 @return: the desired exit code
376
377 """
378 _DEFAULT_SPECS = {"disk_size": [20 * 1024],
379 "backend": {},
380 "iallocator": None,
381 "primary_node": None,
382 "secondary_node": None,
383 "nics": None,
384 "start": True,
385 "ip_check": True,
386 "name_check": True,
387 "hypervisor": None,
388 "hvparams": {},
389 "file_storage_dir": None,
390 "force_variant": False,
391 "file_driver": 'loop'}
392
393 def _PopulateWithDefaults(spec):
394 """Returns a new hash combined with default values."""
395 mydict = _DEFAULT_SPECS.copy()
396 mydict.update(spec)
397 return mydict
398
399 def _Validate(spec):
400 """Validate the instance specs."""
401
402 for required_field in ('os', 'template'):
403 if required_field not in spec:
404 raise errors.OpPrereqError('Required field "%s" is missing.' %
405 required_field, errors.ECODE_INVAL)
406
407 if spec['primary_node'] is not None:
408 if (spec['template'] in constants.DTS_NET_MIRROR and
409 spec['secondary_node'] is None):
410 raise errors.OpPrereqError('Template requires secondary node, but'
411 ' there was no secondary provided.',
412 errors.ECODE_INVAL)
413 elif spec['iallocator'] is None:
414 raise errors.OpPrereqError('You have to provide at least a primary_node'
415 ' or an iallocator.',
416 errors.ECODE_INVAL)
417
418 if (spec['hvparams'] and
419 not isinstance(spec['hvparams'], dict)):
420 raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
421 errors.ECODE_INVAL)
422
423 json_filename = args[0]
424 try:
425 instance_data = simplejson.loads(utils.ReadFile(json_filename))
426 except Exception, err:
427 ToStderr("Can't parse the instance definition file: %s" % str(err))
428 return 1
429
430 if not isinstance(instance_data, dict):
431 ToStderr("The instance definition file is not in dict format.")
432 return 1
433
434 jex = JobExecutor(opts=opts)
435
436
437
438
439 i_names = utils.NiceSort(instance_data.keys())
440 for name in i_names:
441 specs = instance_data[name]
442 specs = _PopulateWithDefaults(specs)
443 _Validate(specs)
444
445 hypervisor = specs['hypervisor']
446 hvparams = specs['hvparams']
447
448 disks = []
449 for elem in specs['disk_size']:
450 try:
451 size = utils.ParseUnit(elem)
452 except (TypeError, ValueError), err:
453 raise errors.OpPrereqError("Invalid disk size '%s' for"
454 " instance %s: %s" %
455 (elem, name, err), errors.ECODE_INVAL)
456 disks.append({"size": size})
457
458 utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
459 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
460
461 tmp_nics = []
462 for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
463 if field in specs:
464 if not tmp_nics:
465 tmp_nics.append({})
466 tmp_nics[0][field] = specs[field]
467
468 if specs['nics'] is not None and tmp_nics:
469 raise errors.OpPrereqError("'nics' list incompatible with using"
470 " individual nic fields as well",
471 errors.ECODE_INVAL)
472 elif specs['nics'] is not None:
473 tmp_nics = specs['nics']
474 elif not tmp_nics:
475 tmp_nics = [{}]
476
477 op = opcodes.OpCreateInstance(instance_name=name,
478 disks=disks,
479 disk_template=specs['template'],
480 mode=constants.INSTANCE_CREATE,
481 os_type=specs['os'],
482 force_variant=specs["force_variant"],
483 pnode=specs['primary_node'],
484 snode=specs['secondary_node'],
485 nics=tmp_nics,
486 start=specs['start'],
487 ip_check=specs['ip_check'],
488 name_check=specs['name_check'],
489 wait_for_sync=True,
490 iallocator=specs['iallocator'],
491 hypervisor=hypervisor,
492 hvparams=hvparams,
493 beparams=specs['backend'],
494 file_storage_dir=specs['file_storage_dir'],
495 file_driver=specs['file_driver'])
496
497 jex.QueueJob(name, op)
498
499 jex.WaitOrShow(False)
500
501 return 0
502
503
505 """Reinstall an instance.
506
507 @param opts: the command line options selected by the user
508 @type args: list
509 @param args: should contain only one element, the name of the
510 instance to be reinstalled
511 @rtype: int
512 @return: the desired exit code
513
514 """
515
516 if opts.multi_mode is None:
517 opts.multi_mode = _SHUTDOWN_INSTANCES
518
519 inames = _ExpandMultiNames(opts.multi_mode, args)
520 if not inames:
521 raise errors.OpPrereqError("Selection filter does not match any instances",
522 errors.ECODE_INVAL)
523
524
525 if opts.select_os is True:
526 op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
527 result = SubmitOpCode(op, opts=opts)
528
529 if not result:
530 ToStdout("Can't get the OS list")
531 return 1
532
533 ToStdout("Available OS templates:")
534 number = 0
535 choices = []
536 for (name, variants) in result:
537 for entry in CalculateOSNames(name, variants):
538 ToStdout("%3s: %s", number, entry)
539 choices.append(("%s" % number, entry, entry))
540 number += 1
541
542 choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
543 selected = AskUser("Enter OS template number (or x to abort):",
544 choices)
545
546 if selected == 'exit':
547 ToStderr("User aborted reinstall, exiting")
548 return 1
549
550 os_name = selected
551 else:
552 os_name = opts.os
553
554
555
556
557 multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
558 if multi_on:
559 warn_msg = "Note: this will remove *all* data for the below instances!\n"
560 if not (opts.force_multi or
561 _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
562 return 1
563 else:
564 if not (opts.force or opts.force_multi):
565 usertext = ("This will reinstall the instance %s and remove"
566 " all data. Continue?") % inames[0]
567 if not AskUser(usertext):
568 return 1
569
570 jex = JobExecutor(verbose=multi_on, opts=opts)
571 for instance_name in inames:
572 op = opcodes.OpReinstallInstance(instance_name=instance_name,
573 os_type=os_name,
574 force_variant=opts.force_variant,
575 osparams=opts.osparams)
576 jex.QueueJob(instance_name, op)
577
578 jex.WaitOrShow(not opts.submit_only)
579 return 0
580
581
583 """Remove an instance.
584
585 @param opts: the command line options selected by the user
586 @type args: list
587 @param args: should contain only one element, the name of
588 the instance to be removed
589 @rtype: int
590 @return: the desired exit code
591
592 """
593 instance_name = args[0]
594 force = opts.force
595 cl = GetClient()
596
597 if not force:
598 _EnsureInstancesExist(cl, [instance_name])
599
600 usertext = ("This will remove the volumes of the instance %s"
601 " (including mirrors), thus removing all the data"
602 " of the instance. Continue?") % instance_name
603 if not AskUser(usertext):
604 return 1
605
606 op = opcodes.OpRemoveInstance(instance_name=instance_name,
607 ignore_failures=opts.ignore_failures,
608 shutdown_timeout=opts.shutdown_timeout)
609 SubmitOrSend(op, opts, cl=cl)
610 return 0
611
612
614 """Rename an instance.
615
616 @param opts: the command line options selected by the user
617 @type args: list
618 @param args: should contain two elements, the old and the
619 new instance names
620 @rtype: int
621 @return: the desired exit code
622
623 """
624 if not opts.name_check:
625 if not AskUser("As you disabled the check of the DNS entry, please verify"
626 " that '%s' is a FQDN. Continue?" % args[1]):
627 return 1
628
629 op = opcodes.OpRenameInstance(instance_name=args[0],
630 new_name=args[1],
631 ip_check=opts.ip_check,
632 name_check=opts.name_check)
633 result = SubmitOrSend(op, opts)
634
635 if result:
636 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
637
638 return 0
639
640
642 """Activate an instance's disks.
643
644 This serves two purposes:
645 - it allows (as long as the instance is not running)
646 mounting the disks and modifying them from the node
647 - it repairs inactive secondary drbds
648
649 @param opts: the command line options selected by the user
650 @type args: list
651 @param args: should contain only one element, the instance name
652 @rtype: int
653 @return: the desired exit code
654
655 """
656 instance_name = args[0]
657 op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
658 ignore_size=opts.ignore_size)
659 disks_info = SubmitOrSend(op, opts)
660 for host, iname, nname in disks_info:
661 ToStdout("%s:%s:%s", host, iname, nname)
662 return 0
663
664
666 """Deactivate an instance's disks.
667
668 This function takes the instance name, looks for its primary node
669 and the tries to shutdown its block devices on that node.
670
671 @param opts: the command line options selected by the user
672 @type args: list
673 @param args: should contain only one element, the instance name
674 @rtype: int
675 @return: the desired exit code
676
677 """
678 instance_name = args[0]
679 op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
680 SubmitOrSend(op, opts)
681 return 0
682
683
685 """Recreate an instance's disks.
686
687 @param opts: the command line options selected by the user
688 @type args: list
689 @param args: should contain only one element, the instance name
690 @rtype: int
691 @return: the desired exit code
692
693 """
694 instance_name = args[0]
695 if opts.disks:
696 try:
697 opts.disks = [int(v) for v in opts.disks.split(",")]
698 except (ValueError, TypeError), err:
699 ToStderr("Invalid disks value: %s" % str(err))
700 return 1
701 else:
702 opts.disks = []
703
704 op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
705 disks=opts.disks)
706 SubmitOrSend(op, opts)
707 return 0
708
709
711 """Grow an instance's disks.
712
713 @param opts: the command line options selected by the user
714 @type args: list
715 @param args: should contain two elements, the instance name
716 whose disks we grow and the disk name, e.g. I{sda}
717 @rtype: int
718 @return: the desired exit code
719
720 """
721 instance = args[0]
722 disk = args[1]
723 try:
724 disk = int(disk)
725 except (TypeError, ValueError), err:
726 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
727 errors.ECODE_INVAL)
728 amount = utils.ParseUnit(args[2])
729 op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
730 wait_for_sync=opts.wait_for_sync)
731 SubmitOrSend(op, opts)
732 return 0
733
734
736 """Startup instances.
737
738 This returns the opcode to start an instance, and its decorator will
739 wrap this into a loop starting all desired instances.
740
741 @param name: the name of the instance to act on
742 @param opts: the command line options selected by the user
743 @return: the opcode needed for the operation
744
745 """
746 op = opcodes.OpStartupInstance(instance_name=name,
747 force=opts.force,
748 ignore_offline_nodes=opts.ignore_offline)
749
750 if opts.hvparams:
751 op.hvparams = opts.hvparams
752 if opts.beparams:
753 op.beparams = opts.beparams
754 return op
755
756
758 """Reboot instance(s).
759
760 This returns the opcode to reboot an instance, and its decorator
761 will wrap this into a loop rebooting all desired instances.
762
763 @param name: the name of the instance to act on
764 @param opts: the command line options selected by the user
765 @return: the opcode needed for the operation
766
767 """
768 return opcodes.OpRebootInstance(instance_name=name,
769 reboot_type=opts.reboot_type,
770 ignore_secondaries=opts.ignore_secondaries,
771 shutdown_timeout=opts.shutdown_timeout)
772
773
775 """Shutdown an instance.
776
777 This returns the opcode to shutdown an instance, and its decorator
778 will wrap this into a loop shutting down all desired instances.
779
780 @param name: the name of the instance to act on
781 @param opts: the command line options selected by the user
782 @return: the opcode needed for the operation
783
784 """
785 return opcodes.OpShutdownInstance(instance_name=name,
786 timeout=opts.timeout,
787 ignore_offline_nodes=opts.ignore_offline)
788
789
791 """Replace the disks of an instance
792
793 @param opts: the command line options selected by the user
794 @type args: list
795 @param args: should contain only one element, the instance name
796 @rtype: int
797 @return: the desired exit code
798
799 """
800 new_2ndary = opts.dst_node
801 iallocator = opts.iallocator
802 if opts.disks is None:
803 disks = []
804 else:
805 try:
806 disks = [int(i) for i in opts.disks.split(",")]
807 except (TypeError, ValueError), err:
808 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
809 errors.ECODE_INVAL)
810 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
811 new_2ndary is not None, iallocator is not None].count(True)
812 if cnt != 1:
813 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
814 " options must be passed", errors.ECODE_INVAL)
815 elif opts.on_primary:
816 mode = constants.REPLACE_DISK_PRI
817 elif opts.on_secondary:
818 mode = constants.REPLACE_DISK_SEC
819 elif opts.auto:
820 mode = constants.REPLACE_DISK_AUTO
821 if disks:
822 raise errors.OpPrereqError("Cannot specify disks when using automatic"
823 " mode", errors.ECODE_INVAL)
824 elif new_2ndary is not None or iallocator is not None:
825
826 mode = constants.REPLACE_DISK_CHG
827
828 op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
829 remote_node=new_2ndary, mode=mode,
830 iallocator=iallocator,
831 early_release=opts.early_release)
832 SubmitOrSend(op, opts)
833 return 0
834
835
837 """Failover an instance.
838
839 The failover is done by shutting it down on its present node and
840 starting it on the secondary.
841
842 @param opts: the command line options selected by the user
843 @type args: list
844 @param args: should contain only one element, the instance name
845 @rtype: int
846 @return: the desired exit code
847
848 """
849 cl = GetClient()
850 instance_name = args[0]
851 force = opts.force
852
853 if not force:
854 _EnsureInstancesExist(cl, [instance_name])
855
856 usertext = ("Failover will happen to image %s."
857 " This requires a shutdown of the instance. Continue?" %
858 (instance_name,))
859 if not AskUser(usertext):
860 return 1
861
862 op = opcodes.OpFailoverInstance(instance_name=instance_name,
863 ignore_consistency=opts.ignore_consistency,
864 shutdown_timeout=opts.shutdown_timeout)
865 SubmitOrSend(op, opts, cl=cl)
866 return 0
867
868
870 """Migrate an instance.
871
872 The migrate is done without shutdown.
873
874 @param opts: the command line options selected by the user
875 @type args: list
876 @param args: should contain only one element, the instance name
877 @rtype: int
878 @return: the desired exit code
879
880 """
881 cl = GetClient()
882 instance_name = args[0]
883 force = opts.force
884
885 if not force:
886 _EnsureInstancesExist(cl, [instance_name])
887
888 if opts.cleanup:
889 usertext = ("Instance %s will be recovered from a failed migration."
890 " Note that the migration procedure (including cleanup)" %
891 (instance_name,))
892 else:
893 usertext = ("Instance %s will be migrated. Note that migration" %
894 (instance_name,))
895 usertext += (" might impact the instance if anything goes wrong"
896 " (e.g. due to bugs in the hypervisor). Continue?")
897 if not AskUser(usertext):
898 return 1
899
900
901 if not opts.live and opts.migration_mode is not None:
902 raise errors.OpPrereqError("Only one of the --non-live and "
903 "--migration-mode options can be passed",
904 errors.ECODE_INVAL)
905 if not opts.live:
906 mode = constants.HT_MIGRATION_NONLIVE
907 else:
908 mode = opts.migration_mode
909
910 op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
911 cleanup=opts.cleanup)
912 SubmitOpCode(op, cl=cl, opts=opts)
913 return 0
914
915
917 """Move an instance.
918
919 @param opts: the command line options selected by the user
920 @type args: list
921 @param args: should contain only one element, the instance name
922 @rtype: int
923 @return: the desired exit code
924
925 """
926 cl = GetClient()
927 instance_name = args[0]
928 force = opts.force
929
930 if not force:
931 usertext = ("Instance %s will be moved."
932 " This requires a shutdown of the instance. Continue?" %
933 (instance_name,))
934 if not AskUser(usertext):
935 return 1
936
937 op = opcodes.OpMoveInstance(instance_name=instance_name,
938 target_node=opts.node,
939 shutdown_timeout=opts.shutdown_timeout)
940 SubmitOrSend(op, opts, cl=cl)
941 return 0
942
943
945 """Connect to the console of an instance.
946
947 @param opts: the command line options selected by the user
948 @type args: list
949 @param args: should contain only one element, the instance name
950 @rtype: int
951 @return: the desired exit code
952
953 """
954 instance_name = args[0]
955
956 op = opcodes.OpConnectConsole(instance_name=instance_name)
957 cmd = SubmitOpCode(op, opts=opts)
958
959 if opts.show_command:
960 ToStdout("%s", utils.ShellQuoteArgs(cmd))
961 else:
962 try:
963 os.execvp(cmd[0], cmd)
964 finally:
965 ToStderr("Can't run console command %s with arguments:\n'%s'",
966 cmd[0], " ".join(cmd))
967 os._exit(1)
968
969
991
992
1066
1067
1068 if top_level:
1069 if dev["iv_name"] is not None:
1070 txt = dev["iv_name"]
1071 else:
1072 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1073 else:
1074 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1075 if isinstance(dev["size"], int):
1076 nice_size = utils.FormatUnit(dev["size"], "h")
1077 else:
1078 nice_size = dev["size"]
1079 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1080 data = []
1081 if top_level:
1082 data.append(("access mode", dev["mode"]))
1083 if dev["logical_id"] is not None:
1084 try:
1085 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1086 except ValueError:
1087 l_id = [str(dev["logical_id"])]
1088 if len(l_id) == 1:
1089 data.append(("logical_id", l_id[0]))
1090 else:
1091 data.extend(l_id)
1092 elif dev["physical_id"] is not None:
1093 data.append("physical_id:")
1094 data.append([dev["physical_id"]])
1095 if not static:
1096 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1097 if dev["sstatus"] and not static:
1098 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1099
1100 if dev["children"]:
1101 data.append("child devices:")
1102 for c_idx, child in enumerate(dev["children"]):
1103 data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1104 d1.append(data)
1105 return d1
1106
1107
1135
1136
1151
1153 """Compute instance run-time status.
1154
1155 @param opts: the command line options selected by the user
1156 @type args: list
1157 @param args: either an empty list, and then we query all
1158 instances, or should contain a list of instance names
1159 @rtype: int
1160 @return: the desired exit code
1161
1162 """
1163 if not args and not opts.show_all:
1164 ToStderr("No instance selected."
1165 " Please pass in --all if you want to query all instances.\n"
1166 "Note that this can take a long time on a big cluster.")
1167 return 1
1168 elif args and opts.show_all:
1169 ToStderr("Cannot use --all if you specify instance names.")
1170 return 1
1171
1172 retcode = 0
1173 op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1174 result = SubmitOpCode(op, opts=opts)
1175 if not result:
1176 ToStdout("No instances.")
1177 return 1
1178
1179 buf = StringIO()
1180 retcode = 0
1181 for instance_name in result:
1182 instance = result[instance_name]
1183 buf.write("Instance name: %s\n" % instance["name"])
1184 buf.write("UUID: %s\n" % instance["uuid"])
1185 buf.write("Serial number: %s\n" %
1186 compat.TryToRoman(instance["serial_no"],
1187 convert=opts.roman_integers))
1188 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1189 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1190 buf.write("State: configured to be %s" % instance["config_state"])
1191 if not opts.static:
1192 buf.write(", actual state is %s" % instance["run_state"])
1193 buf.write("\n")
1194
1195
1196 buf.write(" Nodes:\n")
1197 buf.write(" - primary: %s\n" % instance["pnode"])
1198 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1199 buf.write(" Operating system: %s\n" % instance["os"])
1200 _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
1201 if instance.has_key("network_port"):
1202 buf.write(" Allocated network port: %s\n" %
1203 compat.TryToRoman(instance["network_port"],
1204 convert=opts.roman_integers))
1205 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1206
1207
1208 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1209 None)
1210 if vnc_bind_address:
1211 port = instance["network_port"]
1212 display = int(port) - constants.VNC_BASE_PORT
1213 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1214 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1215 port,
1216 display)
1217 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1218 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1219 (vnc_bind_address, port,
1220 instance["pnode"], display))
1221 else:
1222
1223 vnc_console_port = "%s:%s" % (instance["pnode"],
1224 vnc_bind_address)
1225 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1226
1227 _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
1228 buf.write(" Hardware:\n")
1229 buf.write(" - VCPUs: %s\n" %
1230 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1231 convert=opts.roman_integers))
1232 buf.write(" - memory: %sMiB\n" %
1233 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1234 convert=opts.roman_integers))
1235 buf.write(" - NICs:\n")
1236 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1237 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1238 (idx, mac, ip, mode, link))
1239 buf.write(" Disks:\n")
1240
1241 for idx, device in enumerate(instance["disks"]):
1242 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1243 opts.roman_integers), 2)
1244
1245 ToStdout(buf.getvalue().rstrip('\n'))
1246 return retcode
1247
1248
1250 """Modifies an instance.
1251
1252 All parameters take effect only at the next restart of the instance.
1253
1254 @param opts: the command line options selected by the user
1255 @type args: list
1256 @param args: should contain only one element, the instance name
1257 @rtype: int
1258 @return: the desired exit code
1259
1260 """
1261 if not (opts.nics or opts.disks or opts.disk_template or
1262 opts.hvparams or opts.beparams or opts.os or opts.osparams):
1263 ToStderr("Please give at least one of the parameters.")
1264 return 1
1265
1266 for param in opts.beparams:
1267 if isinstance(opts.beparams[param], basestring):
1268 if opts.beparams[param].lower() == "default":
1269 opts.beparams[param] = constants.VALUE_DEFAULT
1270
1271 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1272 allowed_values=[constants.VALUE_DEFAULT])
1273
1274 for param in opts.hvparams:
1275 if isinstance(opts.hvparams[param], basestring):
1276 if opts.hvparams[param].lower() == "default":
1277 opts.hvparams[param] = constants.VALUE_DEFAULT
1278
1279 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1280 allowed_values=[constants.VALUE_DEFAULT])
1281
1282 for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1283 try:
1284 nic_op = int(nic_op)
1285 opts.nics[idx] = (nic_op, nic_dict)
1286 except (TypeError, ValueError):
1287 pass
1288
1289 for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1290 try:
1291 disk_op = int(disk_op)
1292 opts.disks[idx] = (disk_op, disk_dict)
1293 except (TypeError, ValueError):
1294 pass
1295 if disk_op == constants.DDM_ADD:
1296 if 'size' not in disk_dict:
1297 raise errors.OpPrereqError("Missing required parameter 'size'",
1298 errors.ECODE_INVAL)
1299 disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1300
1301 if (opts.disk_template and
1302 opts.disk_template in constants.DTS_NET_MIRROR and
1303 not opts.node):
1304 ToStderr("Changing the disk template to a mirrored one requires"
1305 " specifying a secondary node")
1306 return 1
1307
1308 op = opcodes.OpSetInstanceParams(instance_name=args[0],
1309 nics=opts.nics,
1310 disks=opts.disks,
1311 disk_template=opts.disk_template,
1312 remote_node=opts.node,
1313 hvparams=opts.hvparams,
1314 beparams=opts.beparams,
1315 os_name=opts.os,
1316 osparams=opts.osparams,
1317 force_variant=opts.force_variant,
1318 force=opts.force)
1319
1320
1321 result = SubmitOrSend(op, opts)
1322
1323 if result:
1324 ToStdout("Modified instance %s", args[0])
1325 for param, data in result:
1326 ToStdout(" - %-5s -> %s", param, data)
1327 ToStdout("Please don't forget that most parameters take effect"
1328 " only at the next start of the instance.")
1329 return 0
1330
1331
1332
1333 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1334 help="Do not ask for confirmation when more than"
1335 " one instance is affected",
1336 action="store_true", default=False)
1337
1338 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1339 help="Filter by nodes (primary only)",
1340 const=_SHUTDOWN_NODES_PRI, action="store_const")
1341
1342 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1343 help="Filter by nodes (secondary only)",
1344 const=_SHUTDOWN_NODES_SEC, action="store_const")
1345
1346 m_node_opt = cli_option("--node", dest="multi_mode",
1347 help="Filter by nodes (primary and secondary)",
1348 const=_SHUTDOWN_NODES_BOTH, action="store_const")
1349
1350 m_clust_opt = cli_option("--all", dest="multi_mode",
1351 help="Select all instances in the cluster",
1352 const=_SHUTDOWN_CLUSTER, action="store_const")
1353
1354 m_inst_opt = cli_option("--instance", dest="multi_mode",
1355 help="Filter by instance name [default]",
1356 const=_SHUTDOWN_INSTANCES, action="store_const")
1357
1358 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1359 help="Filter by node tag",
1360 const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1361 action="store_const")
1362
1363 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1364 help="Filter by primary node tag",
1365 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1366 action="store_const")
1367
1368 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1369 help="Filter by secondary node tag",
1370 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1371 action="store_const")
1372
1373 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1374 help="Filter by instance tag",
1375 const=_SHUTDOWN_INSTANCES_BY_TAGS,
1376 action="store_const")
1377
1378
1379 add_opts = [
1380 NOSTART_OPT,
1381 OS_OPT,
1382 FORCE_VARIANT_OPT,
1383 NO_INSTALL_OPT,
1384 ]
1385
1386 commands = {
1387 'add': (
1388 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1389 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1390 "Creates and adds a new instance to the cluster"),
1391 'batch-create': (
1392 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1393 "<instances.json>",
1394 "Create a bunch of instances based on specs in the file."),
1395 'console': (
1396 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1397 [SHOWCMD_OPT, PRIORITY_OPT],
1398 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1399 'failover': (
1400 FailoverInstance, ARGS_ONE_INSTANCE,
1401 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1402 DRY_RUN_OPT, PRIORITY_OPT],
1403 "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1404 " using the remote mirror (only for instances of type drbd)"),
1405 'migrate': (
1406 MigrateInstance, ARGS_ONE_INSTANCE,
1407 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1408 PRIORITY_OPT],
1409 "[-f] <instance>", "Migrate instance to its secondary node"
1410 " (only for instances of type drbd)"),
1411 'move': (
1412 MoveInstance, ARGS_ONE_INSTANCE,
1413 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1414 DRY_RUN_OPT, PRIORITY_OPT],
1415 "[-f] <instance>", "Move instance to an arbitrary node"
1416 " (only for instances of type file and lv)"),
1417 'info': (
1418 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1419 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1420 "[-s] {--all | <instance>...}",
1421 "Show information on the specified instance(s)"),
1422 'list': (
1423 ListInstances, ARGS_MANY_INSTANCES,
1424 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
1425 "[<instance>...]",
1426 "Lists the instances and their status. The available fields are"
1427 " (see the man page for details): status, oper_state, oper_ram,"
1428 " oper_vcpus, name, os, pnode, snodes, admin_state, admin_ram,"
1429 " disk_template, ip, mac, nic_mode, nic_link, sda_size, sdb_size,"
1430 " vcpus, serial_no,"
1431 " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1432 " nic.macs, nic.ips, nic.modes, nic.links,"
1433 " disk.count, disk.size/N, disk.sizes,"
1434 " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1435 " hypervisor."
1436 " The default field"
1437 " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS),
1438 ),
1439 'reinstall': (
1440 ReinstallInstance, [ArgInstance()],
1441 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1442 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1443 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1444 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1445 "[-f] <instance>", "Reinstall a stopped instance"),
1446 'remove': (
1447 RemoveInstance, ARGS_ONE_INSTANCE,
1448 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1449 DRY_RUN_OPT, PRIORITY_OPT],
1450 "[-f] <instance>", "Shuts down the instance and removes it"),
1451 'rename': (
1452 RenameInstance,
1453 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1454 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1455 "<instance> <new_name>", "Rename the instance"),
1456 'replace-disks': (
1457 ReplaceDisks, ARGS_ONE_INSTANCE,
1458 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1459 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1460 DRY_RUN_OPT, PRIORITY_OPT],
1461 "[-s|-p|-n NODE|-I NAME] <instance>",
1462 "Replaces all disks for the instance"),
1463 'modify': (
1464 SetInstanceParams, ARGS_ONE_INSTANCE,
1465 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1466 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1467 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1468 "<instance>", "Alters the parameters of an instance"),
1469 'shutdown': (
1470 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1471 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1472 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1473 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1474 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1475 "<instance>", "Stops an instance"),
1476 'startup': (
1477 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1478 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1479 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1480 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1481 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1482 "<instance>", "Starts an instance"),
1483 'reboot': (
1484 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1485 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1486 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1487 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1488 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1489 "<instance>", "Reboots an instance"),
1490 'activate-disks': (
1491 ActivateDisks, ARGS_ONE_INSTANCE,
1492 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1493 "<instance>", "Activate an instance's disks"),
1494 'deactivate-disks': (
1495 DeactivateDisks, ARGS_ONE_INSTANCE,
1496 [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1497 "<instance>", "Deactivate an instance's disks"),
1498 'recreate-disks': (
1499 RecreateDisks, ARGS_ONE_INSTANCE,
1500 [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1501 "<instance>", "Recreate an instance's disks"),
1502 'grow-disk': (
1503 GrowDisk,
1504 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1505 ArgUnknown(min=1, max=1)],
1506 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1507 "<instance> <disk> <size>", "Grow an instance's disk"),
1508 'list-tags': (
1509 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1510 "<instance_name>", "List the tags of the given instance"),
1511 'add-tags': (
1512 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1513 [TAG_SRC_OPT, PRIORITY_OPT],
1514 "<instance_name> tag...", "Add tags to the given instance"),
1515 'remove-tags': (
1516 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1517 [TAG_SRC_OPT, PRIORITY_OPT],
1518 "<instance_name> tag...", "Remove tags from given instance"),
1519 }
1520
1521
1522 aliases = {
1523 'start': 'startup',
1524 'stop': 'shutdown',
1525 }
1526
1527
1531