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 copy
29 import itertools
30 import simplejson
31 import logging
32 from cStringIO import StringIO
33
34 from ganeti.cli import *
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import compat
38 from ganeti import utils
39 from ganeti import errors
40 from ganeti import netutils
41 from ganeti import ssh
42 from ganeti import objects
43 from ganeti import ht
44
45
46 _EXPAND_CLUSTER = "cluster"
47 _EXPAND_NODES_BOTH = "nodes"
48 _EXPAND_NODES_PRI = "nodes-pri"
49 _EXPAND_NODES_SEC = "nodes-sec"
50 _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
51 _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
52 _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
53 _EXPAND_INSTANCES = "instances"
54 _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
55
56 _EXPAND_NODES_TAGS_MODES = compat.UniqueFrozenset([
57 _EXPAND_NODES_BOTH_BY_TAGS,
58 _EXPAND_NODES_PRI_BY_TAGS,
59 _EXPAND_NODES_SEC_BY_TAGS,
60 ])
61
62
63 _LIST_DEF_FIELDS = [
64 "name", "hypervisor", "os", "pnode", "status", "oper_ram",
65 ]
66
67 _MISSING = object()
68 _ENV_OVERRIDE = compat.UniqueFrozenset(["list"])
69
70 _INST_DATA_VAL = ht.TListOf(ht.TDict)
71
72
74 """Expand the given names using the passed mode.
75
76 For _EXPAND_CLUSTER, all instances will be returned. For
77 _EXPAND_NODES_PRI/SEC, all instances having those nodes as
78 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
79 instances having those nodes as either primary or secondary will be
80 returned. For _EXPAND_INSTANCES, the given instances will be
81 returned.
82
83 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH},
84 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or
85 L{_EXPAND_INSTANCES}
86 @param names: a list of names; for cluster, it must be empty,
87 and for node and instance it must be a list of valid item
88 names (short names are valid as usual, e.g. node1 instead of
89 node1.example.com)
90 @rtype: list
91 @return: the list of names after the expansion
92 @raise errors.ProgrammerError: for unknown selection type
93 @raise errors.OpPrereqError: for invalid input parameters
94
95 """
96
97
98 if client is None:
99 client = GetClient()
100 if mode == _EXPAND_CLUSTER:
101 if names:
102 raise errors.OpPrereqError("Cluster filter mode takes no arguments",
103 errors.ECODE_INVAL)
104 idata = client.QueryInstances([], ["name"], False)
105 inames = [row[0] for row in idata]
106
107 elif (mode in _EXPAND_NODES_TAGS_MODES or
108 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
109 if mode in _EXPAND_NODES_TAGS_MODES:
110 if not names:
111 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
112 ndata = client.QueryNodes([], ["name", "pinst_list",
113 "sinst_list", "tags"], False)
114 ndata = [row for row in ndata if set(row[3]).intersection(names)]
115 else:
116 if not names:
117 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
118 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
119 False)
120
121 ipri = [row[1] for row in ndata]
122 pri_names = list(itertools.chain(*ipri))
123 isec = [row[2] for row in ndata]
124 sec_names = list(itertools.chain(*isec))
125 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
126 inames = pri_names + sec_names
127 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
128 inames = pri_names
129 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
130 inames = sec_names
131 else:
132 raise errors.ProgrammerError("Unhandled shutdown type")
133 elif mode == _EXPAND_INSTANCES:
134 if not names:
135 raise errors.OpPrereqError("No instance names passed",
136 errors.ECODE_INVAL)
137 idata = client.QueryInstances(names, ["name"], False)
138 inames = [row[0] for row in idata]
139 elif mode == _EXPAND_INSTANCES_BY_TAGS:
140 if not names:
141 raise errors.OpPrereqError("No instance tags passed",
142 errors.ECODE_INVAL)
143 idata = client.QueryInstances([], ["name", "tags"], False)
144 inames = [row[0] for row in idata if set(row[1]).intersection(names)]
145 else:
146 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
147
148 return inames
149
150
152 """Check for and ensure the given instance names exist.
153
154 This function will raise an OpPrereqError in case they don't
155 exist. Otherwise it will exit cleanly.
156
157 @type client: L{ganeti.luxi.Client}
158 @param client: the client to use for the query
159 @type names: list
160 @param names: the list of instance names to query
161 @raise errors.OpPrereqError: in case any instance is missing
162
163 """
164
165
166 result = client.QueryInstances(names, ["name"], False)
167 for orig_name, row in zip(names, result):
168 if row[0] is None:
169 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
170 errors.ECODE_NOENT)
171
172
174 """Generic multi-instance operations.
175
176 The will return a wrapper that processes the options and arguments
177 given, and uses the passed function to build the opcode needed for
178 the specific operation. Thus all the generic loop/confirmation code
179 is abstracted into this function.
180
181 """
182 def realfn(opts, args):
183 if opts.multi_mode is None:
184 opts.multi_mode = _EXPAND_INSTANCES
185 cl = GetClient()
186 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
187 if not inames:
188 if opts.multi_mode == _EXPAND_CLUSTER:
189 ToStdout("Cluster is empty, no instances to shutdown")
190 return 0
191 raise errors.OpPrereqError("Selection filter does not match"
192 " any instances", errors.ECODE_INVAL)
193 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
194 if not (opts.force_multi or not multi_on
195 or ConfirmOperation(inames, "instances", operation)):
196 return 1
197 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
198 for name in inames:
199 op = fn(name, opts)
200 jex.QueueJob(name, op)
201 results = jex.WaitOrShow(not opts.submit_only)
202 rcode = compat.all(row[0] for row in results)
203 return int(not rcode)
204 return realfn
205
206
208 """List instances and their properties.
209
210 @param opts: the command line options selected by the user
211 @type args: list
212 @param args: should be an empty list
213 @rtype: int
214 @return: the desired exit code
215
216 """
217 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
218
219 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
220 "nic.modes", "nic.links", "nic.bridges",
221 "nic.networks",
222 "snodes", "snodes.group", "snodes.group.uuid"],
223 (lambda value: ",".join(str(item)
224 for item in value),
225 False))
226
227 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
228 opts.separator, not opts.no_headers,
229 format_override=fmtoverride, verbose=opts.verbose,
230 force_filter=opts.force_filter)
231
232
234 """List instance fields.
235
236 @param opts: the command line options selected by the user
237 @type args: list
238 @param args: fields to list, or empty for all
239 @rtype: int
240 @return: the desired exit code
241
242 """
243 return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
244 not opts.no_headers)
245
246
254
255
257 """Create instances using a definition file.
258
259 This function reads a json file with L{opcodes.OpInstanceCreate}
260 serialisations.
261
262 @param opts: the command line options selected by the user
263 @type args: list
264 @param args: should contain one element, the json filename
265 @rtype: int
266 @return: the desired exit code
267
268 """
269 (json_filename,) = args
270 cl = GetClient()
271
272 try:
273 instance_data = simplejson.loads(utils.ReadFile(json_filename))
274 except Exception, err:
275 ToStderr("Can't parse the instance definition file: %s" % str(err))
276 return 1
277
278 if not _INST_DATA_VAL(instance_data):
279 ToStderr("The instance definition file is not %s" % _INST_DATA_VAL)
280 return 1
281
282 instances = []
283 possible_params = set(opcodes.OpInstanceCreate.GetAllSlots())
284 for (idx, inst) in enumerate(instance_data):
285 unknown = set(inst.keys()) - possible_params
286
287 if unknown:
288
289 raise errors.OpPrereqError("Unknown fields in definition %s: %s" %
290 (idx, utils.CommaJoin(unknown)),
291 errors.ECODE_INVAL)
292
293 op = opcodes.OpInstanceCreate(**inst)
294 op.Validate(False)
295 instances.append(op)
296
297 op = opcodes.OpInstanceMultiAlloc(iallocator=opts.iallocator,
298 instances=instances)
299 result = SubmitOrSend(op, opts, cl=cl)
300
301
302 jex = JobExecutor(cl=cl, opts=opts)
303
304 for (status, job_id) in result[constants.JOB_IDS_KEY]:
305 jex.AddJobId(None, status, job_id)
306
307 results = jex.GetResults()
308 bad_cnt = len([row for row in results if not row[0]])
309 if bad_cnt == 0:
310 ToStdout("All instances created successfully.")
311 rcode = constants.EXIT_SUCCESS
312 else:
313 ToStdout("There were %s errors during the creation.", bad_cnt)
314 rcode = constants.EXIT_FAILURE
315
316 return rcode
317
318
320 """Reinstall an instance.
321
322 @param opts: the command line options selected by the user
323 @type args: list
324 @param args: should contain only one element, the name of the
325 instance to be reinstalled
326 @rtype: int
327 @return: the desired exit code
328
329 """
330
331 if opts.multi_mode is None:
332 opts.multi_mode = _EXPAND_INSTANCES
333
334 inames = _ExpandMultiNames(opts.multi_mode, args)
335 if not inames:
336 raise errors.OpPrereqError("Selection filter does not match any instances",
337 errors.ECODE_INVAL)
338
339
340 if opts.select_os is True:
341 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
342 result = SubmitOpCode(op, opts=opts)
343
344 if not result:
345 ToStdout("Can't get the OS list")
346 return 1
347
348 ToStdout("Available OS templates:")
349 number = 0
350 choices = []
351 for (name, variants) in result:
352 for entry in CalculateOSNames(name, variants):
353 ToStdout("%3s: %s", number, entry)
354 choices.append(("%s" % number, entry, entry))
355 number += 1
356
357 choices.append(("x", "exit", "Exit gnt-instance reinstall"))
358 selected = AskUser("Enter OS template number (or x to abort):",
359 choices)
360
361 if selected == "exit":
362 ToStderr("User aborted reinstall, exiting")
363 return 1
364
365 os_name = selected
366 os_msg = "change the OS to '%s'" % selected
367 else:
368 os_name = opts.os
369 if opts.os is not None:
370 os_msg = "change the OS to '%s'" % os_name
371 else:
372 os_msg = "keep the same OS"
373
374
375
376
377 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1
378 if multi_on:
379 warn_msg = ("Note: this will remove *all* data for the"
380 " below instances! It will %s.\n" % os_msg)
381 if not (opts.force_multi or
382 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
383 return 1
384 else:
385 if not (opts.force or opts.force_multi):
386 usertext = ("This will reinstall the instance '%s' (and %s) which"
387 " removes all data. Continue?") % (inames[0], os_msg)
388 if not AskUser(usertext):
389 return 1
390
391 jex = JobExecutor(verbose=multi_on, opts=opts)
392 for instance_name in inames:
393 op = opcodes.OpInstanceReinstall(instance_name=instance_name,
394 os_type=os_name,
395 force_variant=opts.force_variant,
396 osparams=opts.osparams)
397 jex.QueueJob(instance_name, op)
398
399 results = jex.WaitOrShow(not opts.submit_only)
400
401 if compat.all(map(compat.fst, results)):
402 return constants.EXIT_SUCCESS
403 else:
404 return constants.EXIT_FAILURE
405
406
408 """Remove an instance.
409
410 @param opts: the command line options selected by the user
411 @type args: list
412 @param args: should contain only one element, the name of
413 the instance to be removed
414 @rtype: int
415 @return: the desired exit code
416
417 """
418 instance_name = args[0]
419 force = opts.force
420 cl = GetClient()
421
422 if not force:
423 _EnsureInstancesExist(cl, [instance_name])
424
425 usertext = ("This will remove the volumes of the instance %s"
426 " (including mirrors), thus removing all the data"
427 " of the instance. Continue?") % instance_name
428 if not AskUser(usertext):
429 return 1
430
431 op = opcodes.OpInstanceRemove(instance_name=instance_name,
432 ignore_failures=opts.ignore_failures,
433 shutdown_timeout=opts.shutdown_timeout)
434 SubmitOrSend(op, opts, cl=cl)
435 return 0
436
437
439 """Rename an instance.
440
441 @param opts: the command line options selected by the user
442 @type args: list
443 @param args: should contain two elements, the old and the
444 new instance names
445 @rtype: int
446 @return: the desired exit code
447
448 """
449 if not opts.name_check:
450 if not AskUser("As you disabled the check of the DNS entry, please verify"
451 " that '%s' is a FQDN. Continue?" % args[1]):
452 return 1
453
454 op = opcodes.OpInstanceRename(instance_name=args[0],
455 new_name=args[1],
456 ip_check=opts.ip_check,
457 name_check=opts.name_check)
458 result = SubmitOrSend(op, opts)
459
460 if result:
461 ToStdout("Instance '%s' renamed to '%s'", args[0], result)
462
463 return 0
464
465
467 """Activate an instance's disks.
468
469 This serves two purposes:
470 - it allows (as long as the instance is not running)
471 mounting the disks and modifying them from the node
472 - it repairs inactive secondary drbds
473
474 @param opts: the command line options selected by the user
475 @type args: list
476 @param args: should contain only one element, the instance name
477 @rtype: int
478 @return: the desired exit code
479
480 """
481 instance_name = args[0]
482 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
483 ignore_size=opts.ignore_size,
484 wait_for_sync=opts.wait_for_sync)
485 disks_info = SubmitOrSend(op, opts)
486 for host, iname, nname in disks_info:
487 ToStdout("%s:%s:%s", host, iname, nname)
488 return 0
489
490
492 """Deactivate an instance's disks.
493
494 This function takes the instance name, looks for its primary node
495 and the tries to shutdown its block devices on that node.
496
497 @param opts: the command line options selected by the user
498 @type args: list
499 @param args: should contain only one element, the instance name
500 @rtype: int
501 @return: the desired exit code
502
503 """
504 instance_name = args[0]
505 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
506 force=opts.force)
507 SubmitOrSend(op, opts)
508 return 0
509
510
512 """Recreate an instance's disks.
513
514 @param opts: the command line options selected by the user
515 @type args: list
516 @param args: should contain only one element, the instance name
517 @rtype: int
518 @return: the desired exit code
519
520 """
521 instance_name = args[0]
522
523 disks = []
524
525 if opts.disks:
526 for didx, ddict in opts.disks:
527 didx = int(didx)
528
529 if not ht.TDict(ddict):
530 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
531 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
532
533 if constants.IDISK_SIZE in ddict:
534 try:
535 ddict[constants.IDISK_SIZE] = \
536 utils.ParseUnit(ddict[constants.IDISK_SIZE])
537 except ValueError, err:
538 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
539 (didx, err), errors.ECODE_INVAL)
540
541 disks.append((didx, ddict))
542
543
544
545
546 if opts.node:
547 if opts.iallocator:
548 msg = "At most one of either --nodes or --iallocator can be passed"
549 raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
550 pnode, snode = SplitNodeOption(opts.node)
551 nodes = [pnode]
552 if snode is not None:
553 nodes.append(snode)
554 else:
555 nodes = []
556
557 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
558 disks=disks, nodes=nodes,
559 iallocator=opts.iallocator)
560 SubmitOrSend(op, opts)
561
562 return 0
563
564
566 """Grow an instance's disks.
567
568 @param opts: the command line options selected by the user
569 @type args: list
570 @param args: should contain three elements, the target instance name,
571 the target disk id, and the target growth
572 @rtype: int
573 @return: the desired exit code
574
575 """
576 instance = args[0]
577 disk = args[1]
578 try:
579 disk = int(disk)
580 except (TypeError, ValueError), err:
581 raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
582 errors.ECODE_INVAL)
583 try:
584 amount = utils.ParseUnit(args[2])
585 except errors.UnitParseError:
586 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2],
587 errors.ECODE_INVAL)
588 op = opcodes.OpInstanceGrowDisk(instance_name=instance,
589 disk=disk, amount=amount,
590 wait_for_sync=opts.wait_for_sync,
591 absolute=opts.absolute)
592 SubmitOrSend(op, opts)
593 return 0
594
595
597 """Startup instances.
598
599 This returns the opcode to start an instance, and its decorator will
600 wrap this into a loop starting all desired instances.
601
602 @param name: the name of the instance to act on
603 @param opts: the command line options selected by the user
604 @return: the opcode needed for the operation
605
606 """
607 op = opcodes.OpInstanceStartup(instance_name=name,
608 force=opts.force,
609 ignore_offline_nodes=opts.ignore_offline,
610 no_remember=opts.no_remember,
611 startup_paused=opts.startup_paused)
612
613 if opts.hvparams:
614 op.hvparams = opts.hvparams
615 if opts.beparams:
616 op.beparams = opts.beparams
617 return op
618
619
621 """Reboot instance(s).
622
623 This returns the opcode to reboot an instance, and its decorator
624 will wrap this into a loop rebooting all desired instances.
625
626 @param name: the name of the instance to act on
627 @param opts: the command line options selected by the user
628 @return: the opcode needed for the operation
629
630 """
631 return opcodes.OpInstanceReboot(instance_name=name,
632 reboot_type=opts.reboot_type,
633 ignore_secondaries=opts.ignore_secondaries,
634 shutdown_timeout=opts.shutdown_timeout)
635
636
638 """Shutdown an instance.
639
640 This returns the opcode to shutdown an instance, and its decorator
641 will wrap this into a loop shutting down all desired instances.
642
643 @param name: the name of the instance to act on
644 @param opts: the command line options selected by the user
645 @return: the opcode needed for the operation
646
647 """
648 return opcodes.OpInstanceShutdown(instance_name=name,
649 force=opts.force,
650 timeout=opts.timeout,
651 ignore_offline_nodes=opts.ignore_offline,
652 no_remember=opts.no_remember)
653
654
656 """Replace the disks of an instance
657
658 @param opts: the command line options selected by the user
659 @type args: list
660 @param args: should contain only one element, the instance name
661 @rtype: int
662 @return: the desired exit code
663
664 """
665 new_2ndary = opts.dst_node
666 iallocator = opts.iallocator
667 if opts.disks is None:
668 disks = []
669 else:
670 try:
671 disks = [int(i) for i in opts.disks.split(",")]
672 except (TypeError, ValueError), err:
673 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
674 errors.ECODE_INVAL)
675 cnt = [opts.on_primary, opts.on_secondary, opts.auto,
676 new_2ndary is not None, iallocator is not None].count(True)
677 if cnt != 1:
678 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
679 " options must be passed", errors.ECODE_INVAL)
680 elif opts.on_primary:
681 mode = constants.REPLACE_DISK_PRI
682 elif opts.on_secondary:
683 mode = constants.REPLACE_DISK_SEC
684 elif opts.auto:
685 mode = constants.REPLACE_DISK_AUTO
686 if disks:
687 raise errors.OpPrereqError("Cannot specify disks when using automatic"
688 " mode", errors.ECODE_INVAL)
689 elif new_2ndary is not None or iallocator is not None:
690
691 mode = constants.REPLACE_DISK_CHG
692
693 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
694 remote_node=new_2ndary, mode=mode,
695 iallocator=iallocator,
696 early_release=opts.early_release,
697 ignore_ipolicy=opts.ignore_ipolicy)
698 SubmitOrSend(op, opts)
699 return 0
700
701
703 """Failover an instance.
704
705 The failover is done by shutting it down on its present node and
706 starting it on the secondary.
707
708 @param opts: the command line options selected by the user
709 @type args: list
710 @param args: should contain only one element, the instance name
711 @rtype: int
712 @return: the desired exit code
713
714 """
715 cl = GetClient()
716 instance_name = args[0]
717 force = opts.force
718 iallocator = opts.iallocator
719 target_node = opts.dst_node
720
721 if iallocator and target_node:
722 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
723 " node (-n) but not both", errors.ECODE_INVAL)
724
725 if not force:
726 _EnsureInstancesExist(cl, [instance_name])
727
728 usertext = ("Failover will happen to image %s."
729 " This requires a shutdown of the instance. Continue?" %
730 (instance_name,))
731 if not AskUser(usertext):
732 return 1
733
734 op = opcodes.OpInstanceFailover(instance_name=instance_name,
735 ignore_consistency=opts.ignore_consistency,
736 shutdown_timeout=opts.shutdown_timeout,
737 iallocator=iallocator,
738 target_node=target_node,
739 ignore_ipolicy=opts.ignore_ipolicy)
740 SubmitOrSend(op, opts, cl=cl)
741 return 0
742
743
745 """Migrate an instance.
746
747 The migrate is done without shutdown.
748
749 @param opts: the command line options selected by the user
750 @type args: list
751 @param args: should contain only one element, the instance name
752 @rtype: int
753 @return: the desired exit code
754
755 """
756 cl = GetClient()
757 instance_name = args[0]
758 force = opts.force
759 iallocator = opts.iallocator
760 target_node = opts.dst_node
761
762 if iallocator and target_node:
763 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
764 " node (-n) but not both", errors.ECODE_INVAL)
765
766 if not force:
767 _EnsureInstancesExist(cl, [instance_name])
768
769 if opts.cleanup:
770 usertext = ("Instance %s will be recovered from a failed migration."
771 " Note that the migration procedure (including cleanup)" %
772 (instance_name,))
773 else:
774 usertext = ("Instance %s will be migrated. Note that migration" %
775 (instance_name,))
776 usertext += (" might impact the instance if anything goes wrong"
777 " (e.g. due to bugs in the hypervisor). Continue?")
778 if not AskUser(usertext):
779 return 1
780
781
782 if not opts.live and opts.migration_mode is not None:
783 raise errors.OpPrereqError("Only one of the --non-live and "
784 "--migration-mode options can be passed",
785 errors.ECODE_INVAL)
786 if not opts.live:
787 mode = constants.HT_MIGRATION_NONLIVE
788 else:
789 mode = opts.migration_mode
790
791 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
792 cleanup=opts.cleanup, iallocator=iallocator,
793 target_node=target_node,
794 allow_failover=opts.allow_failover,
795 allow_runtime_changes=opts.allow_runtime_chgs,
796 ignore_ipolicy=opts.ignore_ipolicy)
797 SubmitOrSend(op, cl=cl, opts=opts)
798 return 0
799
800
802 """Move an instance.
803
804 @param opts: the command line options selected by the user
805 @type args: list
806 @param args: should contain only one element, the instance name
807 @rtype: int
808 @return: the desired exit code
809
810 """
811 cl = GetClient()
812 instance_name = args[0]
813 force = opts.force
814
815 if not force:
816 usertext = ("Instance %s will be moved."
817 " This requires a shutdown of the instance. Continue?" %
818 (instance_name,))
819 if not AskUser(usertext):
820 return 1
821
822 op = opcodes.OpInstanceMove(instance_name=instance_name,
823 target_node=opts.node,
824 shutdown_timeout=opts.shutdown_timeout,
825 ignore_consistency=opts.ignore_consistency,
826 ignore_ipolicy=opts.ignore_ipolicy)
827 SubmitOrSend(op, opts, cl=cl)
828 return 0
829
830
832 """Connect to the console of an instance.
833
834 @param opts: the command line options selected by the user
835 @type args: list
836 @param args: should contain only one element, the instance name
837 @rtype: int
838 @return: the desired exit code
839
840 """
841 instance_name = args[0]
842
843 cl = GetClient()
844 try:
845 cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
846 ((console_data, oper_state), ) = \
847 cl.QueryInstances([instance_name], ["console", "oper_state"], False)
848 finally:
849
850 cl.Close()
851
852 del cl
853
854 if not console_data:
855 if oper_state:
856
857 raise errors.OpExecError("Console information for instance %s is"
858 " unavailable" % instance_name)
859 else:
860 raise errors.OpExecError("Instance %s is not running, can't get console" %
861 instance_name)
862
863 return _DoConsole(objects.InstanceConsole.FromDict(console_data),
864 opts.show_command, cluster_name)
865
866
869 """Acts based on the result of L{opcodes.OpInstanceConsole}.
870
871 @type console: L{objects.InstanceConsole}
872 @param console: Console object
873 @type show_command: bool
874 @param show_command: Whether to just display commands
875 @type cluster_name: string
876 @param cluster_name: Cluster name as retrieved from master daemon
877
878 """
879 assert console.Validate()
880
881 if console.kind == constants.CONS_MESSAGE:
882 feedback_fn(console.message)
883 elif console.kind == constants.CONS_VNC:
884 feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
885 " URL <vnc://%s:%s/>",
886 console.instance, console.host, console.port,
887 console.display, console.host, console.port)
888 elif console.kind == constants.CONS_SPICE:
889 feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
890 console.host, console.port)
891 elif console.kind == constants.CONS_SSH:
892
893 if isinstance(console.command, basestring):
894 cmd = console.command
895 else:
896 cmd = utils.ShellQuoteArgs(console.command)
897
898 srun = ssh.SshRunner(cluster_name=cluster_name)
899 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
900 batch=True, quiet=False, tty=True)
901
902 if show_command:
903 feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
904 else:
905 result = _runcmd_fn(ssh_cmd, interactive=True)
906 if result.failed:
907 logging.error("Console command \"%s\" failed with reason '%s' and"
908 " output %r", result.cmd, result.fail_reason,
909 result.output)
910 raise errors.OpExecError("Connection to console of instance %s failed,"
911 " please check cluster configuration" %
912 console.instance)
913 else:
914 raise errors.GenericError("Unknown console type '%s'" % console.kind)
915
916 return constants.EXIT_SUCCESS
917
918
940
941
1012
1013
1014 if top_level:
1015 if dev["iv_name"] is not None:
1016 txt = dev["iv_name"]
1017 else:
1018 txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1019 else:
1020 txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1021 if isinstance(dev["size"], int):
1022 nice_size = utils.FormatUnit(dev["size"], "h")
1023 else:
1024 nice_size = dev["size"]
1025 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1026 data = []
1027 if top_level:
1028 data.append(("access mode", dev["mode"]))
1029 if dev["logical_id"] is not None:
1030 try:
1031 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1032 except ValueError:
1033 l_id = [str(dev["logical_id"])]
1034 if len(l_id) == 1:
1035 data.append(("logical_id", l_id[0]))
1036 else:
1037 data.extend(l_id)
1038 elif dev["physical_id"] is not None:
1039 data.append("physical_id:")
1040 data.append([dev["physical_id"]])
1041
1042 if dev["pstatus"]:
1043 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1044
1045 if dev["sstatus"]:
1046 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1047
1048 if dev["children"]:
1049 data.append("child devices:")
1050 for c_idx, child in enumerate(dev["children"]):
1051 data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1052 d1.append(data)
1053 return d1
1054
1055
1083
1084
1086 """Compute instance run-time status.
1087
1088 @param opts: the command line options selected by the user
1089 @type args: list
1090 @param args: either an empty list, and then we query all
1091 instances, or should contain a list of instance names
1092 @rtype: int
1093 @return: the desired exit code
1094
1095 """
1096 if not args and not opts.show_all:
1097 ToStderr("No instance selected."
1098 " Please pass in --all if you want to query all instances.\n"
1099 "Note that this can take a long time on a big cluster.")
1100 return 1
1101 elif args and opts.show_all:
1102 ToStderr("Cannot use --all if you specify instance names.")
1103 return 1
1104
1105 retcode = 0
1106 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1107 use_locking=not opts.static)
1108 result = SubmitOpCode(op, opts=opts)
1109 if not result:
1110 ToStdout("No instances.")
1111 return 1
1112
1113 buf = StringIO()
1114 retcode = 0
1115 for instance_name in result:
1116 instance = result[instance_name]
1117 buf.write("Instance name: %s\n" % instance["name"])
1118 buf.write("UUID: %s\n" % instance["uuid"])
1119 buf.write("Serial number: %s\n" %
1120 compat.TryToRoman(instance["serial_no"],
1121 convert=opts.roman_integers))
1122 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1123 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1124 buf.write("State: configured to be %s" % instance["config_state"])
1125 if instance["run_state"]:
1126 buf.write(", actual state is %s" % instance["run_state"])
1127 buf.write("\n")
1128
1129
1130 buf.write(" Nodes:\n")
1131 buf.write(" - primary: %s\n" % instance["pnode"])
1132 buf.write(" group: %s (UUID %s)\n" %
1133 (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1134 buf.write(" - secondaries: %s\n" %
1135 utils.CommaJoin("%s (group %s, group UUID %s)" %
1136 (name, group_name, group_uuid)
1137 for (name, group_name, group_uuid) in
1138 zip(instance["snodes"],
1139 instance["snodes_group_names"],
1140 instance["snodes_group_uuids"])))
1141 buf.write(" Operating system: %s\n" % instance["os"])
1142 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1143 level=2)
1144 if "network_port" in instance:
1145 buf.write(" Allocated network port: %s\n" %
1146 compat.TryToRoman(instance["network_port"],
1147 convert=opts.roman_integers))
1148 buf.write(" Hypervisor: %s\n" % instance["hypervisor"])
1149
1150
1151 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1152 None)
1153 if vnc_bind_address:
1154 port = instance["network_port"]
1155 display = int(port) - constants.VNC_BASE_PORT
1156 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1157 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1158 port,
1159 display)
1160 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1161 vnc_console_port = ("%s:%s (node %s) (display %s)" %
1162 (vnc_bind_address, port,
1163 instance["pnode"], display))
1164 else:
1165
1166 vnc_console_port = "%s:%s" % (instance["pnode"],
1167 vnc_bind_address)
1168 buf.write(" - console connection: vnc to %s\n" % vnc_console_port)
1169
1170 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1171 level=2)
1172 buf.write(" Hardware:\n")
1173
1174
1175 be_actual = copy.deepcopy(instance["be_actual"])
1176 be_actual["memory"] = be_actual[constants.BE_MAXMEM]
1177 FormatParameterDict(buf, instance["be_instance"], be_actual, level=2)
1178
1179 buf.write(" - NICs:\n")
1180 for idx, (ip, mac, mode, link, _, netinfo) in enumerate(instance["nics"]):
1181 network_name = None
1182 if netinfo:
1183 network_name = netinfo["name"]
1184 buf.write(" - nic/%d: MAC: %s, IP: %s,"
1185 " mode: %s, link: %s, network: %s\n" %
1186 (idx, mac, ip, mode, link, network_name))
1187 buf.write(" Disk template: %s\n" % instance["disk_template"])
1188 buf.write(" Disks:\n")
1189
1190 for idx, device in enumerate(instance["disks"]):
1191 _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1192 opts.roman_integers), 2)
1193
1194 ToStdout(buf.getvalue().rstrip("\n"))
1195 return retcode
1196
1197
1199 """Converts NIC/disk modifications from CLI to opcode.
1200
1201 When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1202 disks at arbitrary indices, its parameter format changed. This function
1203 converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1204 newer format and adds support for new-style requests (e.g. "--new 4:add").
1205
1206 @type mods: list of tuples
1207 @param mods: Modifications as given by command line parser
1208 @rtype: list of tuples
1209 @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1210
1211 """
1212 result = []
1213
1214 for (idx, params) in mods:
1215 if idx == constants.DDM_ADD:
1216
1217 action = constants.DDM_ADD
1218 idxno = -1
1219 elif idx == constants.DDM_REMOVE:
1220
1221 action = constants.DDM_REMOVE
1222 idxno = -1
1223 else:
1224
1225 try:
1226 idxno = int(idx)
1227 except (TypeError, ValueError):
1228 raise errors.OpPrereqError("Non-numeric index '%s'" % idx,
1229 errors.ECODE_INVAL)
1230
1231 add = params.pop(constants.DDM_ADD, _MISSING)
1232 remove = params.pop(constants.DDM_REMOVE, _MISSING)
1233 modify = params.pop(constants.DDM_MODIFY, _MISSING)
1234
1235 if modify is _MISSING:
1236 if not (add is _MISSING or remove is _MISSING):
1237 raise errors.OpPrereqError("Cannot add and remove at the same time",
1238 errors.ECODE_INVAL)
1239 elif add is not _MISSING:
1240 action = constants.DDM_ADD
1241 elif remove is not _MISSING:
1242 action = constants.DDM_REMOVE
1243 else:
1244 action = constants.DDM_MODIFY
1245
1246 elif add is _MISSING and remove is _MISSING:
1247 action = constants.DDM_MODIFY
1248 else:
1249 raise errors.OpPrereqError("Cannot modify and add/remove at the"
1250 " same time", errors.ECODE_INVAL)
1251
1252 assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1253
1254 if action == constants.DDM_REMOVE and params:
1255 raise errors.OpPrereqError("Not accepting parameters on removal",
1256 errors.ECODE_INVAL)
1257
1258 result.append((action, idxno, params))
1259
1260 return result
1261
1262
1276
1277
1279 """Modifies an instance.
1280
1281 All parameters take effect only at the next restart of the instance.
1282
1283 @param opts: the command line options selected by the user
1284 @type args: list
1285 @param args: should contain only one element, the instance name
1286 @rtype: int
1287 @return: the desired exit code
1288
1289 """
1290 if not (opts.nics or opts.disks or opts.disk_template or
1291 opts.hvparams or opts.beparams or opts.os or opts.osparams or
1292 opts.offline_inst or opts.online_inst or opts.runtime_mem):
1293 ToStderr("Please give at least one of the parameters.")
1294 return 1
1295
1296 for param in opts.beparams:
1297 if isinstance(opts.beparams[param], basestring):
1298 if opts.beparams[param].lower() == "default":
1299 opts.beparams[param] = constants.VALUE_DEFAULT
1300
1301 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT,
1302 allowed_values=[constants.VALUE_DEFAULT])
1303
1304 for param in opts.hvparams:
1305 if isinstance(opts.hvparams[param], basestring):
1306 if opts.hvparams[param].lower() == "default":
1307 opts.hvparams[param] = constants.VALUE_DEFAULT
1308
1309 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1310 allowed_values=[constants.VALUE_DEFAULT])
1311
1312 nics = _ConvertNicDiskModifications(opts.nics)
1313 disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1314
1315 if (opts.disk_template and
1316 opts.disk_template in constants.DTS_INT_MIRROR and
1317 not opts.node):
1318 ToStderr("Changing the disk template to a mirrored one requires"
1319 " specifying a secondary node")
1320 return 1
1321
1322 if opts.offline_inst:
1323 offline = True
1324 elif opts.online_inst:
1325 offline = False
1326 else:
1327 offline = None
1328
1329 op = opcodes.OpInstanceSetParams(instance_name=args[0],
1330 nics=nics,
1331 disks=disks,
1332 disk_template=opts.disk_template,
1333 remote_node=opts.node,
1334 hvparams=opts.hvparams,
1335 beparams=opts.beparams,
1336 runtime_mem=opts.runtime_mem,
1337 os_name=opts.os,
1338 osparams=opts.osparams,
1339 force_variant=opts.force_variant,
1340 force=opts.force,
1341 wait_for_sync=opts.wait_for_sync,
1342 offline=offline,
1343 conflicts_check=opts.conflicts_check,
1344 ignore_ipolicy=opts.ignore_ipolicy)
1345
1346
1347 result = SubmitOrSend(op, opts)
1348
1349 if result:
1350 ToStdout("Modified instance %s", args[0])
1351 for param, data in result:
1352 ToStdout(" - %-5s -> %s", param, data)
1353 ToStdout("Please don't forget that most parameters take effect"
1354 " only at the next (re)start of the instance initiated by"
1355 " ganeti; restarting from within the instance will"
1356 " not be enough.")
1357 return 0
1358
1359
1361 """Moves an instance to another group.
1362
1363 """
1364 (instance_name, ) = args
1365
1366 cl = GetClient()
1367
1368 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1369 iallocator=opts.iallocator,
1370 target_groups=opts.to,
1371 early_release=opts.early_release)
1372 result = SubmitOrSend(op, opts, cl=cl)
1373
1374
1375 jex = JobExecutor(cl=cl, opts=opts)
1376
1377 for (status, job_id) in result[constants.JOB_IDS_KEY]:
1378 jex.AddJobId(None, status, job_id)
1379
1380 results = jex.GetResults()
1381 bad_cnt = len([row for row in results if not row[0]])
1382 if bad_cnt == 0:
1383 ToStdout("Instance '%s' changed group successfully.", instance_name)
1384 rcode = constants.EXIT_SUCCESS
1385 else:
1386 ToStdout("There were %s errors while changing group of instance '%s'.",
1387 bad_cnt, instance_name)
1388 rcode = constants.EXIT_FAILURE
1389
1390 return rcode
1391
1392
1393
1394 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1395 help="Do not ask for confirmation when more than"
1396 " one instance is affected",
1397 action="store_true", default=False)
1398
1399 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1400 help="Filter by nodes (primary only)",
1401 const=_EXPAND_NODES_PRI, action="store_const")
1402
1403 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1404 help="Filter by nodes (secondary only)",
1405 const=_EXPAND_NODES_SEC, action="store_const")
1406
1407 m_node_opt = cli_option("--node", dest="multi_mode",
1408 help="Filter by nodes (primary and secondary)",
1409 const=_EXPAND_NODES_BOTH, action="store_const")
1410
1411 m_clust_opt = cli_option("--all", dest="multi_mode",
1412 help="Select all instances in the cluster",
1413 const=_EXPAND_CLUSTER, action="store_const")
1414
1415 m_inst_opt = cli_option("--instance", dest="multi_mode",
1416 help="Filter by instance name [default]",
1417 const=_EXPAND_INSTANCES, action="store_const")
1418
1419 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1420 help="Filter by node tag",
1421 const=_EXPAND_NODES_BOTH_BY_TAGS,
1422 action="store_const")
1423
1424 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1425 help="Filter by primary node tag",
1426 const=_EXPAND_NODES_PRI_BY_TAGS,
1427 action="store_const")
1428
1429 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1430 help="Filter by secondary node tag",
1431 const=_EXPAND_NODES_SEC_BY_TAGS,
1432 action="store_const")
1433
1434 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1435 help="Filter by instance tag",
1436 const=_EXPAND_INSTANCES_BY_TAGS,
1437 action="store_const")
1438
1439
1440 add_opts = [
1441 NOSTART_OPT,
1442 OS_OPT,
1443 FORCE_VARIANT_OPT,
1444 NO_INSTALL_OPT,
1445 IGNORE_IPOLICY_OPT,
1446 ]
1447
1448 commands = {
1449 "add": (
1450 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1451 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1452 "Creates and adds a new instance to the cluster"),
1453 "batch-create": (
1454 BatchCreate, [ArgFile(min=1, max=1)],
1455 [DRY_RUN_OPT, PRIORITY_OPT, IALLOCATOR_OPT, SUBMIT_OPT],
1456 "<instances.json>",
1457 "Create a bunch of instances based on specs in the file."),
1458 "console": (
1459 ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1460 [SHOWCMD_OPT, PRIORITY_OPT],
1461 "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1462 "failover": (
1463 FailoverInstance, ARGS_ONE_INSTANCE,
1464 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1465 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT,
1466 IGNORE_IPOLICY_OPT],
1467 "[-f] <instance>", "Stops the instance, changes its primary node and"
1468 " (if it was originally running) starts it on the new node"
1469 " (the secondary for mirrored instances or any node"
1470 " for shared storage)."),
1471 "migrate": (
1472 MigrateInstance, ARGS_ONE_INSTANCE,
1473 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1474 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT,
1475 IGNORE_IPOLICY_OPT, NORUNTIME_CHGS_OPT, SUBMIT_OPT],
1476 "[-f] <instance>", "Migrate instance to its secondary node"
1477 " (only for mirrored instances)"),
1478 "move": (
1479 MoveInstance, ARGS_ONE_INSTANCE,
1480 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1481 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT, IGNORE_IPOLICY_OPT],
1482 "[-f] <instance>", "Move instance to an arbitrary node"
1483 " (only for instances of type file and lv)"),
1484 "info": (
1485 ShowInstanceConfig, ARGS_MANY_INSTANCES,
1486 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1487 "[-s] {--all | <instance>...}",
1488 "Show information on the specified instance(s)"),
1489 "list": (
1490 ListInstances, ARGS_MANY_INSTANCES,
1491 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1492 FORCE_FILTER_OPT],
1493 "[<instance>...]",
1494 "Lists the instances and their status. The available fields can be shown"
1495 " using the \"list-fields\" command (see the man page for details)."
1496 " The default field list is (in order): %s." %
1497 utils.CommaJoin(_LIST_DEF_FIELDS),
1498 ),
1499 "list-fields": (
1500 ListInstanceFields, [ArgUnknown()],
1501 [NOHDR_OPT, SEP_OPT],
1502 "[fields...]",
1503 "Lists all available fields for instances"),
1504 "reinstall": (
1505 ReinstallInstance, [ArgInstance()],
1506 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1507 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1508 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1509 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1510 "[-f] <instance>", "Reinstall a stopped instance"),
1511 "remove": (
1512 RemoveInstance, ARGS_ONE_INSTANCE,
1513 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1514 DRY_RUN_OPT, PRIORITY_OPT],
1515 "[-f] <instance>", "Shuts down the instance and removes it"),
1516 "rename": (
1517 RenameInstance,
1518 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1519 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1520 "<instance> <new_name>", "Rename the instance"),
1521 "replace-disks": (
1522 ReplaceDisks, ARGS_ONE_INSTANCE,
1523 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1524 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1525 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_IPOLICY_OPT],
1526 "[-s|-p|-a|-n NODE|-I NAME] <instance>",
1527 "Replaces disks for the instance"),
1528 "modify": (
1529 SetInstanceParams, ARGS_ONE_INSTANCE,
1530 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1531 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1532 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT, OFFLINE_INST_OPT,
1533 ONLINE_INST_OPT, IGNORE_IPOLICY_OPT, RUNTIME_MEM_OPT,
1534 NOCONFLICTSCHECK_OPT],
1535 "<instance>", "Alters the parameters of an instance"),
1536 "shutdown": (
1537 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1538 [FORCE_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1539 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1540 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1541 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1542 "<instance>", "Stops an instance"),
1543 "startup": (
1544 GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1545 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1546 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1547 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1548 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1549 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1550 "<instance>", "Starts an instance"),
1551 "reboot": (
1552 GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1553 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1554 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1555 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1556 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1557 "<instance>", "Reboots an instance"),
1558 "activate-disks": (
1559 ActivateDisks, ARGS_ONE_INSTANCE,
1560 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT, WFSYNC_OPT],
1561 "<instance>", "Activate an instance's disks"),
1562 "deactivate-disks": (
1563 DeactivateDisks, ARGS_ONE_INSTANCE,
1564 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1565 "[-f] <instance>", "Deactivate an instance's disks"),
1566 "recreate-disks": (
1567 RecreateDisks, ARGS_ONE_INSTANCE,
1568 [SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT,
1569 IALLOCATOR_OPT],
1570 "<instance>", "Recreate an instance's disks"),
1571 "grow-disk": (
1572 GrowDisk,
1573 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1574 ArgUnknown(min=1, max=1)],
1575 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT, ABSOLUTE_OPT],
1576 "<instance> <disk> <size>", "Grow an instance's disk"),
1577 "change-group": (
1578 ChangeGroup, ARGS_ONE_INSTANCE,
1579 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, PRIORITY_OPT, SUBMIT_OPT],
1580 "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1581 "list-tags": (
1582 ListTags, ARGS_ONE_INSTANCE, [],
1583 "<instance_name>", "List the tags of the given instance"),
1584 "add-tags": (
1585 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1586 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1587 "<instance_name> tag...", "Add tags to the given instance"),
1588 "remove-tags": (
1589 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1590 [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1591 "<instance_name> tag...", "Remove tags from given instance"),
1592 }
1593
1594
1595 aliases = {
1596 "start": "startup",
1597 "stop": "shutdown",
1598 "show": "info",
1599 }
1600
1601
1603 return GenericMain(commands, aliases=aliases,
1604 override={"tag_type": constants.TAG_INSTANCE},
1605 env_override=_ENV_OVERRIDE)
1606