1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Functions used by the node daemon
23
24 @var _ALLOWED_UPLOAD_FILES: denotes which files are accepted in
25 the L{UploadFile} function
26 @var _ALLOWED_CLEAN_DIRS: denotes which directories are accepted
27 in the L{_CleanDirectory} function
28
29 """
30
31
32
33
34
35
36
37
38 import os
39 import os.path
40 import shutil
41 import time
42 import stat
43 import errno
44 import re
45 import random
46 import logging
47 import tempfile
48 import zlib
49 import base64
50 import signal
51
52 from ganeti import errors
53 from ganeti import utils
54 from ganeti import ssh
55 from ganeti import hypervisor
56 from ganeti import constants
57 from ganeti import bdev
58 from ganeti import objects
59 from ganeti import ssconf
60 from ganeti import serializer
61 from ganeti import netutils
62 from ganeti import runtime
63
64
65 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
66 _ALLOWED_CLEAN_DIRS = frozenset([
67 constants.DATA_DIR,
68 constants.JOB_QUEUE_ARCHIVE_DIR,
69 constants.QUEUE_DIR,
70 constants.CRYPTO_KEYS_DIR,
71 ])
72 _MAX_SSL_CERT_VALIDITY = 7 * 24 * 60 * 60
73 _X509_KEY_FILE = "key"
74 _X509_CERT_FILE = "cert"
75 _IES_STATUS_FILE = "status"
76 _IES_PID_FILE = "pid"
77 _IES_CA_FILE = "ca"
78
79
80 _LVSLINE_REGEX = re.compile("^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
84 """Class denoting RPC failure.
85
86 Its argument is the error message.
87
88 """
89
90
91 -def _Fail(msg, *args, **kwargs):
92 """Log an error and the raise an RPCFail exception.
93
94 This exception is then handled specially in the ganeti daemon and
95 turned into a 'failed' return type. As such, this function is a
96 useful shortcut for logging the error and returning it to the master
97 daemon.
98
99 @type msg: string
100 @param msg: the text of the exception
101 @raise RPCFail
102
103 """
104 if args:
105 msg = msg % args
106 if "log" not in kwargs or kwargs["log"]:
107 if "exc" in kwargs and kwargs["exc"]:
108 logging.exception(msg)
109 else:
110 logging.error(msg)
111 raise RPCFail(msg)
112
115 """Simple wrapper to return a SimpleStore.
116
117 @rtype: L{ssconf.SimpleStore}
118 @return: a SimpleStore instance
119
120 """
121 return ssconf.SimpleStore()
122
125 """Simple wrapper to return an SshRunner.
126
127 @type cluster_name: str
128 @param cluster_name: the cluster name, which is needed
129 by the SshRunner constructor
130 @rtype: L{ssh.SshRunner}
131 @return: an SshRunner instance
132
133 """
134 return ssh.SshRunner(cluster_name)
135
138 """Unpacks data compressed by the RPC client.
139
140 @type data: list or tuple
141 @param data: Data sent by RPC client
142 @rtype: str
143 @return: Decompressed data
144
145 """
146 assert isinstance(data, (list, tuple))
147 assert len(data) == 2
148 (encoding, content) = data
149 if encoding == constants.RPC_ENCODING_NONE:
150 return content
151 elif encoding == constants.RPC_ENCODING_ZLIB_BASE64:
152 return zlib.decompress(base64.b64decode(content))
153 else:
154 raise AssertionError("Unknown data encoding")
155
158 """Removes all regular files in a directory.
159
160 @type path: str
161 @param path: the directory to clean
162 @type exclude: list
163 @param exclude: list of files to be excluded, defaults
164 to the empty list
165
166 """
167 if path not in _ALLOWED_CLEAN_DIRS:
168 _Fail("Path passed to _CleanDirectory not in allowed clean targets: '%s'",
169 path)
170
171 if not os.path.isdir(path):
172 return
173 if exclude is None:
174 exclude = []
175 else:
176
177 exclude = [os.path.normpath(i) for i in exclude]
178
179 for rel_name in utils.ListVisibleFiles(path):
180 full_name = utils.PathJoin(path, rel_name)
181 if full_name in exclude:
182 continue
183 if os.path.isfile(full_name) and not os.path.islink(full_name):
184 utils.RemoveFile(full_name)
185
209
210
211 _ALLOWED_UPLOAD_FILES = _BuildUploadFileList()
223
226 """Returns master information.
227
228 This is an utility function to compute master information, either
229 for consumption here or from the node daemon.
230
231 @rtype: tuple
232 @return: master_netdev, master_ip, master_name, primary_ip_family
233 @raise RPCFail: in case of errors
234
235 """
236 try:
237 cfg = _GetConfig()
238 master_netdev = cfg.GetMasterNetdev()
239 master_ip = cfg.GetMasterIP()
240 master_node = cfg.GetMasterNode()
241 primary_ip_family = cfg.GetPrimaryIPFamily()
242 except errors.ConfigurationError, err:
243 _Fail("Cluster configuration incomplete: %s", err, exc=True)
244 return (master_netdev, master_ip, master_node, primary_ip_family)
245
248 """Activate local node as master node.
249
250 The function will either try activate the IP address of the master
251 (unless someone else has it) or also start the master daemons, based
252 on the start_daemons parameter.
253
254 @type start_daemons: boolean
255 @param start_daemons: whether to start the master daemons
256 (ganeti-masterd and ganeti-rapi), or (if false) activate the
257 master ip
258 @type no_voting: boolean
259 @param no_voting: whether to start ganeti-masterd without a node vote
260 (if start_daemons is True), but still non-interactively
261 @rtype: None
262
263 """
264
265 master_netdev, master_ip, _, family = GetMasterInfo()
266
267 err_msgs = []
268
269 if start_daemons:
270 if no_voting:
271 masterd_args = "--no-voting --yes-do-it"
272 else:
273 masterd_args = ""
274
275 env = {
276 "EXTRA_MASTERD_ARGS": masterd_args,
277 }
278
279 result = utils.RunCmd([constants.DAEMON_UTIL, "start-master"], env=env)
280 if result.failed:
281 msg = "Can't start Ganeti master: %s" % result.output
282 logging.error(msg)
283 err_msgs.append(msg)
284
285 else:
286 if netutils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
287 if netutils.IPAddress.Own(master_ip):
288
289 logging.debug("Master IP already configured, doing nothing")
290 else:
291 msg = "Someone else has the master ip, not activating"
292 logging.error(msg)
293 err_msgs.append(msg)
294 else:
295 ipcls = netutils.IP4Address
296 if family == netutils.IP6Address.family:
297 ipcls = netutils.IP6Address
298
299 result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add",
300 "%s/%d" % (master_ip, ipcls.iplen),
301 "dev", master_netdev, "label",
302 "%s:0" % master_netdev])
303 if result.failed:
304 msg = "Can't activate master IP: %s" % result.output
305 logging.error(msg)
306 err_msgs.append(msg)
307
308
309 if ipcls == netutils.IP4Address:
310 utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev, "-s",
311 master_ip, master_ip])
312 elif ipcls == netutils.IP6Address:
313 try:
314 utils.RunCmd(["ndisc6", "-q", "-r 3", master_ip, master_netdev])
315 except errors.OpExecError:
316
317 logging.warning("Can't execute ndisc6, please install if missing")
318
319 if err_msgs:
320 _Fail("; ".join(err_msgs))
321
324 """Deactivate this node as master.
325
326 The function will always try to deactivate the IP address of the
327 master. It will also stop the master daemons depending on the
328 stop_daemons parameter.
329
330 @type stop_daemons: boolean
331 @param stop_daemons: whether to also stop the master daemons
332 (ganeti-masterd and ganeti-rapi)
333 @rtype: None
334
335 """
336
337
338
339
340 master_netdev, master_ip, _, family = GetMasterInfo()
341
342 ipcls = netutils.IP4Address
343 if family == netutils.IP6Address.family:
344 ipcls = netutils.IP6Address
345
346 result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del",
347 "%s/%d" % (master_ip, ipcls.iplen),
348 "dev", master_netdev])
349 if result.failed:
350 logging.error("Can't remove the master IP, error: %s", result.output)
351
352
353 if stop_daemons:
354 result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"])
355 if result.failed:
356 logging.error("Could not stop Ganeti master, command %s had exitcode %s"
357 " and error %s",
358 result.cmd, result.exit_code, result.output)
359
362 """Modify a host entry in /etc/hosts.
363
364 @param mode: The mode to operate. Either add or remove entry
365 @param host: The host to operate on
366 @param ip: The ip associated with the entry
367
368 """
369 if mode == constants.ETC_HOSTS_ADD:
370 if not ip:
371 RPCFail("Mode 'add' needs 'ip' parameter, but parameter not"
372 " present")
373 utils.AddHostToEtcHosts(host, ip)
374 elif mode == constants.ETC_HOSTS_REMOVE:
375 if ip:
376 RPCFail("Mode 'remove' does not allow 'ip' parameter, but"
377 " parameter is present")
378 utils.RemoveHostFromEtcHosts(host)
379 else:
380 RPCFail("Mode not supported")
381
384 """Cleans up and remove the current node.
385
386 This function cleans up and prepares the current node to be removed
387 from the cluster.
388
389 If processing is successful, then it raises an
390 L{errors.QuitGanetiException} which is used as a special case to
391 shutdown the node daemon.
392
393 @param modify_ssh_setup: boolean
394
395 """
396 _CleanDirectory(constants.DATA_DIR)
397 _CleanDirectory(constants.CRYPTO_KEYS_DIR)
398 JobQueuePurge()
399
400 if modify_ssh_setup:
401 try:
402 priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
403
404 utils.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
405
406 utils.RemoveFile(priv_key)
407 utils.RemoveFile(pub_key)
408 except errors.OpExecError:
409 logging.exception("Error while processing ssh files")
410
411 try:
412 utils.RemoveFile(constants.CONFD_HMAC_KEY)
413 utils.RemoveFile(constants.RAPI_CERT_FILE)
414 utils.RemoveFile(constants.NODED_CERT_FILE)
415 except:
416 logging.exception("Error while removing cluster secrets")
417
418 result = utils.RunCmd([constants.DAEMON_UTIL, "stop", constants.CONFD])
419 if result.failed:
420 logging.error("Command %s failed with exitcode %s and error %s",
421 result.cmd, result.exit_code, result.output)
422
423
424 raise errors.QuitGanetiException(True, "Shutdown scheduled")
425
428 """Gives back a hash with different information about the node.
429
430 @type vgname: C{string}
431 @param vgname: the name of the volume group to ask for disk space information
432 @type hypervisor_type: C{str}
433 @param hypervisor_type: the name of the hypervisor to ask for
434 memory information
435 @rtype: C{dict}
436 @return: dictionary with the following keys:
437 - vg_size is the size of the configured volume group in MiB
438 - vg_free is the free size of the volume group in MiB
439 - memory_dom0 is the memory allocated for domain0 in MiB
440 - memory_free is the currently available (free) ram in MiB
441 - memory_total is the total number of ram in MiB
442
443 """
444 outputarray = {}
445
446 if vgname is not None:
447 vginfo = bdev.LogicalVolume.GetVGInfo([vgname])
448 vg_free = vg_size = None
449 if vginfo:
450 vg_free = int(round(vginfo[0][0], 0))
451 vg_size = int(round(vginfo[0][1], 0))
452 outputarray["vg_size"] = vg_size
453 outputarray["vg_free"] = vg_free
454
455 if hypervisor_type is not None:
456 hyper = hypervisor.GetHypervisor(hypervisor_type)
457 hyp_info = hyper.GetNodeInfo()
458 if hyp_info is not None:
459 outputarray.update(hyp_info)
460
461 outputarray["bootid"] = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
462
463 return outputarray
464
467 """Verify the status of the local node.
468
469 Based on the input L{what} parameter, various checks are done on the
470 local node.
471
472 If the I{filelist} key is present, this list of
473 files is checksummed and the file/checksum pairs are returned.
474
475 If the I{nodelist} key is present, we check that we have
476 connectivity via ssh with the target nodes (and check the hostname
477 report).
478
479 If the I{node-net-test} key is present, we check that we have
480 connectivity to the given nodes via both primary IP and, if
481 applicable, secondary IPs.
482
483 @type what: C{dict}
484 @param what: a dictionary of things to check:
485 - filelist: list of files for which to compute checksums
486 - nodelist: list of nodes we should check ssh communication with
487 - node-net-test: list of nodes we should check node daemon port
488 connectivity with
489 - hypervisor: list with hypervisors to run the verify for
490 @rtype: dict
491 @return: a dictionary with the same keys as the input dict, and
492 values representing the result of the checks
493
494 """
495 result = {}
496 my_name = netutils.Hostname.GetSysName()
497 port = netutils.GetDaemonPort(constants.NODED)
498 vm_capable = my_name not in what.get(constants.NV_VMNODES, [])
499
500 if constants.NV_HYPERVISOR in what and vm_capable:
501 result[constants.NV_HYPERVISOR] = tmp = {}
502 for hv_name in what[constants.NV_HYPERVISOR]:
503 try:
504 val = hypervisor.GetHypervisor(hv_name).Verify()
505 except errors.HypervisorError, err:
506 val = "Error while checking hypervisor: %s" % str(err)
507 tmp[hv_name] = val
508
509 if constants.NV_HVPARAMS in what and vm_capable:
510 result[constants.NV_HVPARAMS] = tmp = []
511 for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
512 try:
513 logging.info("Validating hv %s, %s", hv_name, hvparms)
514 hypervisor.GetHypervisor(hv_name).ValidateParameters(hvparms)
515 except errors.HypervisorError, err:
516 tmp.append((source, hv_name, str(err)))
517
518 if constants.NV_FILELIST in what:
519 result[constants.NV_FILELIST] = utils.FingerprintFiles(
520 what[constants.NV_FILELIST])
521
522 if constants.NV_NODELIST in what:
523 (nodes, bynode) = what[constants.NV_NODELIST]
524
525
526 try:
527 nodes.extend(bynode[my_name])
528 except KeyError:
529 pass
530
531
532 random.shuffle(nodes)
533
534
535 val = {}
536 for node in nodes:
537 success, message = _GetSshRunner(cluster_name).VerifyNodeHostname(node)
538 if not success:
539 val[node] = message
540
541 result[constants.NV_NODELIST] = val
542
543 if constants.NV_NODENETTEST in what:
544 result[constants.NV_NODENETTEST] = tmp = {}
545 my_pip = my_sip = None
546 for name, pip, sip in what[constants.NV_NODENETTEST]:
547 if name == my_name:
548 my_pip = pip
549 my_sip = sip
550 break
551 if not my_pip:
552 tmp[my_name] = ("Can't find my own primary/secondary IP"
553 " in the node list")
554 else:
555 for name, pip, sip in what[constants.NV_NODENETTEST]:
556 fail = []
557 if not netutils.TcpPing(pip, port, source=my_pip):
558 fail.append("primary")
559 if sip != pip:
560 if not netutils.TcpPing(sip, port, source=my_sip):
561 fail.append("secondary")
562 if fail:
563 tmp[name] = ("failure using the %s interface(s)" %
564 " and ".join(fail))
565
566 if constants.NV_MASTERIP in what:
567
568
569 master_name, master_ip = what[constants.NV_MASTERIP]
570 if master_name == my_name:
571 source = constants.IP4_ADDRESS_LOCALHOST
572 else:
573 source = None
574 result[constants.NV_MASTERIP] = netutils.TcpPing(master_ip, port,
575 source=source)
576
577 if constants.NV_OOB_PATHS in what:
578 result[constants.NV_OOB_PATHS] = tmp = []
579 for path in what[constants.NV_OOB_PATHS]:
580 try:
581 st = os.stat(path)
582 except OSError, err:
583 tmp.append("error stating out of band helper: %s" % err)
584 else:
585 if stat.S_ISREG(st.st_mode):
586 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR:
587 tmp.append(None)
588 else:
589 tmp.append("out of band helper %s is not executable" % path)
590 else:
591 tmp.append("out of band helper %s is not a file" % path)
592
593 if constants.NV_LVLIST in what and vm_capable:
594 try:
595 val = GetVolumeList(utils.ListVolumeGroups().keys())
596 except RPCFail, err:
597 val = str(err)
598 result[constants.NV_LVLIST] = val
599
600 if constants.NV_INSTANCELIST in what and vm_capable:
601
602 try:
603 val = GetInstanceList(what[constants.NV_INSTANCELIST])
604 except RPCFail, err:
605 val = str(err)
606 result[constants.NV_INSTANCELIST] = val
607
608 if constants.NV_VGLIST in what and vm_capable:
609 result[constants.NV_VGLIST] = utils.ListVolumeGroups()
610
611 if constants.NV_PVLIST in what and vm_capable:
612 result[constants.NV_PVLIST] = \
613 bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
614 filter_allocatable=False)
615
616 if constants.NV_VERSION in what:
617 result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
618 constants.RELEASE_VERSION)
619
620 if constants.NV_HVINFO in what and vm_capable:
621 hyper = hypervisor.GetHypervisor(what[constants.NV_HVINFO])
622 result[constants.NV_HVINFO] = hyper.GetNodeInfo()
623
624 if constants.NV_DRBDLIST in what and vm_capable:
625 try:
626 used_minors = bdev.DRBD8.GetUsedDevs().keys()
627 except errors.BlockDeviceError, err:
628 logging.warning("Can't get used minors list", exc_info=True)
629 used_minors = str(err)
630 result[constants.NV_DRBDLIST] = used_minors
631
632 if constants.NV_DRBDHELPER in what and vm_capable:
633 status = True
634 try:
635 payload = bdev.BaseDRBD.GetUsermodeHelper()
636 except errors.BlockDeviceError, err:
637 logging.error("Can't get DRBD usermode helper: %s", str(err))
638 status = False
639 payload = str(err)
640 result[constants.NV_DRBDHELPER] = (status, payload)
641
642 if constants.NV_NODESETUP in what:
643 result[constants.NV_NODESETUP] = tmpr = []
644 if not os.path.isdir("/sys/block") or not os.path.isdir("/sys/class/net"):
645 tmpr.append("The sysfs filesytem doesn't seem to be mounted"
646 " under /sys, missing required directories /sys/block"
647 " and /sys/class/net")
648 if (not os.path.isdir("/proc/sys") or
649 not os.path.isfile("/proc/sysrq-trigger")):
650 tmpr.append("The procfs filesystem doesn't seem to be mounted"
651 " under /proc, missing required directory /proc/sys and"
652 " the file /proc/sysrq-trigger")
653
654 if constants.NV_TIME in what:
655 result[constants.NV_TIME] = utils.SplitTime(time.time())
656
657 if constants.NV_OSLIST in what and vm_capable:
658 result[constants.NV_OSLIST] = DiagnoseOS()
659
660 if constants.NV_BRIDGES in what and vm_capable:
661 result[constants.NV_BRIDGES] = [bridge
662 for bridge in what[constants.NV_BRIDGES]
663 if not utils.BridgeExists(bridge)]
664 return result
665
668 """Return the size of the given block devices
669
670 @type devices: list
671 @param devices: list of block device nodes to query
672 @rtype: dict
673 @return:
674 dictionary of all block devices under /dev (key). The value is their
675 size in MiB.
676
677 {'/dev/disk/by-uuid/123456-12321231-312312-312': 124}
678
679 """
680 DEV_PREFIX = "/dev/"
681 blockdevs = {}
682
683 for devpath in devices:
684 if os.path.commonprefix([DEV_PREFIX, devpath]) != DEV_PREFIX:
685 continue
686
687 try:
688 st = os.stat(devpath)
689 except EnvironmentError, err:
690 logging.warning("Error stat()'ing device %s: %s", devpath, str(err))
691 continue
692
693 if stat.S_ISBLK(st.st_mode):
694 result = utils.RunCmd(["blockdev", "--getsize64", devpath])
695 if result.failed:
696
697 logging.warning("Cannot get size for block device %s", devpath)
698 continue
699
700 size = int(result.stdout) / (1024 * 1024)
701 blockdevs[devpath] = size
702 return blockdevs
703
706 """Compute list of logical volumes and their size.
707
708 @type vg_names: list
709 @param vg_names: the volume groups whose LVs we should list, or
710 empty for all volume groups
711 @rtype: dict
712 @return:
713 dictionary of all partions (key) with value being a tuple of
714 their size (in MiB), inactive and online status::
715
716 {'xenvg/test1': ('20.06', True, True)}
717
718 in case of errors, a string is returned with the error
719 details.
720
721 """
722 lvs = {}
723 sep = "|"
724 if not vg_names:
725 vg_names = []
726 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
727 "--separator=%s" % sep,
728 "-ovg_name,lv_name,lv_size,lv_attr"] + vg_names)
729 if result.failed:
730 _Fail("Failed to list logical volumes, lvs output: %s", result.output)
731
732 for line in result.stdout.splitlines():
733 line = line.strip()
734 match = _LVSLINE_REGEX.match(line)
735 if not match:
736 logging.error("Invalid line returned from lvs output: '%s'", line)
737 continue
738 vg_name, name, size, attr = match.groups()
739 inactive = attr[4] == "-"
740 online = attr[5] == "o"
741 virtual = attr[0] == "v"
742 if virtual:
743
744
745 continue
746 lvs[vg_name + "/" + name] = (size, inactive, online)
747
748 return lvs
749
752 """List the volume groups and their size.
753
754 @rtype: dict
755 @return: dictionary with keys volume name and values the
756 size of the volume
757
758 """
759 return utils.ListVolumeGroups()
760
763 """List all volumes on this node.
764
765 @rtype: list
766 @return:
767 A list of dictionaries, each having four keys:
768 - name: the logical volume name,
769 - size: the size of the logical volume
770 - dev: the physical device on which the LV lives
771 - vg: the volume group to which it belongs
772
773 In case of errors, we return an empty list and log the
774 error.
775
776 Note that since a logical volume can live on multiple physical
777 volumes, the resulting list might include a logical volume
778 multiple times.
779
780 """
781 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
782 "--separator=|",
783 "--options=lv_name,lv_size,devices,vg_name"])
784 if result.failed:
785 _Fail("Failed to list logical volumes, lvs output: %s",
786 result.output)
787
788 def parse_dev(dev):
789 return dev.split("(")[0]
790
791 def handle_dev(dev):
792 return [parse_dev(x) for x in dev.split(",")]
793
794 def map_line(line):
795 line = [v.strip() for v in line]
796 return [{"name": line[0], "size": line[1],
797 "dev": dev, "vg": line[3]} for dev in handle_dev(line[2])]
798
799 all_devs = []
800 for line in result.stdout.splitlines():
801 if line.count("|") >= 3:
802 all_devs.extend(map_line(line.split("|")))
803 else:
804 logging.warning("Strange line in the output from lvs: '%s'", line)
805 return all_devs
806
809 """Check if a list of bridges exist on the current node.
810
811 @rtype: boolean
812 @return: C{True} if all of them exist, C{False} otherwise
813
814 """
815 missing = []
816 for bridge in bridges_list:
817 if not utils.BridgeExists(bridge):
818 missing.append(bridge)
819
820 if missing:
821 _Fail("Missing bridges %s", utils.CommaJoin(missing))
822
825 """Provides a list of instances.
826
827 @type hypervisor_list: list
828 @param hypervisor_list: the list of hypervisors to query information
829
830 @rtype: list
831 @return: a list of all running instances on the current node
832 - instance1.example.com
833 - instance2.example.com
834
835 """
836 results = []
837 for hname in hypervisor_list:
838 try:
839 names = hypervisor.GetHypervisor(hname).ListInstances()
840 results.extend(names)
841 except errors.HypervisorError, err:
842 _Fail("Error enumerating instances (hypervisor %s): %s",
843 hname, err, exc=True)
844
845 return results
846
849 """Gives back the information about an instance as a dictionary.
850
851 @type instance: string
852 @param instance: the instance name
853 @type hname: string
854 @param hname: the hypervisor type of the instance
855
856 @rtype: dict
857 @return: dictionary with the following keys:
858 - memory: memory size of instance (int)
859 - state: xen state of instance (string)
860 - time: cpu time of instance (float)
861
862 """
863 output = {}
864
865 iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance)
866 if iinfo is not None:
867 output["memory"] = iinfo[2]
868 output["state"] = iinfo[4]
869 output["time"] = iinfo[5]
870
871 return output
872
875 """Gives whether an instance can be migrated.
876
877 @type instance: L{objects.Instance}
878 @param instance: object representing the instance to be checked.
879
880 @rtype: tuple
881 @return: tuple of (result, description) where:
882 - result: whether the instance can be migrated or not
883 - description: a description of the issue, if relevant
884
885 """
886 hyper = hypervisor.GetHypervisor(instance.hypervisor)
887 iname = instance.name
888 if iname not in hyper.ListInstances():
889 _Fail("Instance %s is not running", iname)
890
891 for idx in range(len(instance.disks)):
892 link_name = _GetBlockDevSymlinkPath(iname, idx)
893 if not os.path.islink(link_name):
894 logging.warning("Instance %s is missing symlink %s for disk %d",
895 iname, link_name, idx)
896
899 """Gather data about all instances.
900
901 This is the equivalent of L{GetInstanceInfo}, except that it
902 computes data for all instances at once, thus being faster if one
903 needs data about more than one instance.
904
905 @type hypervisor_list: list
906 @param hypervisor_list: list of hypervisors to query for instance data
907
908 @rtype: dict
909 @return: dictionary of instance: data, with data having the following keys:
910 - memory: memory size of instance (int)
911 - state: xen state of instance (string)
912 - time: cpu time of instance (float)
913 - vcpus: the number of vcpus
914
915 """
916 output = {}
917
918 for hname in hypervisor_list:
919 iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo()
920 if iinfo:
921 for name, _, memory, vcpus, state, times in iinfo:
922 value = {
923 "memory": memory,
924 "vcpus": vcpus,
925 "state": state,
926 "time": times,
927 }
928 if name in output:
929
930
931
932 for key in "memory", "vcpus":
933 if value[key] != output[name][key]:
934 _Fail("Instance %s is running twice"
935 " with different parameters", name)
936 output[name] = value
937
938 return output
939
942 """Compute the OS log filename for a given instance and operation.
943
944 The instance name and os name are passed in as strings since not all
945 operations have these as part of an instance object.
946
947 @type kind: string
948 @param kind: the operation type (e.g. add, import, etc.)
949 @type os_name: string
950 @param os_name: the os name
951 @type instance: string
952 @param instance: the name of the instance being imported/added/etc.
953 @type component: string or None
954 @param component: the name of the component of the instance being
955 transferred
956
957 """
958
959 if component:
960 assert "/" not in component
961 c_msg = "-%s" % component
962 else:
963 c_msg = ""
964 base = ("%s-%s-%s%s-%s.log" %
965 (kind, os_name, instance, c_msg, utils.TimestampForFilename()))
966 return utils.PathJoin(constants.LOG_OS_DIR, base)
967
970 """Add an OS to an instance.
971
972 @type instance: L{objects.Instance}
973 @param instance: Instance whose OS is to be installed
974 @type reinstall: boolean
975 @param reinstall: whether this is an instance reinstall
976 @type debug: integer
977 @param debug: debug level, passed to the OS scripts
978 @rtype: None
979
980 """
981 inst_os = OSFromDisk(instance.os)
982
983 create_env = OSEnvironment(instance, inst_os, debug)
984 if reinstall:
985 create_env["INSTANCE_REINSTALL"] = "1"
986
987 logfile = _InstanceLogName("add", instance.os, instance.name, None)
988
989 result = utils.RunCmd([inst_os.create_script], env=create_env,
990 cwd=inst_os.path, output=logfile, reset_env=True)
991 if result.failed:
992 logging.error("os create command '%s' returned error: %s, logfile: %s,"
993 " output: %s", result.cmd, result.fail_reason, logfile,
994 result.output)
995 lines = [utils.SafeEncode(val)
996 for val in utils.TailFile(logfile, lines=20)]
997 _Fail("OS create script failed (%s), last lines in the"
998 " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
999
1002 """Run the OS rename script for an instance.
1003
1004 @type instance: L{objects.Instance}
1005 @param instance: Instance whose OS is to be installed
1006 @type old_name: string
1007 @param old_name: previous instance name
1008 @type debug: integer
1009 @param debug: debug level, passed to the OS scripts
1010 @rtype: boolean
1011 @return: the success of the operation
1012
1013 """
1014 inst_os = OSFromDisk(instance.os)
1015
1016 rename_env = OSEnvironment(instance, inst_os, debug)
1017 rename_env["OLD_INSTANCE_NAME"] = old_name
1018
1019 logfile = _InstanceLogName("rename", instance.os,
1020 "%s-%s" % (old_name, instance.name), None)
1021
1022 result = utils.RunCmd([inst_os.rename_script], env=rename_env,
1023 cwd=inst_os.path, output=logfile, reset_env=True)
1024
1025 if result.failed:
1026 logging.error("os create command '%s' returned error: %s output: %s",
1027 result.cmd, result.fail_reason, result.output)
1028 lines = [utils.SafeEncode(val)
1029 for val in utils.TailFile(logfile, lines=20)]
1030 _Fail("OS rename script failed (%s), last lines in the"
1031 " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
1032
1037
1040 """Set up symlinks to a instance's block device.
1041
1042 This is an auxiliary function run when an instance is start (on the primary
1043 node) or when an instance is migrated (on the target node).
1044
1045
1046 @param instance_name: the name of the target instance
1047 @param device_path: path of the physical block device, on the node
1048 @param idx: the disk index
1049 @return: absolute path to the disk's symlink
1050
1051 """
1052 link_name = _GetBlockDevSymlinkPath(instance_name, idx)
1053 try:
1054 os.symlink(device_path, link_name)
1055 except OSError, err:
1056 if err.errno == errno.EEXIST:
1057 if (not os.path.islink(link_name) or
1058 os.readlink(link_name) != device_path):
1059 os.remove(link_name)
1060 os.symlink(device_path, link_name)
1061 else:
1062 raise
1063
1064 return link_name
1065
1068 """Remove the block device symlinks belonging to the given instance.
1069
1070 """
1071 for idx, _ in enumerate(disks):
1072 link_name = _GetBlockDevSymlinkPath(instance_name, idx)
1073 if os.path.islink(link_name):
1074 try:
1075 os.remove(link_name)
1076 except OSError:
1077 logging.exception("Can't remove symlink '%s'", link_name)
1078
1081 """Set up an instance's block device(s).
1082
1083 This is run on the primary node at instance startup. The block
1084 devices must be already assembled.
1085
1086 @type instance: L{objects.Instance}
1087 @param instance: the instance whose disks we shoul assemble
1088 @rtype: list
1089 @return: list of (disk_object, device_path)
1090
1091 """
1092 block_devices = []
1093 for idx, disk in enumerate(instance.disks):
1094 device = _RecursiveFindBD(disk)
1095 if device is None:
1096 raise errors.BlockDeviceError("Block device '%s' is not set up." %
1097 str(disk))
1098 device.Open()
1099 try:
1100 link_name = _SymlinkBlockDev(instance.name, device.dev_path, idx)
1101 except OSError, e:
1102 raise errors.BlockDeviceError("Cannot create block device symlink: %s" %
1103 e.strerror)
1104
1105 block_devices.append((disk, link_name))
1106
1107 return block_devices
1108
1111 """Start an instance.
1112
1113 @type instance: L{objects.Instance}
1114 @param instance: the instance object
1115 @type startup_paused: bool
1116 @param instance: pause instance at startup?
1117 @rtype: None
1118
1119 """
1120 running_instances = GetInstanceList([instance.hypervisor])
1121
1122 if instance.name in running_instances:
1123 logging.info("Instance %s already running, not starting", instance.name)
1124 return
1125
1126 try:
1127 block_devices = _GatherAndLinkBlockDevs(instance)
1128 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1129 hyper.StartInstance(instance, block_devices, startup_paused)
1130 except errors.BlockDeviceError, err:
1131 _Fail("Block device error: %s", err, exc=True)
1132 except errors.HypervisorError, err:
1133 _RemoveBlockDevLinks(instance.name, instance.disks)
1134 _Fail("Hypervisor error: %s", err, exc=True)
1135
1138 """Shut an instance down.
1139
1140 @note: this functions uses polling with a hardcoded timeout.
1141
1142 @type instance: L{objects.Instance}
1143 @param instance: the instance object
1144 @type timeout: integer
1145 @param timeout: maximum timeout for soft shutdown
1146 @rtype: None
1147
1148 """
1149 hv_name = instance.hypervisor
1150 hyper = hypervisor.GetHypervisor(hv_name)
1151 iname = instance.name
1152
1153 if instance.name not in hyper.ListInstances():
1154 logging.info("Instance %s not running, doing nothing", iname)
1155 return
1156
1157 class _TryShutdown:
1158 def __init__(self):
1159 self.tried_once = False
1160
1161 def __call__(self):
1162 if iname not in hyper.ListInstances():
1163 return
1164
1165 try:
1166 hyper.StopInstance(instance, retry=self.tried_once)
1167 except errors.HypervisorError, err:
1168 if iname not in hyper.ListInstances():
1169
1170
1171 return
1172
1173 _Fail("Failed to stop instance %s: %s", iname, err)
1174
1175 self.tried_once = True
1176
1177 raise utils.RetryAgain()
1178
1179 try:
1180 utils.Retry(_TryShutdown(), 5, timeout)
1181 except utils.RetryTimeout:
1182
1183 logging.error("Shutdown of '%s' unsuccessful, forcing", iname)
1184
1185 try:
1186 hyper.StopInstance(instance, force=True)
1187 except errors.HypervisorError, err:
1188 if iname in hyper.ListInstances():
1189
1190
1191 _Fail("Failed to force stop instance %s: %s", iname, err)
1192
1193 time.sleep(1)
1194
1195 if iname in hyper.ListInstances():
1196 _Fail("Could not shutdown instance %s even by destroy", iname)
1197
1198 try:
1199 hyper.CleanupInstance(instance.name)
1200 except errors.HypervisorError, err:
1201 logging.warning("Failed to execute post-shutdown cleanup step: %s", err)
1202
1203 _RemoveBlockDevLinks(iname, instance.disks)
1204
1207 """Reboot an instance.
1208
1209 @type instance: L{objects.Instance}
1210 @param instance: the instance object to reboot
1211 @type reboot_type: str
1212 @param reboot_type: the type of reboot, one the following
1213 constants:
1214 - L{constants.INSTANCE_REBOOT_SOFT}: only reboot the
1215 instance OS, do not recreate the VM
1216 - L{constants.INSTANCE_REBOOT_HARD}: tear down and
1217 restart the VM (at the hypervisor level)
1218 - the other reboot type (L{constants.INSTANCE_REBOOT_FULL}) is
1219 not accepted here, since that mode is handled differently, in
1220 cmdlib, and translates into full stop and start of the
1221 instance (instead of a call_instance_reboot RPC)
1222 @type shutdown_timeout: integer
1223 @param shutdown_timeout: maximum timeout for soft shutdown
1224 @rtype: None
1225
1226 """
1227 running_instances = GetInstanceList([instance.hypervisor])
1228
1229 if instance.name not in running_instances:
1230 _Fail("Cannot reboot instance %s that is not running", instance.name)
1231
1232 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1233 if reboot_type == constants.INSTANCE_REBOOT_SOFT:
1234 try:
1235 hyper.RebootInstance(instance)
1236 except errors.HypervisorError, err:
1237 _Fail("Failed to soft reboot instance %s: %s", instance.name, err)
1238 elif reboot_type == constants.INSTANCE_REBOOT_HARD:
1239 try:
1240 InstanceShutdown(instance, shutdown_timeout)
1241 return StartInstance(instance, False)
1242 except errors.HypervisorError, err:
1243 _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
1244 else:
1245 _Fail("Invalid reboot_type received: %s", reboot_type)
1246
1261
1264 """Prepare the node to accept an instance.
1265
1266 @type instance: L{objects.Instance}
1267 @param instance: the instance definition
1268 @type info: string/data (opaque)
1269 @param info: migration information, from the source node
1270 @type target: string
1271 @param target: target host (usually ip), on this node
1272
1273 """
1274
1275 if instance.disk_template in constants.DTS_EXT_MIRROR:
1276
1277
1278 try:
1279 _GatherAndLinkBlockDevs(instance)
1280 except errors.BlockDeviceError, err:
1281 _Fail("Block device error: %s", err, exc=True)
1282
1283 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1284 try:
1285 hyper.AcceptInstance(instance, info, target)
1286 except errors.HypervisorError, err:
1287 if instance.disk_template in constants.DTS_EXT_MIRROR:
1288 _RemoveBlockDevLinks(instance.name, instance.disks)
1289 _Fail("Failed to accept instance: %s", err, exc=True)
1290
1293 """Finalize any preparation to accept an instance.
1294
1295 @type instance: L{objects.Instance}
1296 @param instance: the instance definition
1297 @type info: string/data (opaque)
1298 @param info: migration information, from the source node
1299 @type success: boolean
1300 @param success: whether the migration was a success or a failure
1301
1302 """
1303 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1304 try:
1305 hyper.FinalizeMigration(instance, info, success)
1306 except errors.HypervisorError, err:
1307 _Fail("Failed to finalize migration: %s", err, exc=True)
1308
1311 """Migrates an instance to another node.
1312
1313 @type instance: L{objects.Instance}
1314 @param instance: the instance definition
1315 @type target: string
1316 @param target: the target node name
1317 @type live: boolean
1318 @param live: whether the migration should be done live or not (the
1319 interpretation of this parameter is left to the hypervisor)
1320 @rtype: tuple
1321 @return: a tuple of (success, msg) where:
1322 - succes is a boolean denoting the success/failure of the operation
1323 - msg is a string with details in case of failure
1324
1325 """
1326 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1327
1328 try:
1329 hyper.MigrateInstance(instance, target, live)
1330 except errors.HypervisorError, err:
1331 _Fail("Failed to migrate instance: %s", err, exc=True)
1332
1335 """Creates a block device for an instance.
1336
1337 @type disk: L{objects.Disk}
1338 @param disk: the object describing the disk we should create
1339 @type size: int
1340 @param size: the size of the physical underlying device, in MiB
1341 @type owner: str
1342 @param owner: the name of the instance for which disk is created,
1343 used for device cache data
1344 @type on_primary: boolean
1345 @param on_primary: indicates if it is the primary node or not
1346 @type info: string
1347 @param info: string that will be sent to the physical device
1348 creation, used for example to set (LVM) tags on LVs
1349
1350 @return: the new unique_id of the device (this can sometime be
1351 computed only after creation), or None. On secondary nodes,
1352 it's not required to return anything.
1353
1354 """
1355
1356
1357 clist = []
1358 if disk.children:
1359 for child in disk.children:
1360 try:
1361 crdev = _RecursiveAssembleBD(child, owner, on_primary)
1362 except errors.BlockDeviceError, err:
1363 _Fail("Can't assemble device %s: %s", child, err)
1364 if on_primary or disk.AssembleOnSecondary():
1365
1366
1367 try:
1368
1369 crdev.Open()
1370 except errors.BlockDeviceError, err:
1371 _Fail("Can't make child '%s' read-write: %s", child, err)
1372 clist.append(crdev)
1373
1374 try:
1375 device = bdev.Create(disk.dev_type, disk.physical_id, clist, disk.size)
1376 except errors.BlockDeviceError, err:
1377 _Fail("Can't create block device: %s", err)
1378
1379 if on_primary or disk.AssembleOnSecondary():
1380 try:
1381 device.Assemble()
1382 except errors.BlockDeviceError, err:
1383 _Fail("Can't assemble device after creation, unusual event: %s", err)
1384 device.SetSyncSpeed(constants.SYNC_SPEED)
1385 if on_primary or disk.OpenOnSecondary():
1386 try:
1387 device.Open(force=True)
1388 except errors.BlockDeviceError, err:
1389 _Fail("Can't make device r/w after creation, unusual event: %s", err)
1390 DevCacheManager.UpdateCache(device.dev_path, owner,
1391 on_primary, disk.iv_name)
1392
1393 device.SetInfo(info)
1394
1395 return device.unique_id
1396
1399 """This function actually wipes the device.
1400
1401 @param path: The path to the device to wipe
1402 @param offset: The offset in MiB in the file
1403 @param size: The size in MiB to write
1404
1405 """
1406 cmd = [constants.DD_CMD, "if=/dev/zero", "seek=%d" % offset,
1407 "bs=%d" % constants.WIPE_BLOCK_SIZE, "oflag=direct", "of=%s" % path,
1408 "count=%d" % size]
1409 result = utils.RunCmd(cmd)
1410
1411 if result.failed:
1412 _Fail("Wipe command '%s' exited with error: %s; output: %s", result.cmd,
1413 result.fail_reason, result.output)
1414
1417 """Wipes a block device.
1418
1419 @type disk: L{objects.Disk}
1420 @param disk: the disk object we want to wipe
1421 @type offset: int
1422 @param offset: The offset in MiB in the file
1423 @type size: int
1424 @param size: The size in MiB to write
1425
1426 """
1427 try:
1428 rdev = _RecursiveFindBD(disk)
1429 except errors.BlockDeviceError:
1430 rdev = None
1431
1432 if not rdev:
1433 _Fail("Cannot execute wipe for device %s: device not found", disk.iv_name)
1434
1435
1436 if offset > rdev.size:
1437 _Fail("Offset is bigger than device size")
1438 if (offset + size) > rdev.size:
1439 _Fail("The provided offset and size to wipe is bigger than device size")
1440
1441 _WipeDevice(rdev.dev_path, offset, size)
1442
1445 """Pause or resume the sync of the block device.
1446
1447 @type disks: list of L{objects.Disk}
1448 @param disks: the disks object we want to pause/resume
1449 @type pause: bool
1450 @param pause: Wheater to pause or resume
1451
1452 """
1453 success = []
1454 for disk in disks:
1455 try:
1456 rdev = _RecursiveFindBD(disk)
1457 except errors.BlockDeviceError:
1458 rdev = None
1459
1460 if not rdev:
1461 success.append((False, ("Cannot change sync for device %s:"
1462 " device not found" % disk.iv_name)))
1463 continue
1464
1465 result = rdev.PauseResumeSync(pause)
1466
1467 if result:
1468 success.append((result, None))
1469 else:
1470 if pause:
1471 msg = "Pause"
1472 else:
1473 msg = "Resume"
1474 success.append((result, "%s for device %s failed" % (msg, disk.iv_name)))
1475
1476 return success
1477
1480 """Remove a block device.
1481
1482 @note: This is intended to be called recursively.
1483
1484 @type disk: L{objects.Disk}
1485 @param disk: the disk object we should remove
1486 @rtype: boolean
1487 @return: the success of the operation
1488
1489 """
1490 msgs = []
1491 try:
1492 rdev = _RecursiveFindBD(disk)
1493 except errors.BlockDeviceError, err:
1494
1495 logging.info("Can't attach to device %s in remove", disk)
1496 rdev = None
1497 if rdev is not None:
1498 r_path = rdev.dev_path
1499 try:
1500 rdev.Remove()
1501 except errors.BlockDeviceError, err:
1502 msgs.append(str(err))
1503 if not msgs:
1504 DevCacheManager.RemoveCache(r_path)
1505
1506 if disk.children:
1507 for child in disk.children:
1508 try:
1509 BlockdevRemove(child)
1510 except RPCFail, err:
1511 msgs.append(str(err))
1512
1513 if msgs:
1514 _Fail("; ".join(msgs))
1515
1518 """Activate a block device for an instance.
1519
1520 This is run on the primary and secondary nodes for an instance.
1521
1522 @note: this function is called recursively.
1523
1524 @type disk: L{objects.Disk}
1525 @param disk: the disk we try to assemble
1526 @type owner: str
1527 @param owner: the name of the instance which owns the disk
1528 @type as_primary: boolean
1529 @param as_primary: if we should make the block device
1530 read/write
1531
1532 @return: the assembled device or None (in case no device
1533 was assembled)
1534 @raise errors.BlockDeviceError: in case there is an error
1535 during the activation of the children or the device
1536 itself
1537
1538 """
1539 children = []
1540 if disk.children:
1541 mcn = disk.ChildrenNeeded()
1542 if mcn == -1:
1543 mcn = 0
1544 else:
1545 mcn = len(disk.children) - mcn
1546 for chld_disk in disk.children:
1547 try:
1548 cdev = _RecursiveAssembleBD(chld_disk, owner, as_primary)
1549 except errors.BlockDeviceError, err:
1550 if children.count(None) >= mcn:
1551 raise
1552 cdev = None
1553 logging.error("Error in child activation (but continuing): %s",
1554 str(err))
1555 children.append(cdev)
1556
1557 if as_primary or disk.AssembleOnSecondary():
1558 r_dev = bdev.Assemble(disk.dev_type, disk.physical_id, children, disk.size)
1559 r_dev.SetSyncSpeed(constants.SYNC_SPEED)
1560 result = r_dev
1561 if as_primary or disk.OpenOnSecondary():
1562 r_dev.Open()
1563 DevCacheManager.UpdateCache(r_dev.dev_path, owner,
1564 as_primary, disk.iv_name)
1565
1566 else:
1567 result = True
1568 return result
1569
1572 """Activate a block device for an instance.
1573
1574 This is a wrapper over _RecursiveAssembleBD.
1575
1576 @rtype: str or boolean
1577 @return: a C{/dev/...} path for primary nodes, and
1578 C{True} for secondary nodes
1579
1580 """
1581 try:
1582 result = _RecursiveAssembleBD(disk, owner, as_primary)
1583 if isinstance(result, bdev.BlockDev):
1584
1585 result = result.dev_path
1586 if as_primary:
1587 _SymlinkBlockDev(owner, result, idx)
1588 except errors.BlockDeviceError, err:
1589 _Fail("Error while assembling disk: %s", err, exc=True)
1590 except OSError, err:
1591 _Fail("Error while symlinking disk: %s", err, exc=True)
1592
1593 return result
1594
1597 """Shut down a block device.
1598
1599 First, if the device is assembled (Attach() is successful), then
1600 the device is shutdown. Then the children of the device are
1601 shutdown.
1602
1603 This function is called recursively. Note that we don't cache the
1604 children or such, as oppossed to assemble, shutdown of different
1605 devices doesn't require that the upper device was active.
1606
1607 @type disk: L{objects.Disk}
1608 @param disk: the description of the disk we should
1609 shutdown
1610 @rtype: None
1611
1612 """
1613 msgs = []
1614 r_dev = _RecursiveFindBD(disk)
1615 if r_dev is not None:
1616 r_path = r_dev.dev_path
1617 try:
1618 r_dev.Shutdown()
1619 DevCacheManager.RemoveCache(r_path)
1620 except errors.BlockDeviceError, err:
1621 msgs.append(str(err))
1622
1623 if disk.children:
1624 for child in disk.children:
1625 try:
1626 BlockdevShutdown(child)
1627 except RPCFail, err:
1628 msgs.append(str(err))
1629
1630 if msgs:
1631 _Fail("; ".join(msgs))
1632
1635 """Extend a mirrored block device.
1636
1637 @type parent_cdev: L{objects.Disk}
1638 @param parent_cdev: the disk to which we should add children
1639 @type new_cdevs: list of L{objects.Disk}
1640 @param new_cdevs: the list of children which we should add
1641 @rtype: None
1642
1643 """
1644 parent_bdev = _RecursiveFindBD(parent_cdev)
1645 if parent_bdev is None:
1646 _Fail("Can't find parent device '%s' in add children", parent_cdev)
1647 new_bdevs = [_RecursiveFindBD(disk) for disk in new_cdevs]
1648 if new_bdevs.count(None) > 0:
1649 _Fail("Can't find new device(s) to add: %s:%s", new_bdevs, new_cdevs)
1650 parent_bdev.AddChildren(new_bdevs)
1651
1654 """Shrink a mirrored block device.
1655
1656 @type parent_cdev: L{objects.Disk}
1657 @param parent_cdev: the disk from which we should remove children
1658 @type new_cdevs: list of L{objects.Disk}
1659 @param new_cdevs: the list of children which we should remove
1660 @rtype: None
1661
1662 """
1663 parent_bdev = _RecursiveFindBD(parent_cdev)
1664 if parent_bdev is None:
1665 _Fail("Can't find parent device '%s' in remove children", parent_cdev)
1666 devs = []
1667 for disk in new_cdevs:
1668 rpath = disk.StaticDevPath()
1669 if rpath is None:
1670 bd = _RecursiveFindBD(disk)
1671 if bd is None:
1672 _Fail("Can't find device %s while removing children", disk)
1673 else:
1674 devs.append(bd.dev_path)
1675 else:
1676 if not utils.IsNormAbsPath(rpath):
1677 _Fail("Strange path returned from StaticDevPath: '%s'", rpath)
1678 devs.append(rpath)
1679 parent_bdev.RemoveChildren(devs)
1680
1683 """Get the mirroring status of a list of devices.
1684
1685 @type disks: list of L{objects.Disk}
1686 @param disks: the list of disks which we should query
1687 @rtype: disk
1688 @return: List of L{objects.BlockDevStatus}, one for each disk
1689 @raise errors.BlockDeviceError: if any of the disks cannot be
1690 found
1691
1692 """
1693 stats = []
1694 for dsk in disks:
1695 rbd = _RecursiveFindBD(dsk)
1696 if rbd is None:
1697 _Fail("Can't find device %s", dsk)
1698
1699 stats.append(rbd.CombinedSyncStatus())
1700
1701 return stats
1702
1705 """Get the mirroring status of a list of devices.
1706
1707 @type disks: list of L{objects.Disk}
1708 @param disks: the list of disks which we should query
1709 @rtype: disk
1710 @return: List of tuples, (bool, status), one for each disk; bool denotes
1711 success/failure, status is L{objects.BlockDevStatus} on success, string
1712 otherwise
1713
1714 """
1715 result = []
1716 for disk in disks:
1717 try:
1718 rbd = _RecursiveFindBD(disk)
1719 if rbd is None:
1720 result.append((False, "Can't find device %s" % disk))
1721 continue
1722
1723 status = rbd.CombinedSyncStatus()
1724 except errors.BlockDeviceError, err:
1725 logging.exception("Error while getting disk status")
1726 result.append((False, str(err)))
1727 else:
1728 result.append((True, status))
1729
1730 assert len(disks) == len(result)
1731
1732 return result
1733
1736 """Check if a device is activated.
1737
1738 If so, return information about the real device.
1739
1740 @type disk: L{objects.Disk}
1741 @param disk: the disk object we need to find
1742
1743 @return: None if the device can't be found,
1744 otherwise the device instance
1745
1746 """
1747 children = []
1748 if disk.children:
1749 for chdisk in disk.children:
1750 children.append(_RecursiveFindBD(chdisk))
1751
1752 return bdev.FindDevice(disk.dev_type, disk.physical_id, children, disk.size)
1753
1756 """Opens the underlying block device of a disk.
1757
1758 @type disk: L{objects.Disk}
1759 @param disk: the disk object we want to open
1760
1761 """
1762 real_disk = _RecursiveFindBD(disk)
1763 if real_disk is None:
1764 _Fail("Block device '%s' is not set up", disk)
1765
1766 real_disk.Open()
1767
1768 return real_disk
1769
1772 """Check if a device is activated.
1773
1774 If it is, return information about the real device.
1775
1776 @type disk: L{objects.Disk}
1777 @param disk: the disk to find
1778 @rtype: None or objects.BlockDevStatus
1779 @return: None if the disk cannot be found, otherwise a the current
1780 information
1781
1782 """
1783 try:
1784 rbd = _RecursiveFindBD(disk)
1785 except errors.BlockDeviceError, err:
1786 _Fail("Failed to find device: %s", err, exc=True)
1787
1788 if rbd is None:
1789 return None
1790
1791 return rbd.GetSyncStatus()
1792
1795 """Computes the size of the given disks.
1796
1797 If a disk is not found, returns None instead.
1798
1799 @type disks: list of L{objects.Disk}
1800 @param disks: the list of disk to compute the size for
1801 @rtype: list
1802 @return: list with elements None if the disk cannot be found,
1803 otherwise the size
1804
1805 """
1806 result = []
1807 for cf in disks:
1808 try:
1809 rbd = _RecursiveFindBD(cf)
1810 except errors.BlockDeviceError:
1811 result.append(None)
1812 continue
1813 if rbd is None:
1814 result.append(None)
1815 else:
1816 result.append(rbd.GetActualSize())
1817 return result
1818
1821 """Export a block device to a remote node.
1822
1823 @type disk: L{objects.Disk}
1824 @param disk: the description of the disk to export
1825 @type dest_node: str
1826 @param dest_node: the destination node to export to
1827 @type dest_path: str
1828 @param dest_path: the destination path on the target node
1829 @type cluster_name: str
1830 @param cluster_name: the cluster name, needed for SSH hostalias
1831 @rtype: None
1832
1833 """
1834 real_disk = _OpenRealBD(disk)
1835
1836
1837 expcmd = utils.BuildShellCmd("set -e; set -o pipefail; "
1838 "dd if=%s bs=1048576 count=%s",
1839 real_disk.dev_path, str(disk.size))
1840
1841
1842
1843
1844
1845
1846
1847 destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536"
1848 " oflag=dsync", dest_path)
1849
1850 remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node,
1851 constants.GANETI_RUNAS,
1852 destcmd)
1853
1854
1855 command = "|".join([expcmd, utils.ShellQuoteArgs(remotecmd)])
1856
1857 result = utils.RunCmd(["bash", "-c", command])
1858
1859 if result.failed:
1860 _Fail("Disk copy command '%s' returned error: %s"
1861 " output: %s", command, result.fail_reason, result.output)
1862
1863
1864 -def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
1865 """Write a file to the filesystem.
1866
1867 This allows the master to overwrite(!) a file. It will only perform
1868 the operation if the file belongs to a list of configuration files.
1869
1870 @type file_name: str
1871 @param file_name: the target file name
1872 @type data: str
1873 @param data: the new contents of the file
1874 @type mode: int
1875 @param mode: the mode to give the file (can be None)
1876 @type uid: string
1877 @param uid: the owner of the file
1878 @type gid: string
1879 @param gid: the group of the file
1880 @type atime: float
1881 @param atime: the atime to set on the file (can be None)
1882 @type mtime: float
1883 @param mtime: the mtime to set on the file (can be None)
1884 @rtype: None
1885
1886 """
1887 if not os.path.isabs(file_name):
1888 _Fail("Filename passed to UploadFile is not absolute: '%s'", file_name)
1889
1890 if file_name not in _ALLOWED_UPLOAD_FILES:
1891 _Fail("Filename passed to UploadFile not in allowed upload targets: '%s'",
1892 file_name)
1893
1894 raw_data = _Decompress(data)
1895
1896 if not (isinstance(uid, basestring) and isinstance(gid, basestring)):
1897 _Fail("Invalid username/groupname type")
1898
1899 getents = runtime.GetEnts()
1900 uid = getents.LookupUser(uid)
1901 gid = getents.LookupGroup(gid)
1902
1903 utils.SafeWriteFile(file_name, None,
1904 data=raw_data, mode=mode, uid=uid, gid=gid,
1905 atime=atime, mtime=mtime)
1906
1907
1908 -def RunOob(oob_program, command, node, timeout):
1909 """Executes oob_program with given command on given node.
1910
1911 @param oob_program: The path to the executable oob_program
1912 @param command: The command to invoke on oob_program
1913 @param node: The node given as an argument to the program
1914 @param timeout: Timeout after which we kill the oob program
1915
1916 @return: stdout
1917 @raise RPCFail: If execution fails for some reason
1918
1919 """
1920 result = utils.RunCmd([oob_program, command, node], timeout=timeout)
1921
1922 if result.failed:
1923 _Fail("'%s' failed with reason '%s'; output: %s", result.cmd,
1924 result.fail_reason, result.output)
1925
1926 return result.stdout
1927
1930 """Update all ssconf files.
1931
1932 Wrapper around the SimpleStore.WriteFiles.
1933
1934 """
1935 ssconf.SimpleStore().WriteFiles(values)
1936
1939 """Format an EnvironmentError exception.
1940
1941 If the L{err} argument has an errno attribute, it will be looked up
1942 and converted into a textual C{E...} description. Otherwise the
1943 string representation of the error will be returned.
1944
1945 @type err: L{EnvironmentError}
1946 @param err: the exception to format
1947
1948 """
1949 if hasattr(err, "errno"):
1950 detail = errno.errorcode[err.errno]
1951 else:
1952 detail = str(err)
1953 return detail
1954
1957 """Compute and return the API version of a given OS.
1958
1959 This function will try to read the API version of the OS residing in
1960 the 'os_dir' directory.
1961
1962 @type os_dir: str
1963 @param os_dir: the directory in which we should look for the OS
1964 @rtype: tuple
1965 @return: tuple (status, data) with status denoting the validity and
1966 data holding either the vaid versions or an error message
1967
1968 """
1969 api_file = utils.PathJoin(os_dir, constants.OS_API_FILE)
1970
1971 try:
1972 st = os.stat(api_file)
1973 except EnvironmentError, err:
1974 return False, ("Required file '%s' not found under path %s: %s" %
1975 (constants.OS_API_FILE, os_dir, _ErrnoOrStr(err)))
1976
1977 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1978 return False, ("File '%s' in %s is not a regular file" %
1979 (constants.OS_API_FILE, os_dir))
1980
1981 try:
1982 api_versions = utils.ReadFile(api_file).splitlines()
1983 except EnvironmentError, err:
1984 return False, ("Error while reading the API version file at %s: %s" %
1985 (api_file, _ErrnoOrStr(err)))
1986
1987 try:
1988 api_versions = [int(version.strip()) for version in api_versions]
1989 except (TypeError, ValueError), err:
1990 return False, ("API version(s) can't be converted to integer: %s" %
1991 str(err))
1992
1993 return True, api_versions
1994
1997 """Compute the validity for all OSes.
1998
1999 @type top_dirs: list
2000 @param top_dirs: the list of directories in which to
2001 search (if not given defaults to
2002 L{constants.OS_SEARCH_PATH})
2003 @rtype: list of L{objects.OS}
2004 @return: a list of tuples (name, path, status, diagnose, variants,
2005 parameters, api_version) for all (potential) OSes under all
2006 search paths, where:
2007 - name is the (potential) OS name
2008 - path is the full path to the OS
2009 - status True/False is the validity of the OS
2010 - diagnose is the error message for an invalid OS, otherwise empty
2011 - variants is a list of supported OS variants, if any
2012 - parameters is a list of (name, help) parameters, if any
2013 - api_version is a list of support OS API versions
2014
2015 """
2016 if top_dirs is None:
2017 top_dirs = constants.OS_SEARCH_PATH
2018
2019 result = []
2020 for dir_name in top_dirs:
2021 if os.path.isdir(dir_name):
2022 try:
2023 f_names = utils.ListVisibleFiles(dir_name)
2024 except EnvironmentError, err:
2025 logging.exception("Can't list the OS directory %s: %s", dir_name, err)
2026 break
2027 for name in f_names:
2028 os_path = utils.PathJoin(dir_name, name)
2029 status, os_inst = _TryOSFromDisk(name, base_dir=dir_name)
2030 if status:
2031 diagnose = ""
2032 variants = os_inst.supported_variants
2033 parameters = os_inst.supported_parameters
2034 api_versions = os_inst.api_versions
2035 else:
2036 diagnose = os_inst
2037 variants = parameters = api_versions = []
2038 result.append((name, os_path, status, diagnose, variants,
2039 parameters, api_versions))
2040
2041 return result
2042
2045 """Create an OS instance from disk.
2046
2047 This function will return an OS instance if the given name is a
2048 valid OS name.
2049
2050 @type base_dir: string
2051 @keyword base_dir: Base directory containing OS installations.
2052 Defaults to a search in all the OS_SEARCH_PATH dirs.
2053 @rtype: tuple
2054 @return: success and either the OS instance if we find a valid one,
2055 or error message
2056
2057 """
2058 if base_dir is None:
2059 os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir)
2060 else:
2061 os_dir = utils.FindFile(name, [base_dir], os.path.isdir)
2062
2063 if os_dir is None:
2064 return False, "Directory for OS %s not found in search path" % name
2065
2066 status, api_versions = _OSOndiskAPIVersion(os_dir)
2067 if not status:
2068
2069 return status, api_versions
2070
2071 if not constants.OS_API_VERSIONS.intersection(api_versions):
2072 return False, ("API version mismatch for path '%s': found %s, want %s." %
2073 (os_dir, api_versions, constants.OS_API_VERSIONS))
2074
2075
2076
2077
2078 os_files = dict.fromkeys(constants.OS_SCRIPTS, True)
2079
2080 if max(api_versions) >= constants.OS_API_V15:
2081 os_files[constants.OS_VARIANTS_FILE] = False
2082
2083 if max(api_versions) >= constants.OS_API_V20:
2084 os_files[constants.OS_PARAMETERS_FILE] = True
2085 else:
2086 del os_files[constants.OS_SCRIPT_VERIFY]
2087
2088 for (filename, required) in os_files.items():
2089 os_files[filename] = utils.PathJoin(os_dir, filename)
2090
2091 try:
2092 st = os.stat(os_files[filename])
2093 except EnvironmentError, err:
2094 if err.errno == errno.ENOENT and not required:
2095 del os_files[filename]
2096 continue
2097 return False, ("File '%s' under path '%s' is missing (%s)" %
2098 (filename, os_dir, _ErrnoOrStr(err)))
2099
2100 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
2101 return False, ("File '%s' under path '%s' is not a regular file" %
2102 (filename, os_dir))
2103
2104 if filename in constants.OS_SCRIPTS:
2105 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
2106 return False, ("File '%s' under path '%s' is not executable" %
2107 (filename, os_dir))
2108
2109 variants = []
2110 if constants.OS_VARIANTS_FILE in os_files:
2111 variants_file = os_files[constants.OS_VARIANTS_FILE]
2112 try:
2113 variants = utils.ReadFile(variants_file).splitlines()
2114 except EnvironmentError, err:
2115
2116 if err.errno != errno.ENOENT:
2117 return False, ("Error while reading the OS variants file at %s: %s" %
2118 (variants_file, _ErrnoOrStr(err)))
2119
2120 parameters = []
2121 if constants.OS_PARAMETERS_FILE in os_files:
2122 parameters_file = os_files[constants.OS_PARAMETERS_FILE]
2123 try:
2124 parameters = utils.ReadFile(parameters_file).splitlines()
2125 except EnvironmentError, err:
2126 return False, ("Error while reading the OS parameters file at %s: %s" %
2127 (parameters_file, _ErrnoOrStr(err)))
2128 parameters = [v.split(None, 1) for v in parameters]
2129
2130 os_obj = objects.OS(name=name, path=os_dir,
2131 create_script=os_files[constants.OS_SCRIPT_CREATE],
2132 export_script=os_files[constants.OS_SCRIPT_EXPORT],
2133 import_script=os_files[constants.OS_SCRIPT_IMPORT],
2134 rename_script=os_files[constants.OS_SCRIPT_RENAME],
2135 verify_script=os_files.get(constants.OS_SCRIPT_VERIFY,
2136 None),
2137 supported_variants=variants,
2138 supported_parameters=parameters,
2139 api_versions=api_versions)
2140 return True, os_obj
2141
2144 """Create an OS instance from disk.
2145
2146 This function will return an OS instance if the given name is a
2147 valid OS name. Otherwise, it will raise an appropriate
2148 L{RPCFail} exception, detailing why this is not a valid OS.
2149
2150 This is just a wrapper over L{_TryOSFromDisk}, which doesn't raise
2151 an exception but returns true/false status data.
2152
2153 @type base_dir: string
2154 @keyword base_dir: Base directory containing OS installations.
2155 Defaults to a search in all the OS_SEARCH_PATH dirs.
2156 @rtype: L{objects.OS}
2157 @return: the OS instance if we find a valid one
2158 @raise RPCFail: if we don't find a valid OS
2159
2160 """
2161 name_only = objects.OS.GetName(name)
2162 status, payload = _TryOSFromDisk(name_only, base_dir)
2163
2164 if not status:
2165 _Fail(payload)
2166
2167 return payload
2168
2169
2170 -def OSCoreEnv(os_name, inst_os, os_params, debug=0):
2171 """Calculate the basic environment for an os script.
2172
2173 @type os_name: str
2174 @param os_name: full operating system name (including variant)
2175 @type inst_os: L{objects.OS}
2176 @param inst_os: operating system for which the environment is being built
2177 @type os_params: dict
2178 @param os_params: the OS parameters
2179 @type debug: integer
2180 @param debug: debug level (0 or 1, for OS Api 10)
2181 @rtype: dict
2182 @return: dict of environment variables
2183 @raise errors.BlockDeviceError: if the block device
2184 cannot be found
2185
2186 """
2187 result = {}
2188 api_version = \
2189 max(constants.OS_API_VERSIONS.intersection(inst_os.api_versions))
2190 result["OS_API_VERSION"] = "%d" % api_version
2191 result["OS_NAME"] = inst_os.name
2192 result["DEBUG_LEVEL"] = "%d" % debug
2193
2194
2195 if api_version >= constants.OS_API_V15 and inst_os.supported_variants:
2196 variant = objects.OS.GetVariant(os_name)
2197 if not variant:
2198 variant = inst_os.supported_variants[0]
2199 else:
2200 variant = ""
2201 result["OS_VARIANT"] = variant
2202
2203
2204 for pname, pvalue in os_params.items():
2205 result["OSP_%s" % pname.upper()] = pvalue
2206
2207
2208
2209
2210 result["PATH"] = constants.HOOKS_PATH
2211
2212 return result
2213
2216 """Calculate the environment for an os script.
2217
2218 @type instance: L{objects.Instance}
2219 @param instance: target instance for the os script run
2220 @type inst_os: L{objects.OS}
2221 @param inst_os: operating system for which the environment is being built
2222 @type debug: integer
2223 @param debug: debug level (0 or 1, for OS Api 10)
2224 @rtype: dict
2225 @return: dict of environment variables
2226 @raise errors.BlockDeviceError: if the block device
2227 cannot be found
2228
2229 """
2230 result = OSCoreEnv(instance.os, inst_os, instance.osparams, debug=debug)
2231
2232 for attr in ["name", "os", "uuid", "ctime", "mtime", "primary_node"]:
2233 result["INSTANCE_%s" % attr.upper()] = str(getattr(instance, attr))
2234
2235 result["HYPERVISOR"] = instance.hypervisor
2236 result["DISK_COUNT"] = "%d" % len(instance.disks)
2237 result["NIC_COUNT"] = "%d" % len(instance.nics)
2238 result["INSTANCE_SECONDARY_NODES"] = \
2239 ("%s" % " ".join(instance.secondary_nodes))
2240
2241
2242 for idx, disk in enumerate(instance.disks):
2243 real_disk = _OpenRealBD(disk)
2244 result["DISK_%d_PATH" % idx] = real_disk.dev_path
2245 result["DISK_%d_ACCESS" % idx] = disk.mode
2246 if constants.HV_DISK_TYPE in instance.hvparams:
2247 result["DISK_%d_FRONTEND_TYPE" % idx] = \
2248 instance.hvparams[constants.HV_DISK_TYPE]
2249 if disk.dev_type in constants.LDS_BLOCK:
2250 result["DISK_%d_BACKEND_TYPE" % idx] = "block"
2251 elif disk.dev_type == constants.LD_FILE:
2252 result["DISK_%d_BACKEND_TYPE" % idx] = \
2253 "file:%s" % disk.physical_id[0]
2254
2255
2256 for idx, nic in enumerate(instance.nics):
2257 result["NIC_%d_MAC" % idx] = nic.mac
2258 if nic.ip:
2259 result["NIC_%d_IP" % idx] = nic.ip
2260 result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE]
2261 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
2262 result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK]
2263 if nic.nicparams[constants.NIC_LINK]:
2264 result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK]
2265 if constants.HV_NIC_TYPE in instance.hvparams:
2266 result["NIC_%d_FRONTEND_TYPE" % idx] = \
2267 instance.hvparams[constants.HV_NIC_TYPE]
2268
2269
2270 for source, kind in [(instance.beparams, "BE"), (instance.hvparams, "HV")]:
2271 for key, value in source.items():
2272 result["INSTANCE_%s_%s" % (kind, key)] = str(value)
2273
2274 return result
2275
2278 """Grow a stack of block devices.
2279
2280 This function is called recursively, with the childrens being the
2281 first ones to resize.
2282
2283 @type disk: L{objects.Disk}
2284 @param disk: the disk to be grown
2285 @type amount: integer
2286 @param amount: the amount (in mebibytes) to grow with
2287 @type dryrun: boolean
2288 @param dryrun: whether to execute the operation in simulation mode
2289 only, without actually increasing the size
2290 @rtype: (status, result)
2291 @return: a tuple with the status of the operation (True/False), and
2292 the errors message if status is False
2293
2294 """
2295 r_dev = _RecursiveFindBD(disk)
2296 if r_dev is None:
2297 _Fail("Cannot find block device %s", disk)
2298
2299 try:
2300 r_dev.Grow(amount, dryrun)
2301 except errors.BlockDeviceError, err:
2302 _Fail("Failed to grow block device: %s", err, exc=True)
2303
2306 """Create a snapshot copy of a block device.
2307
2308 This function is called recursively, and the snapshot is actually created
2309 just for the leaf lvm backend device.
2310
2311 @type disk: L{objects.Disk}
2312 @param disk: the disk to be snapshotted
2313 @rtype: string
2314 @return: snapshot disk ID as (vg, lv)
2315
2316 """
2317 if disk.dev_type == constants.LD_DRBD8:
2318 if not disk.children:
2319 _Fail("DRBD device '%s' without backing storage cannot be snapshotted",
2320 disk.unique_id)
2321 return BlockdevSnapshot(disk.children[0])
2322 elif disk.dev_type == constants.LD_LV:
2323 r_dev = _RecursiveFindBD(disk)
2324 if r_dev is not None:
2325
2326
2327 return r_dev.Snapshot(disk.size)
2328 else:
2329 _Fail("Cannot find block device %s", disk)
2330 else:
2331 _Fail("Cannot snapshot non-lvm block device '%s' of type '%s'",
2332 disk.unique_id, disk.dev_type)
2333
2336 """Write out the export configuration information.
2337
2338 @type instance: L{objects.Instance}
2339 @param instance: the instance which we export, used for
2340 saving configuration
2341 @type snap_disks: list of L{objects.Disk}
2342 @param snap_disks: list of snapshot block devices, which
2343 will be used to get the actual name of the dump file
2344
2345 @rtype: None
2346
2347 """
2348 destdir = utils.PathJoin(constants.EXPORT_DIR, instance.name + ".new")
2349 finaldestdir = utils.PathJoin(constants.EXPORT_DIR, instance.name)
2350
2351 config = objects.SerializableConfigParser()
2352
2353 config.add_section(constants.INISECT_EXP)
2354 config.set(constants.INISECT_EXP, "version", "0")
2355 config.set(constants.INISECT_EXP, "timestamp", "%d" % int(time.time()))
2356 config.set(constants.INISECT_EXP, "source", instance.primary_node)
2357 config.set(constants.INISECT_EXP, "os", instance.os)
2358 config.set(constants.INISECT_EXP, "compression", "none")
2359
2360 config.add_section(constants.INISECT_INS)
2361 config.set(constants.INISECT_INS, "name", instance.name)
2362 config.set(constants.INISECT_INS, "memory", "%d" %
2363 instance.beparams[constants.BE_MEMORY])
2364 config.set(constants.INISECT_INS, "vcpus", "%d" %
2365 instance.beparams[constants.BE_VCPUS])
2366 config.set(constants.INISECT_INS, "disk_template", instance.disk_template)
2367 config.set(constants.INISECT_INS, "hypervisor", instance.hypervisor)
2368 config.set(constants.INISECT_INS, "tags", " ".join(instance.GetTags()))
2369
2370 nic_total = 0
2371 for nic_count, nic in enumerate(instance.nics):
2372 nic_total += 1
2373 config.set(constants.INISECT_INS, "nic%d_mac" %
2374 nic_count, "%s" % nic.mac)
2375 config.set(constants.INISECT_INS, "nic%d_ip" % nic_count, "%s" % nic.ip)
2376 for param in constants.NICS_PARAMETER_TYPES:
2377 config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param),
2378 "%s" % nic.nicparams.get(param, None))
2379
2380 config.set(constants.INISECT_INS, "nic_count", "%d" % nic_total)
2381
2382 disk_total = 0
2383 for disk_count, disk in enumerate(snap_disks):
2384 if disk:
2385 disk_total += 1
2386 config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count,
2387 ("%s" % disk.iv_name))
2388 config.set(constants.INISECT_INS, "disk%d_dump" % disk_count,
2389 ("%s" % disk.physical_id[1]))
2390 config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
2391 ("%d" % disk.size))
2392
2393 config.set(constants.INISECT_INS, "disk_count", "%d" % disk_total)
2394
2395
2396
2397 config.add_section(constants.INISECT_HYP)
2398 for name, value in instance.hvparams.items():
2399 if name not in constants.HVC_GLOBALS:
2400 config.set(constants.INISECT_HYP, name, str(value))
2401
2402 config.add_section(constants.INISECT_BEP)
2403 for name, value in instance.beparams.items():
2404 config.set(constants.INISECT_BEP, name, str(value))
2405
2406 config.add_section(constants.INISECT_OSP)
2407 for name, value in instance.osparams.items():
2408 config.set(constants.INISECT_OSP, name, str(value))
2409
2410 utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE),
2411 data=config.Dumps())
2412 shutil.rmtree(finaldestdir, ignore_errors=True)
2413 shutil.move(destdir, finaldestdir)
2414
2437
2450
2453 """Remove an existing export from the node.
2454
2455 @type export: str
2456 @param export: the name of the export to remove
2457 @rtype: None
2458
2459 """
2460 target = utils.PathJoin(constants.EXPORT_DIR, export)
2461
2462 try:
2463 shutil.rmtree(target)
2464 except EnvironmentError, err:
2465 _Fail("Error while removing the export: %s", err, exc=True)
2466
2469 """Rename a list of block devices.
2470
2471 @type devlist: list of tuples
2472 @param devlist: list of tuples of the form (disk,
2473 new_logical_id, new_physical_id); disk is an
2474 L{objects.Disk} object describing the current disk,
2475 and new logical_id/physical_id is the name we
2476 rename it to
2477 @rtype: boolean
2478 @return: True if all renames succeeded, False otherwise
2479
2480 """
2481 msgs = []
2482 result = True
2483 for disk, unique_id in devlist:
2484 dev = _RecursiveFindBD(disk)
2485 if dev is None:
2486 msgs.append("Can't find device %s in rename" % str(disk))
2487 result = False
2488 continue
2489 try:
2490 old_rpath = dev.dev_path
2491 dev.Rename(unique_id)
2492 new_rpath = dev.dev_path
2493 if old_rpath != new_rpath:
2494 DevCacheManager.RemoveCache(old_rpath)
2495
2496
2497
2498
2499
2500 except errors.BlockDeviceError, err:
2501 msgs.append("Can't rename device '%s' to '%s': %s" %
2502 (dev, unique_id, err))
2503 logging.exception("Can't rename device '%s' to '%s'", dev, unique_id)
2504 result = False
2505 if not result:
2506 _Fail("; ".join(msgs))
2507
2534
2537 """Create file storage directory.
2538
2539 @type file_storage_dir: str
2540 @param file_storage_dir: directory to create
2541
2542 @rtype: tuple
2543 @return: tuple with first element a boolean indicating wheter dir
2544 creation was successful or not
2545
2546 """
2547 file_storage_dir = _TransformFileStorageDir(file_storage_dir)
2548 if os.path.exists(file_storage_dir):
2549 if not os.path.isdir(file_storage_dir):
2550 _Fail("Specified storage dir '%s' is not a directory",
2551 file_storage_dir)
2552 else:
2553 try:
2554 os.makedirs(file_storage_dir, 0750)
2555 except OSError, err:
2556 _Fail("Cannot create file storage directory '%s': %s",
2557 file_storage_dir, err, exc=True)
2558
2561 """Remove file storage directory.
2562
2563 Remove it only if it's empty. If not log an error and return.
2564
2565 @type file_storage_dir: str
2566 @param file_storage_dir: the directory we should cleanup
2567 @rtype: tuple (success,)
2568 @return: tuple of one element, C{success}, denoting
2569 whether the operation was successful
2570
2571 """
2572 file_storage_dir = _TransformFileStorageDir(file_storage_dir)
2573 if os.path.exists(file_storage_dir):
2574 if not os.path.isdir(file_storage_dir):
2575 _Fail("Specified Storage directory '%s' is not a directory",
2576 file_storage_dir)
2577
2578 try:
2579 os.rmdir(file_storage_dir)
2580 except OSError, err:
2581 _Fail("Cannot remove file storage directory '%s': %s",
2582 file_storage_dir, err)
2583
2586 """Rename the file storage directory.
2587
2588 @type old_file_storage_dir: str
2589 @param old_file_storage_dir: the current path
2590 @type new_file_storage_dir: str
2591 @param new_file_storage_dir: the name we should rename to
2592 @rtype: tuple (success,)
2593 @return: tuple of one element, C{success}, denoting
2594 whether the operation was successful
2595
2596 """
2597 old_file_storage_dir = _TransformFileStorageDir(old_file_storage_dir)
2598 new_file_storage_dir = _TransformFileStorageDir(new_file_storage_dir)
2599 if not os.path.exists(new_file_storage_dir):
2600 if os.path.isdir(old_file_storage_dir):
2601 try:
2602 os.rename(old_file_storage_dir, new_file_storage_dir)
2603 except OSError, err:
2604 _Fail("Cannot rename '%s' to '%s': %s",
2605 old_file_storage_dir, new_file_storage_dir, err)
2606 else:
2607 _Fail("Specified storage dir '%s' is not a directory",
2608 old_file_storage_dir)
2609 else:
2610 if os.path.exists(old_file_storage_dir):
2611 _Fail("Cannot rename '%s' to '%s': both locations exist",
2612 old_file_storage_dir, new_file_storage_dir)
2613
2616 """Checks whether the given filename is in the queue directory.
2617
2618 @type file_name: str
2619 @param file_name: the file name we should check
2620 @rtype: None
2621 @raises RPCFail: if the file is not valid
2622
2623 """
2624 queue_dir = os.path.normpath(constants.QUEUE_DIR)
2625 result = (os.path.commonprefix([queue_dir, file_name]) == queue_dir)
2626
2627 if not result:
2628 _Fail("Passed job queue file '%s' does not belong to"
2629 " the queue directory '%s'", file_name, queue_dir)
2630
2633 """Updates a file in the queue directory.
2634
2635 This is just a wrapper over L{utils.io.WriteFile}, with proper
2636 checking.
2637
2638 @type file_name: str
2639 @param file_name: the job file name
2640 @type content: str
2641 @param content: the new job contents
2642 @rtype: boolean
2643 @return: the success of the operation
2644
2645 """
2646 _EnsureJobQueueFile(file_name)
2647 getents = runtime.GetEnts()
2648
2649
2650 utils.WriteFile(file_name, data=_Decompress(content), uid=getents.masterd_uid,
2651 gid=getents.masterd_gid)
2652
2655 """Renames a job queue file.
2656
2657 This is just a wrapper over os.rename with proper checking.
2658
2659 @type old: str
2660 @param old: the old (actual) file name
2661 @type new: str
2662 @param new: the desired file name
2663 @rtype: tuple
2664 @return: the success of the operation and payload
2665
2666 """
2667 _EnsureJobQueueFile(old)
2668 _EnsureJobQueueFile(new)
2669
2670 getents = runtime.GetEnts()
2671
2672 utils.RenameFile(old, new, mkdir=True, mkdir_mode=0700,
2673 dir_uid=getents.masterd_uid, dir_gid=getents.masterd_gid)
2674
2677 """Closes the given block devices.
2678
2679 This means they will be switched to secondary mode (in case of
2680 DRBD).
2681
2682 @param instance_name: if the argument is not empty, the symlinks
2683 of this instance will be removed
2684 @type disks: list of L{objects.Disk}
2685 @param disks: the list of disks to be closed
2686 @rtype: tuple (success, message)
2687 @return: a tuple of success and message, where success
2688 indicates the succes of the operation, and message
2689 which will contain the error details in case we
2690 failed
2691
2692 """
2693 bdevs = []
2694 for cf in disks:
2695 rd = _RecursiveFindBD(cf)
2696 if rd is None:
2697 _Fail("Can't find device %s", cf)
2698 bdevs.append(rd)
2699
2700 msg = []
2701 for rd in bdevs:
2702 try:
2703 rd.Close()
2704 except errors.BlockDeviceError, err:
2705 msg.append(str(err))
2706 if msg:
2707 _Fail("Can't make devices secondary: %s", ",".join(msg))
2708 else:
2709 if instance_name:
2710 _RemoveBlockDevLinks(instance_name, disks)
2711
2714 """Validates the given hypervisor parameters.
2715
2716 @type hvname: string
2717 @param hvname: the hypervisor name
2718 @type hvparams: dict
2719 @param hvparams: the hypervisor parameters to be validated
2720 @rtype: None
2721
2722 """
2723 try:
2724 hv_type = hypervisor.GetHypervisor(hvname)
2725 hv_type.ValidateParameters(hvparams)
2726 except errors.HypervisorError, err:
2727 _Fail(str(err), log=False)
2728
2731 """Check whether a list of parameters is supported by the OS.
2732
2733 @type os_obj: L{objects.OS}
2734 @param os_obj: OS object to check
2735 @type parameters: list
2736 @param parameters: the list of parameters to check
2737
2738 """
2739 supported = [v[0] for v in os_obj.supported_parameters]
2740 delta = frozenset(parameters).difference(supported)
2741 if delta:
2742 _Fail("The following parameters are not supported"
2743 " by the OS %s: %s" % (os_obj.name, utils.CommaJoin(delta)))
2744
2745
2746 -def ValidateOS(required, osname, checks, osparams):
2747 """Validate the given OS' parameters.
2748
2749 @type required: boolean
2750 @param required: whether absence of the OS should translate into
2751 failure or not
2752 @type osname: string
2753 @param osname: the OS to be validated
2754 @type checks: list
2755 @param checks: list of the checks to run (currently only 'parameters')
2756 @type osparams: dict
2757 @param osparams: dictionary with OS parameters
2758 @rtype: boolean
2759 @return: True if the validation passed, or False if the OS was not
2760 found and L{required} was false
2761
2762 """
2763 if not constants.OS_VALIDATE_CALLS.issuperset(checks):
2764 _Fail("Unknown checks required for OS %s: %s", osname,
2765 set(checks).difference(constants.OS_VALIDATE_CALLS))
2766
2767 name_only = objects.OS.GetName(osname)
2768 status, tbv = _TryOSFromDisk(name_only, None)
2769
2770 if not status:
2771 if required:
2772 _Fail(tbv)
2773 else:
2774 return False
2775
2776 if max(tbv.api_versions) < constants.OS_API_V20:
2777 return True
2778
2779 if constants.OS_VALIDATE_PARAMETERS in checks:
2780 _CheckOSPList(tbv, osparams.keys())
2781
2782 validate_env = OSCoreEnv(osname, tbv, osparams)
2783 result = utils.RunCmd([tbv.verify_script] + checks, env=validate_env,
2784 cwd=tbv.path, reset_env=True)
2785 if result.failed:
2786 logging.error("os validate command '%s' returned error: %s output: %s",
2787 result.cmd, result.fail_reason, result.output)
2788 _Fail("OS validation script failed (%s), output: %s",
2789 result.fail_reason, result.output, log=False)
2790
2791 return True
2792
2815
2824
2827 """Creates a new X509 certificate for SSL/TLS.
2828
2829 @type validity: int
2830 @param validity: Validity in seconds
2831 @rtype: tuple; (string, string)
2832 @return: Certificate name and public part
2833
2834 """
2835 (key_pem, cert_pem) = \
2836 utils.GenerateSelfSignedX509Cert(netutils.Hostname.GetSysName(),
2837 min(validity, _MAX_SSL_CERT_VALIDITY))
2838
2839 cert_dir = tempfile.mkdtemp(dir=cryptodir,
2840 prefix="x509-%s-" % utils.TimestampForFilename())
2841 try:
2842 name = os.path.basename(cert_dir)
2843 assert len(name) > 5
2844
2845 (_, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
2846
2847 utils.WriteFile(key_file, mode=0400, data=key_pem)
2848 utils.WriteFile(cert_file, mode=0400, data=cert_pem)
2849
2850
2851 return (name, cert_pem)
2852 except Exception:
2853 shutil.rmtree(cert_dir, ignore_errors=True)
2854 raise
2855
2858 """Removes a X509 certificate.
2859
2860 @type name: string
2861 @param name: Certificate name
2862
2863 """
2864 (cert_dir, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
2865
2866 utils.RemoveFile(key_file)
2867 utils.RemoveFile(cert_file)
2868
2869 try:
2870 os.rmdir(cert_dir)
2871 except EnvironmentError, err:
2872 _Fail("Cannot remove certificate directory '%s': %s",
2873 cert_dir, err)
2874
2877 """Returns the command for the requested input/output.
2878
2879 @type instance: L{objects.Instance}
2880 @param instance: The instance object
2881 @param mode: Import/export mode
2882 @param ieio: Input/output type
2883 @param ieargs: Input/output arguments
2884
2885 """
2886 assert mode in (constants.IEM_IMPORT, constants.IEM_EXPORT)
2887
2888 env = None
2889 prefix = None
2890 suffix = None
2891 exp_size = None
2892
2893 if ieio == constants.IEIO_FILE:
2894 (filename, ) = ieargs
2895
2896 if not utils.IsNormAbsPath(filename):
2897 _Fail("Path '%s' is not normalized or absolute", filename)
2898
2899 directory = os.path.normpath(os.path.dirname(filename))
2900
2901 if (os.path.commonprefix([constants.EXPORT_DIR, directory]) !=
2902 constants.EXPORT_DIR):
2903 _Fail("File '%s' is not under exports directory '%s'",
2904 filename, constants.EXPORT_DIR)
2905
2906
2907 utils.Makedirs(directory, mode=0750)
2908
2909 quoted_filename = utils.ShellQuote(filename)
2910
2911 if mode == constants.IEM_IMPORT:
2912 suffix = "> %s" % quoted_filename
2913 elif mode == constants.IEM_EXPORT:
2914 suffix = "< %s" % quoted_filename
2915
2916
2917 try:
2918 st = os.stat(filename)
2919 except EnvironmentError, err:
2920 logging.error("Can't stat(2) %s: %s", filename, err)
2921 else:
2922 exp_size = utils.BytesToMebibyte(st.st_size)
2923
2924 elif ieio == constants.IEIO_RAW_DISK:
2925 (disk, ) = ieargs
2926
2927 real_disk = _OpenRealBD(disk)
2928
2929 if mode == constants.IEM_IMPORT:
2930
2931
2932
2933
2934
2935
2936 suffix = utils.BuildShellCmd(("| dd of=%s conv=nocreat,notrunc"
2937 " bs=%s oflag=dsync"),
2938 real_disk.dev_path,
2939 str(64 * 1024))
2940
2941 elif mode == constants.IEM_EXPORT:
2942
2943 prefix = utils.BuildShellCmd("dd if=%s bs=%s count=%s |",
2944 real_disk.dev_path,
2945 str(1024 * 1024),
2946 str(disk.size))
2947 exp_size = disk.size
2948
2949 elif ieio == constants.IEIO_SCRIPT:
2950 (disk, disk_index, ) = ieargs
2951
2952 assert isinstance(disk_index, (int, long))
2953
2954 real_disk = _OpenRealBD(disk)
2955
2956 inst_os = OSFromDisk(instance.os)
2957 env = OSEnvironment(instance, inst_os)
2958
2959 if mode == constants.IEM_IMPORT:
2960 env["IMPORT_DEVICE"] = env["DISK_%d_PATH" % disk_index]
2961 env["IMPORT_INDEX"] = str(disk_index)
2962 script = inst_os.import_script
2963
2964 elif mode == constants.IEM_EXPORT:
2965 env["EXPORT_DEVICE"] = real_disk.dev_path
2966 env["EXPORT_INDEX"] = str(disk_index)
2967 script = inst_os.export_script
2968
2969
2970 script_cmd = utils.BuildShellCmd("( cd %s && %s; )", inst_os.path, script)
2971
2972 if mode == constants.IEM_IMPORT:
2973 suffix = "| %s" % script_cmd
2974
2975 elif mode == constants.IEM_EXPORT:
2976 prefix = "%s |" % script_cmd
2977
2978
2979 exp_size = constants.IE_CUSTOM_SIZE
2980
2981 else:
2982 _Fail("Invalid %s I/O mode %r", mode, ieio)
2983
2984 return (env, prefix, suffix, exp_size)
2985
2994
2998 """Starts an import or export daemon.
2999
3000 @param mode: Import/output mode
3001 @type opts: L{objects.ImportExportOptions}
3002 @param opts: Daemon options
3003 @type host: string
3004 @param host: Remote host for export (None for import)
3005 @type port: int
3006 @param port: Remote port for export (None for import)
3007 @type instance: L{objects.Instance}
3008 @param instance: Instance object
3009 @type component: string
3010 @param component: which part of the instance is transferred now,
3011 e.g. 'disk/0'
3012 @param ieio: Input/output type
3013 @param ieioargs: Input/output arguments
3014
3015 """
3016 if mode == constants.IEM_IMPORT:
3017 prefix = "import"
3018
3019 if not (host is None and port is None):
3020 _Fail("Can not specify host or port on import")
3021
3022 elif mode == constants.IEM_EXPORT:
3023 prefix = "export"
3024
3025 if host is None or port is None:
3026 _Fail("Host and port must be specified for an export")
3027
3028 else:
3029 _Fail("Invalid mode %r", mode)
3030
3031 if (opts.key_name is None) ^ (opts.ca_pem is None):
3032 _Fail("Cluster certificate can only be used for both key and CA")
3033
3034 (cmd_env, cmd_prefix, cmd_suffix, exp_size) = \
3035 _GetImportExportIoCommand(instance, mode, ieio, ieioargs)
3036
3037 if opts.key_name is None:
3038
3039 key_path = constants.NODED_CERT_FILE
3040 cert_path = constants.NODED_CERT_FILE
3041 assert opts.ca_pem is None
3042 else:
3043 (_, key_path, cert_path) = _GetX509Filenames(constants.CRYPTO_KEYS_DIR,
3044 opts.key_name)
3045 assert opts.ca_pem is not None
3046
3047 for i in [key_path, cert_path]:
3048 if not os.path.exists(i):
3049 _Fail("File '%s' does not exist" % i)
3050
3051 status_dir = _CreateImportExportStatusDir("%s-%s" % (prefix, component))
3052 try:
3053 status_file = utils.PathJoin(status_dir, _IES_STATUS_FILE)
3054 pid_file = utils.PathJoin(status_dir, _IES_PID_FILE)
3055 ca_file = utils.PathJoin(status_dir, _IES_CA_FILE)
3056
3057 if opts.ca_pem is None:
3058
3059 ca = utils.ReadFile(constants.NODED_CERT_FILE)
3060 else:
3061 ca = opts.ca_pem
3062
3063
3064 utils.WriteFile(ca_file, data=ca, mode=0400)
3065
3066 cmd = [
3067 constants.IMPORT_EXPORT_DAEMON,
3068 status_file, mode,
3069 "--key=%s" % key_path,
3070 "--cert=%s" % cert_path,
3071 "--ca=%s" % ca_file,
3072 ]
3073
3074 if host:
3075 cmd.append("--host=%s" % host)
3076
3077 if port:
3078 cmd.append("--port=%s" % port)
3079
3080 if opts.ipv6:
3081 cmd.append("--ipv6")
3082 else:
3083 cmd.append("--ipv4")
3084
3085 if opts.compress:
3086 cmd.append("--compress=%s" % opts.compress)
3087
3088 if opts.magic:
3089 cmd.append("--magic=%s" % opts.magic)
3090
3091 if exp_size is not None:
3092 cmd.append("--expected-size=%s" % exp_size)
3093
3094 if cmd_prefix:
3095 cmd.append("--cmd-prefix=%s" % cmd_prefix)
3096
3097 if cmd_suffix:
3098 cmd.append("--cmd-suffix=%s" % cmd_suffix)
3099
3100 if mode == constants.IEM_EXPORT:
3101
3102 cmd.append("--connect-retries=%s" % constants.RIE_CONNECT_RETRIES)
3103 cmd.append("--connect-timeout=%s" % constants.RIE_CONNECT_ATTEMPT_TIMEOUT)
3104 elif opts.connect_timeout is not None:
3105 assert mode == constants.IEM_IMPORT
3106
3107 cmd.append("--connect-timeout=%s" % opts.connect_timeout)
3108
3109 logfile = _InstanceLogName(prefix, instance.os, instance.name, component)
3110
3111
3112
3113 utils.StartDaemon(cmd, env=cmd_env, pidfile=pid_file,
3114 output=logfile)
3115
3116
3117 return os.path.basename(status_dir)
3118
3119 except Exception:
3120 shutil.rmtree(status_dir, ignore_errors=True)
3121 raise
3122
3125 """Returns import/export daemon status.
3126
3127 @type names: sequence
3128 @param names: List of names
3129 @rtype: List of dicts
3130 @return: Returns a list of the state of each named import/export or None if a
3131 status couldn't be read
3132
3133 """
3134 result = []
3135
3136 for name in names:
3137 status_file = utils.PathJoin(constants.IMPORT_EXPORT_DIR, name,
3138 _IES_STATUS_FILE)
3139
3140 try:
3141 data = utils.ReadFile(status_file)
3142 except EnvironmentError, err:
3143 if err.errno != errno.ENOENT:
3144 raise
3145 data = None
3146
3147 if not data:
3148 result.append(None)
3149 continue
3150
3151 result.append(serializer.LoadJson(data))
3152
3153 return result
3154
3169
3172 """Cleanup after an import or export.
3173
3174 If the import/export daemon is still running it's killed. Afterwards the
3175 whole status directory is removed.
3176
3177 """
3178 logging.info("Finalizing import/export %s", name)
3179
3180 status_dir = utils.PathJoin(constants.IMPORT_EXPORT_DIR, name)
3181
3182 pid = utils.ReadLockedPidFile(utils.PathJoin(status_dir, _IES_PID_FILE))
3183
3184 if pid:
3185 logging.info("Import/export %s is still running with PID %s",
3186 name, pid)
3187 utils.KillProcess(pid, waitpid=False)
3188
3189 shutil.rmtree(status_dir, ignore_errors=True)
3190
3193 """Sets the physical ID on disks and returns the block devices.
3194
3195 """
3196
3197 my_name = netutils.Hostname.GetSysName()
3198 for cf in disks:
3199 cf.SetPhysicalID(my_name, nodes_ip)
3200
3201 bdevs = []
3202
3203 for cf in disks:
3204 rd = _RecursiveFindBD(cf)
3205 if rd is None:
3206 _Fail("Can't find device %s", cf)
3207 bdevs.append(rd)
3208 return bdevs
3209
3212 """Disconnects the network on a list of drbd devices.
3213
3214 """
3215 bdevs = _FindDisks(nodes_ip, disks)
3216
3217
3218 for rd in bdevs:
3219 try:
3220 rd.DisconnectNet()
3221 except errors.BlockDeviceError, err:
3222 _Fail("Can't change network configuration to standalone mode: %s",
3223 err, exc=True)
3224
3225
3226 -def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
3227 """Attaches the network on a list of drbd devices.
3228
3229 """
3230 bdevs = _FindDisks(nodes_ip, disks)
3231
3232 if multimaster:
3233 for idx, rd in enumerate(bdevs):
3234 try:
3235 _SymlinkBlockDev(instance_name, rd.dev_path, idx)
3236 except EnvironmentError, err:
3237 _Fail("Can't create symlink: %s", err)
3238
3239
3240 for rd in bdevs:
3241 try:
3242 rd.AttachNet(multimaster)
3243 except errors.BlockDeviceError, err:
3244 _Fail("Can't change network configuration: %s", err)
3245
3246
3247
3248
3249
3250
3251
3252 def _Attach():
3253 all_connected = True
3254
3255 for rd in bdevs:
3256 stats = rd.GetProcStatus()
3257
3258 all_connected = (all_connected and
3259 (stats.is_connected or stats.is_in_resync))
3260
3261 if stats.is_standalone:
3262
3263
3264
3265 try:
3266 rd.AttachNet(multimaster)
3267 except errors.BlockDeviceError, err:
3268 _Fail("Can't change network configuration: %s", err)
3269
3270 if not all_connected:
3271 raise utils.RetryAgain()
3272
3273 try:
3274
3275 utils.Retry(_Attach, (0.1, 1.5, 5.0), 2 * 60)
3276 except utils.RetryTimeout:
3277 _Fail("Timeout in disk reconnecting")
3278
3279 if multimaster:
3280
3281 for rd in bdevs:
3282 try:
3283 rd.Open()
3284 except errors.BlockDeviceError, err:
3285 _Fail("Can't change to primary mode: %s", err)
3286
3289 """Wait until DRBDs have synchronized.
3290
3291 """
3292 def _helper(rd):
3293 stats = rd.GetProcStatus()
3294 if not (stats.is_connected or stats.is_in_resync):
3295 raise utils.RetryAgain()
3296 return stats
3297
3298 bdevs = _FindDisks(nodes_ip, disks)
3299
3300 min_resync = 100
3301 alldone = True
3302 for rd in bdevs:
3303 try:
3304
3305 stats = utils.Retry(_helper, 1, 15, args=[rd])
3306 except utils.RetryTimeout:
3307 stats = rd.GetProcStatus()
3308
3309 if not (stats.is_connected or stats.is_in_resync):
3310 _Fail("DRBD device %s is not in sync: stats=%s", rd, stats)
3311 alldone = alldone and (not stats.is_in_resync)
3312 if stats.sync_percent is not None:
3313 min_resync = min(min_resync, stats.sync_percent)
3314
3315 return (alldone, min_resync)
3316
3326
3329 """Hard-powercycle the node.
3330
3331 Because we need to return first, and schedule the powercycle in the
3332 background, we won't be able to report failures nicely.
3333
3334 """
3335 hyper = hypervisor.GetHypervisor(hypervisor_type)
3336 try:
3337 pid = os.fork()
3338 except OSError:
3339
3340 pid = 0
3341 if pid > 0:
3342 return "Reboot scheduled in 5 seconds"
3343
3344 try:
3345 utils.Mlockall()
3346 except Exception:
3347 pass
3348 time.sleep(5)
3349 hyper.PowercycleNode()
3350
3353 """Hook runner.
3354
3355 This class is instantiated on the node side (ganeti-noded) and not
3356 on the master side.
3357
3358 """
3359 - def __init__(self, hooks_base_dir=None):
3360 """Constructor for hooks runner.
3361
3362 @type hooks_base_dir: str or None
3363 @param hooks_base_dir: if not None, this overrides the
3364 L{constants.HOOKS_BASE_DIR} (useful for unittests)
3365
3366 """
3367 if hooks_base_dir is None:
3368 hooks_base_dir = constants.HOOKS_BASE_DIR
3369
3370
3371 self._BASE_DIR = hooks_base_dir
3372
3373 - def RunHooks(self, hpath, phase, env):
3374 """Run the scripts in the hooks directory.
3375
3376 @type hpath: str
3377 @param hpath: the path to the hooks directory which
3378 holds the scripts
3379 @type phase: str
3380 @param phase: either L{constants.HOOKS_PHASE_PRE} or
3381 L{constants.HOOKS_PHASE_POST}
3382 @type env: dict
3383 @param env: dictionary with the environment for the hook
3384 @rtype: list
3385 @return: list of 3-element tuples:
3386 - script path
3387 - script result, either L{constants.HKR_SUCCESS} or
3388 L{constants.HKR_FAIL}
3389 - output of the script
3390
3391 @raise errors.ProgrammerError: for invalid input
3392 parameters
3393
3394 """
3395 if phase == constants.HOOKS_PHASE_PRE:
3396 suffix = "pre"
3397 elif phase == constants.HOOKS_PHASE_POST:
3398 suffix = "post"
3399 else:
3400 _Fail("Unknown hooks phase '%s'", phase)
3401
3402 subdir = "%s-%s.d" % (hpath, suffix)
3403 dir_name = utils.PathJoin(self._BASE_DIR, subdir)
3404
3405 results = []
3406
3407 if not os.path.isdir(dir_name):
3408
3409
3410 return results
3411
3412 runparts_results = utils.RunParts(dir_name, env=env, reset_env=True)
3413
3414 for (relname, relstatus, runresult) in runparts_results:
3415 if relstatus == constants.RUNPARTS_SKIP:
3416 rrval = constants.HKR_SKIP
3417 output = ""
3418 elif relstatus == constants.RUNPARTS_ERR:
3419 rrval = constants.HKR_FAIL
3420 output = "Hook script execution error: %s" % runresult
3421 elif relstatus == constants.RUNPARTS_RUN:
3422 if runresult.failed:
3423 rrval = constants.HKR_FAIL
3424 else:
3425 rrval = constants.HKR_SUCCESS
3426 output = utils.SafeEncode(runresult.output.strip())
3427 results.append(("%s/%s" % (subdir, relname), rrval, output))
3428
3429 return results
3430
3433 """IAllocator runner.
3434
3435 This class is instantiated on the node side (ganeti-noded) and not on
3436 the master side.
3437
3438 """
3439 @staticmethod
3440 - def Run(name, idata):
3441 """Run an iallocator script.
3442
3443 @type name: str
3444 @param name: the iallocator script name
3445 @type idata: str
3446 @param idata: the allocator input data
3447
3448 @rtype: tuple
3449 @return: two element tuple of:
3450 - status
3451 - either error message or stdout of allocator (for success)
3452
3453 """
3454 alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
3455 os.path.isfile)
3456 if alloc_script is None:
3457 _Fail("iallocator module '%s' not found in the search path", name)
3458
3459 fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
3460 try:
3461 os.write(fd, idata)
3462 os.close(fd)
3463 result = utils.RunCmd([alloc_script, fin_name])
3464 if result.failed:
3465 _Fail("iallocator module '%s' failed: %s, output '%s'",
3466 name, result.fail_reason, result.output)
3467 finally:
3468 os.unlink(fin_name)
3469
3470 return result.stdout
3471
3474 """Simple class for managing a cache of block device information.
3475
3476 """
3477 _DEV_PREFIX = "/dev/"
3478 _ROOT_DIR = constants.BDEV_CACHE_DIR
3479
3480 @classmethod
3482 """Converts a /dev/name path to the cache file name.
3483
3484 This replaces slashes with underscores and strips the /dev
3485 prefix. It then returns the full path to the cache file.
3486
3487 @type dev_path: str
3488 @param dev_path: the C{/dev/} path name
3489 @rtype: str
3490 @return: the converted path name
3491
3492 """
3493 if dev_path.startswith(cls._DEV_PREFIX):
3494 dev_path = dev_path[len(cls._DEV_PREFIX):]
3495 dev_path = dev_path.replace("/", "_")
3496 fpath = utils.PathJoin(cls._ROOT_DIR, "bdev_%s" % dev_path)
3497 return fpath
3498
3499 @classmethod
3500 - def UpdateCache(cls, dev_path, owner, on_primary, iv_name):
3501 """Updates the cache information for a given device.
3502
3503 @type dev_path: str
3504 @param dev_path: the pathname of the device
3505 @type owner: str
3506 @param owner: the owner (instance name) of the device
3507 @type on_primary: bool
3508 @param on_primary: whether this is the primary
3509 node nor not
3510 @type iv_name: str
3511 @param iv_name: the instance-visible name of the
3512 device, as in objects.Disk.iv_name
3513
3514 @rtype: None
3515
3516 """
3517 if dev_path is None:
3518 logging.error("DevCacheManager.UpdateCache got a None dev_path")
3519 return
3520 fpath = cls._ConvertPath(dev_path)
3521 if on_primary:
3522 state = "primary"
3523 else:
3524 state = "secondary"
3525 if iv_name is None:
3526 iv_name = "not_visible"
3527 fdata = "%s %s %s\n" % (str(owner), state, iv_name)
3528 try:
3529 utils.WriteFile(fpath, data=fdata)
3530 except EnvironmentError, err:
3531 logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
3532
3533 @classmethod
3535 """Remove data for a dev_path.
3536
3537 This is just a wrapper over L{utils.io.RemoveFile} with a converted
3538 path name and logging.
3539
3540 @type dev_path: str
3541 @param dev_path: the pathname of the device
3542
3543 @rtype: None
3544
3545 """
3546 if dev_path is None:
3547 logging.error("DevCacheManager.RemoveCache got a None dev_path")
3548 return
3549 fpath = cls._ConvertPath(dev_path)
3550 try:
3551 utils.RemoveFile(fpath)
3552 except EnvironmentError, err:
3553 logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
3554