1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Functions used by the node daemon
32
33 @var _ALLOWED_UPLOAD_FILES: denotes which files are accepted in
34 the L{UploadFile} function
35 @var _ALLOWED_CLEAN_DIRS: denotes which directories are accepted
36 in the L{_CleanDirectory} function
37
38 """
39
40
41
42
43
44
45
46
47
48
49 import os
50 import os.path
51 import shutil
52 import time
53 import stat
54 import errno
55 import re
56 import random
57 import logging
58 import tempfile
59 import zlib
60 import base64
61 import signal
62
63 from ganeti import errors
64 from ganeti import utils
65 from ganeti import ssh
66 from ganeti import hypervisor
67 from ganeti.hypervisor import hv_base
68 from ganeti import constants
69 from ganeti.storage import bdev
70 from ganeti.storage import drbd
71 from ganeti.storage import filestorage
72 from ganeti import objects
73 from ganeti import ssconf
74 from ganeti import serializer
75 from ganeti import netutils
76 from ganeti import runtime
77 from ganeti import compat
78 from ganeti import pathutils
79 from ganeti import vcluster
80 from ganeti import ht
81 from ganeti.storage.base import BlockDev
82 from ganeti.storage.drbd import DRBD8
83 from ganeti import hooksmaster
84
85
86 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
87 _ALLOWED_CLEAN_DIRS = compat.UniqueFrozenset([
88 pathutils.DATA_DIR,
89 pathutils.JOB_QUEUE_ARCHIVE_DIR,
90 pathutils.QUEUE_DIR,
91 pathutils.CRYPTO_KEYS_DIR,
92 ])
93 _MAX_SSL_CERT_VALIDITY = 7 * 24 * 60 * 60
94 _X509_KEY_FILE = "key"
95 _X509_CERT_FILE = "cert"
96 _IES_STATUS_FILE = "status"
97 _IES_PID_FILE = "pid"
98 _IES_CA_FILE = "ca"
99
100
101 _LVSLINE_REGEX = re.compile(r"^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
102
103
104 _MASTER_START = "start"
105 _MASTER_STOP = "stop"
106
107
108 _RCMD_MAX_MODE = (stat.S_IRWXU |
109 stat.S_IRGRP | stat.S_IXGRP |
110 stat.S_IROTH | stat.S_IXOTH)
111
112
113 _RCMD_INVALID_DELAY = 10
114
115
116
117
118 _RCMD_LOCK_TIMEOUT = _RCMD_INVALID_DELAY * 0.8
122 """Class denoting RPC failure.
123
124 Its argument is the error message.
125
126 """
127
130 """Path of the file containing the reason of the instance status change.
131
132 @type instance_name: string
133 @param instance_name: The name of the instance
134 @rtype: string
135 @return: The path of the file
136
137 """
138 return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name)
139
142 """Serialize a reason trail related to an instance change of state to file.
143
144 The exact location of the file depends on the name of the instance and on
145 the configuration of the Ganeti cluster defined at deploy time.
146
147 @type instance_name: string
148 @param instance_name: The name of the instance
149 @rtype: None
150
151 """
152 json = serializer.DumpJson(trail)
153 filename = _GetInstReasonFilename(instance_name)
154 utils.WriteFile(filename, data=json)
155
156
157 -def _Fail(msg, *args, **kwargs):
158 """Log an error and the raise an RPCFail exception.
159
160 This exception is then handled specially in the ganeti daemon and
161 turned into a 'failed' return type. As such, this function is a
162 useful shortcut for logging the error and returning it to the master
163 daemon.
164
165 @type msg: string
166 @param msg: the text of the exception
167 @raise RPCFail
168
169 """
170 if args:
171 msg = msg % args
172 if "log" not in kwargs or kwargs["log"]:
173 if "exc" in kwargs and kwargs["exc"]:
174 logging.exception(msg)
175 else:
176 logging.error(msg)
177 raise RPCFail(msg)
178
181 """Simple wrapper to return a SimpleStore.
182
183 @rtype: L{ssconf.SimpleStore}
184 @return: a SimpleStore instance
185
186 """
187 return ssconf.SimpleStore()
188
191 """Simple wrapper to return an SshRunner.
192
193 @type cluster_name: str
194 @param cluster_name: the cluster name, which is needed
195 by the SshRunner constructor
196 @rtype: L{ssh.SshRunner}
197 @return: an SshRunner instance
198
199 """
200 return ssh.SshRunner(cluster_name)
201
204 """Unpacks data compressed by the RPC client.
205
206 @type data: list or tuple
207 @param data: Data sent by RPC client
208 @rtype: str
209 @return: Decompressed data
210
211 """
212 assert isinstance(data, (list, tuple))
213 assert len(data) == 2
214 (encoding, content) = data
215 if encoding == constants.RPC_ENCODING_NONE:
216 return content
217 elif encoding == constants.RPC_ENCODING_ZLIB_BASE64:
218 return zlib.decompress(base64.b64decode(content))
219 else:
220 raise AssertionError("Unknown data encoding")
221
224 """Removes all regular files in a directory.
225
226 @type path: str
227 @param path: the directory to clean
228 @type exclude: list
229 @param exclude: list of files to be excluded, defaults
230 to the empty list
231
232 """
233 if path not in _ALLOWED_CLEAN_DIRS:
234 _Fail("Path passed to _CleanDirectory not in allowed clean targets: '%s'",
235 path)
236
237 if not os.path.isdir(path):
238 return
239 if exclude is None:
240 exclude = []
241 else:
242
243 exclude = [os.path.normpath(i) for i in exclude]
244
245 for rel_name in utils.ListVisibleFiles(path):
246 full_name = utils.PathJoin(path, rel_name)
247 if full_name in exclude:
248 continue
249 if os.path.isfile(full_name) and not os.path.islink(full_name):
250 utils.RemoveFile(full_name)
251
254 """Build the list of allowed upload files.
255
256 This is abstracted so that it's built only once at module import time.
257
258 """
259 allowed_files = set([
260 pathutils.CLUSTER_CONF_FILE,
261 pathutils.ETC_HOSTS,
262 pathutils.SSH_KNOWN_HOSTS_FILE,
263 pathutils.VNC_PASSWORD_FILE,
264 pathutils.RAPI_CERT_FILE,
265 pathutils.SPICE_CERT_FILE,
266 pathutils.SPICE_CACERT_FILE,
267 pathutils.RAPI_USERS_FILE,
268 pathutils.CONFD_HMAC_KEY,
269 pathutils.CLUSTER_DOMAIN_SECRET_FILE,
270 ])
271
272 for hv_name in constants.HYPER_TYPES:
273 hv_class = hypervisor.GetHypervisorClass(hv_name)
274 allowed_files.update(hv_class.GetAncillaryFiles()[0])
275
276 assert pathutils.FILE_STORAGE_PATHS_FILE not in allowed_files, \
277 "Allowed file storage paths should never be uploaded via RPC"
278
279 return frozenset(allowed_files)
280
281
282 _ALLOWED_UPLOAD_FILES = _BuildUploadFileList()
294
297 """Returns the master node name.
298
299 @rtype: string
300 @return: name of the master node
301 @raise RPCFail: in case of errors
302
303 """
304 try:
305 return _GetConfig().GetMasterNode()
306 except errors.ConfigurationError, err:
307 _Fail("Cluster configuration incomplete: %s", err, exc=True)
308
311 """Decorator that runs hooks before and after the decorated function.
312
313 @type hook_opcode: string
314 @param hook_opcode: opcode of the hook
315 @type hooks_path: string
316 @param hooks_path: path of the hooks
317 @type env_builder_fn: function
318 @param env_builder_fn: function that returns a dictionary containing the
319 environment variables for the hooks. Will get all the parameters of the
320 decorated function.
321 @raise RPCFail: in case of pre-hook failure
322
323 """
324 def decorator(fn):
325 def wrapper(*args, **kwargs):
326 _, myself = ssconf.GetMasterAndMyself()
327 nodes = ([myself], [myself])
328
329 env_fn = compat.partial(env_builder_fn, *args, **kwargs)
330
331 cfg = _GetConfig()
332 hr = HooksRunner()
333 hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes,
334 hr.RunLocalHooks, None, env_fn, None,
335 logging.warning, cfg.GetClusterName(),
336 cfg.GetMasterNode())
337 hm.RunPhase(constants.HOOKS_PHASE_PRE)
338 result = fn(*args, **kwargs)
339 hm.RunPhase(constants.HOOKS_PHASE_POST)
340
341 return result
342 return wrapper
343 return decorator
344
347 """Builds environment variables for master IP hooks.
348
349 @type master_params: L{objects.MasterNetworkParameters}
350 @param master_params: network parameters of the master
351 @type use_external_mip_script: boolean
352 @param use_external_mip_script: whether to use an external master IP
353 address setup script (unused, but necessary per the implementation of the
354 _RunLocalHooks decorator)
355
356 """
357
358 ver = netutils.IPAddress.GetVersionFromAddressFamily(master_params.ip_family)
359 env = {
360 "MASTER_NETDEV": master_params.netdev,
361 "MASTER_IP": master_params.ip,
362 "MASTER_NETMASK": str(master_params.netmask),
363 "CLUSTER_IP_VERSION": str(ver),
364 }
365
366 return env
367
370 """Execute the master IP address setup script.
371
372 @type master_params: L{objects.MasterNetworkParameters}
373 @param master_params: network parameters of the master
374 @type action: string
375 @param action: action to pass to the script. Must be one of
376 L{backend._MASTER_START} or L{backend._MASTER_STOP}
377 @type use_external_mip_script: boolean
378 @param use_external_mip_script: whether to use an external master IP
379 address setup script
380 @raise backend.RPCFail: if there are errors during the execution of the
381 script
382
383 """
384 env = _BuildMasterIpEnv(master_params)
385
386 if use_external_mip_script:
387 setup_script = pathutils.EXTERNAL_MASTER_SETUP_SCRIPT
388 else:
389 setup_script = pathutils.DEFAULT_MASTER_SETUP_SCRIPT
390
391 result = utils.RunCmd([setup_script, action], env=env, reset_env=True)
392
393 if result.failed:
394 _Fail("Failed to %s the master IP. Script return value: %s, output: '%s'" %
395 (action, result.exit_code, result.output), log=True)
396
397
398 @RunLocalHooks(constants.FAKE_OP_MASTER_TURNUP, "master-ip-turnup",
399 _BuildMasterIpEnv)
401 """Activate the IP address of the master daemon.
402
403 @type master_params: L{objects.MasterNetworkParameters}
404 @param master_params: network parameters of the master
405 @type use_external_mip_script: boolean
406 @param use_external_mip_script: whether to use an external master IP
407 address setup script
408 @raise RPCFail: in case of errors during the IP startup
409
410 """
411 _RunMasterSetupScript(master_params, _MASTER_START,
412 use_external_mip_script)
413
416 """Activate local node as master node.
417
418 The function will start the master daemons (ganeti-masterd and ganeti-rapi).
419
420 @type no_voting: boolean
421 @param no_voting: whether to start ganeti-masterd without a node vote
422 but still non-interactively
423 @rtype: None
424
425 """
426
427 if no_voting:
428 masterd_args = "--no-voting --yes-do-it"
429 else:
430 masterd_args = ""
431
432 env = {
433 "EXTRA_MASTERD_ARGS": masterd_args,
434 }
435
436 result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-master"], env=env)
437 if result.failed:
438 msg = "Can't start Ganeti master: %s" % result.output
439 logging.error(msg)
440 _Fail(msg)
441
442
443 @RunLocalHooks(constants.FAKE_OP_MASTER_TURNDOWN, "master-ip-turndown",
444 _BuildMasterIpEnv)
446 """Deactivate the master IP on this node.
447
448 @type master_params: L{objects.MasterNetworkParameters}
449 @param master_params: network parameters of the master
450 @type use_external_mip_script: boolean
451 @param use_external_mip_script: whether to use an external master IP
452 address setup script
453 @raise RPCFail: in case of errors during the IP turndown
454
455 """
456 _RunMasterSetupScript(master_params, _MASTER_STOP,
457 use_external_mip_script)
458
461 """Stop the master daemons on this node.
462
463 Stop the master daemons (ganeti-masterd and ganeti-rapi) on this node.
464
465 @rtype: None
466
467 """
468
469
470
471 result = utils.RunCmd([pathutils.DAEMON_UTIL, "stop-master"])
472 if result.failed:
473 logging.error("Could not stop Ganeti master, command %s had exitcode %s"
474 " and error %s",
475 result.cmd, result.exit_code, result.output)
476
479 """Change the netmask of the master IP.
480
481 @param old_netmask: the old value of the netmask
482 @param netmask: the new value of the netmask
483 @param master_ip: the master IP
484 @param master_netdev: the master network device
485
486 """
487 if old_netmask == netmask:
488 return
489
490 if not netutils.IPAddress.Own(master_ip):
491 _Fail("The master IP address is not up, not attempting to change its"
492 " netmask")
493
494 result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add",
495 "%s/%s" % (master_ip, netmask),
496 "dev", master_netdev, "label",
497 "%s:0" % master_netdev])
498 if result.failed:
499 _Fail("Could not set the new netmask on the master IP address")
500
501 result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del",
502 "%s/%s" % (master_ip, old_netmask),
503 "dev", master_netdev, "label",
504 "%s:0" % master_netdev])
505 if result.failed:
506 _Fail("Could not bring down the master IP address with the old netmask")
507
510 """Modify a host entry in /etc/hosts.
511
512 @param mode: The mode to operate. Either add or remove entry
513 @param host: The host to operate on
514 @param ip: The ip associated with the entry
515
516 """
517 if mode == constants.ETC_HOSTS_ADD:
518 if not ip:
519 RPCFail("Mode 'add' needs 'ip' parameter, but parameter not"
520 " present")
521 utils.AddHostToEtcHosts(host, ip)
522 elif mode == constants.ETC_HOSTS_REMOVE:
523 if ip:
524 RPCFail("Mode 'remove' does not allow 'ip' parameter, but"
525 " parameter is present")
526 utils.RemoveHostFromEtcHosts(host)
527 else:
528 RPCFail("Mode not supported")
529
574
577 """Performs sanity checks for storage parameters.
578
579 @type params: list
580 @param params: list of storage parameters
581 @type num_params: int
582 @param num_params: expected number of parameters
583
584 """
585 if params is None:
586 raise errors.ProgrammerError("No storage parameters for storage"
587 " reporting is provided.")
588 if not isinstance(params, list):
589 raise errors.ProgrammerError("The storage parameters are not of type"
590 " list: '%s'" % params)
591 if not len(params) == num_params:
592 raise errors.ProgrammerError("Did not receive the expected number of"
593 "storage parameters: expected %s,"
594 " received '%s'" % (num_params, len(params)))
595
598 """Performs sanity check for the 'exclusive storage' flag.
599
600 @see: C{_CheckStorageParams}
601
602 """
603 _CheckStorageParams(params, 1)
604 excl_stor = params[0]
605 if not isinstance(params[0], bool):
606 raise errors.ProgrammerError("Exclusive storage parameter is not"
607 " boolean: '%s'." % excl_stor)
608 return excl_stor
609
612 """Wrapper around C{_GetVgInfo} which checks the storage parameters.
613
614 @type name: string
615 @param name: name of the volume group
616 @type params: list
617 @param params: list of storage parameters, which in this case should be
618 containing only one for exclusive storage
619
620 """
621 excl_stor = _CheckLvmStorageParams(params)
622 return _GetVgInfo(name, excl_stor)
623
627 """Retrieves information about a LVM volume group.
628
629 """
630
631 vginfo = info_fn([name], excl_stor)
632 if vginfo:
633 vg_free = int(round(vginfo[0][0], 0))
634 vg_size = int(round(vginfo[0][1], 0))
635 else:
636 vg_free = None
637 vg_size = None
638
639 return {
640 "type": constants.ST_LVM_VG,
641 "name": name,
642 "storage_free": vg_free,
643 "storage_size": vg_size,
644 }
645
655
659 """Retrieves information about spindles in an LVM volume group.
660
661 @type name: string
662 @param name: VG name
663 @type excl_stor: bool
664 @param excl_stor: exclusive storage
665 @rtype: dict
666 @return: dictionary whose keys are "name", "vg_free", "vg_size" for VG name,
667 free spindles, total spindles respectively
668
669 """
670 if excl_stor:
671 (vg_free, vg_size) = info_fn(name)
672 else:
673 vg_free = 0
674 vg_size = 0
675 return {
676 "type": constants.ST_LVM_PV,
677 "name": name,
678 "storage_free": vg_free,
679 "storage_size": vg_size,
680 }
681
684 """Retrieves node information from a hypervisor.
685
686 The information returned depends on the hypervisor. Common items:
687
688 - vg_size is the size of the configured volume group in MiB
689 - vg_free is the free size of the volume group in MiB
690 - memory_dom0 is the memory allocated for domain0 in MiB
691 - memory_free is the currently available (free) ram in MiB
692 - memory_total is the total number of ram in MiB
693 - hv_version: the hypervisor version, if available
694
695 @type hvparams: dict of string
696 @param hvparams: the hypervisor's hvparams
697
698 """
699 return get_hv_fn(name).GetNodeInfo(hvparams=hvparams)
700
703 """Retrieves node information for all hypervisors.
704
705 See C{_GetHvInfo} for information on the output.
706
707 @type hv_specs: list of pairs (string, dict of strings)
708 @param hv_specs: list of pairs of a hypervisor's name and its hvparams
709
710 """
711 if hv_specs is None:
712 return None
713
714 result = []
715 for hvname, hvparams in hv_specs:
716 result.append(_GetHvInfo(hvname, hvparams, get_hv_fn))
717 return result
718
721 """Calls C{fn} for all names in C{names} and returns a dictionary.
722
723 @rtype: None or dict
724
725 """
726 if names is None:
727 return None
728 else:
729 return map(fn, names)
730
733 """Gives back a hash with different information about the node.
734
735 @type storage_units: list of tuples (string, string, list)
736 @param storage_units: List of tuples (storage unit, identifier, parameters) to
737 ask for disk space information. In case of lvm-vg, the identifier is
738 the VG name. The parameters can contain additional, storage-type-specific
739 parameters, for example exclusive storage for lvm storage.
740 @type hv_specs: list of pairs (string, dict of strings)
741 @param hv_specs: list of pairs of a hypervisor's name and its hvparams
742 @rtype: tuple; (string, None/dict, None/dict)
743 @return: Tuple containing boot ID, volume group information and hypervisor
744 information
745
746 """
747 bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
748 storage_info = _GetNamedNodeInfo(
749 storage_units,
750 (lambda (storage_type, storage_key, storage_params):
751 _ApplyStorageInfoFunction(storage_type, storage_key, storage_params)))
752 hv_info = _GetHvInfoAll(hv_specs)
753 return (bootid, storage_info, hv_info)
754
757 """Wrapper around filestorage.GetSpaceInfo.
758
759 The purpose of this wrapper is to call filestorage.GetFileStorageSpaceInfo
760 and ignore the *args parameter to not leak it into the filestorage
761 module's code.
762
763 @see: C{filestorage.GetFileStorageSpaceInfo} for description of the
764 parameters.
765
766 """
767 _CheckStorageParams(params, 0)
768 return filestorage.GetFileStorageSpaceInfo(path)
769
770
771
772 _STORAGE_TYPE_INFO_FN = {
773 constants.ST_BLOCK: None,
774 constants.ST_DISKLESS: None,
775 constants.ST_EXT: None,
776 constants.ST_FILE: _GetFileStorageSpaceInfo,
777 constants.ST_LVM_PV: _GetLvmPvSpaceInfo,
778 constants.ST_LVM_VG: _GetLvmVgSpaceInfo,
779 constants.ST_SHARED_FILE: None,
780 constants.ST_RADOS: None,
781 }
785 """Looks up and applies the correct function to calculate free and total
786 storage for the given storage type.
787
788 @type storage_type: string
789 @param storage_type: the storage type for which the storage shall be reported.
790 @type storage_key: string
791 @param storage_key: identifier of a storage unit, e.g. the volume group name
792 of an LVM storage unit
793 @type args: any
794 @param args: various parameters that can be used for storage reporting. These
795 parameters and their semantics vary from storage type to storage type and
796 are just propagated in this function.
797 @return: the results of the application of the storage space function (see
798 _STORAGE_TYPE_INFO_FN) if storage space reporting is implemented for that
799 storage type
800 @raises NotImplementedError: for storage types who don't support space
801 reporting yet
802 """
803 fn = _STORAGE_TYPE_INFO_FN[storage_type]
804 if fn is not None:
805 return fn(storage_key, *args)
806 else:
807 raise NotImplementedError
808
811 """Check that PVs are not shared among LVs
812
813 @type pvi_list: list of L{objects.LvmPvInfo} objects
814 @param pvi_list: information about the PVs
815
816 @rtype: list of tuples (string, list of strings)
817 @return: offending volumes, as tuples: (pv_name, [lv1_name, lv2_name...])
818
819 """
820 res = []
821 for pvi in pvi_list:
822 if len(pvi.lv_list) > 1:
823 res.append((pvi.name, pvi.lv_list))
824 return res
825
829 """Verifies the hypervisor. Appends the results to the 'results' list.
830
831 @type what: C{dict}
832 @param what: a dictionary of things to check
833 @type vm_capable: boolean
834 @param vm_capable: whether or not this node is vm capable
835 @type result: dict
836 @param result: dictionary of verification results; results of the
837 verifications in this function will be added here
838 @type all_hvparams: dict of dict of string
839 @param all_hvparams: dictionary mapping hypervisor names to hvparams
840 @type get_hv_fn: function
841 @param get_hv_fn: function to retrieve the hypervisor, to improve testability
842
843 """
844 if not vm_capable:
845 return
846
847 if constants.NV_HYPERVISOR in what:
848 result[constants.NV_HYPERVISOR] = {}
849 for hv_name in what[constants.NV_HYPERVISOR]:
850 hvparams = all_hvparams[hv_name]
851 try:
852 val = get_hv_fn(hv_name).Verify(hvparams=hvparams)
853 except errors.HypervisorError, err:
854 val = "Error while checking hypervisor: %s" % str(err)
855 result[constants.NV_HYPERVISOR][hv_name] = val
856
860 """Verifies the hvparams. Appends the results to the 'results' list.
861
862 @type what: C{dict}
863 @param what: a dictionary of things to check
864 @type vm_capable: boolean
865 @param vm_capable: whether or not this node is vm capable
866 @type result: dict
867 @param result: dictionary of verification results; results of the
868 verifications in this function will be added here
869 @type get_hv_fn: function
870 @param get_hv_fn: function to retrieve the hypervisor, to improve testability
871
872 """
873 if not vm_capable:
874 return
875
876 if constants.NV_HVPARAMS in what:
877 result[constants.NV_HVPARAMS] = []
878 for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
879 try:
880 logging.info("Validating hv %s, %s", hv_name, hvparms)
881 get_hv_fn(hv_name).ValidateParameters(hvparms)
882 except errors.HypervisorError, err:
883 result[constants.NV_HVPARAMS].append((source, hv_name, str(err)))
884
887 """Verifies the instance list.
888
889 @type what: C{dict}
890 @param what: a dictionary of things to check
891 @type vm_capable: boolean
892 @param vm_capable: whether or not this node is vm capable
893 @type result: dict
894 @param result: dictionary of verification results; results of the
895 verifications in this function will be added here
896 @type all_hvparams: dict of dict of string
897 @param all_hvparams: dictionary mapping hypervisor names to hvparams
898
899 """
900 if constants.NV_INSTANCELIST in what and vm_capable:
901
902 try:
903 val = GetInstanceList(what[constants.NV_INSTANCELIST],
904 all_hvparams=all_hvparams)
905 except RPCFail, err:
906 val = str(err)
907 result[constants.NV_INSTANCELIST] = val
908
911 """Verifies the node info.
912
913 @type what: C{dict}
914 @param what: a dictionary of things to check
915 @type vm_capable: boolean
916 @param vm_capable: whether or not this node is vm capable
917 @type result: dict
918 @param result: dictionary of verification results; results of the
919 verifications in this function will be added here
920 @type all_hvparams: dict of dict of string
921 @param all_hvparams: dictionary mapping hypervisor names to hvparams
922
923 """
924 if constants.NV_HVINFO in what and vm_capable:
925 hvname = what[constants.NV_HVINFO]
926 hyper = hypervisor.GetHypervisor(hvname)
927 hvparams = all_hvparams[hvname]
928 result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams)
929
932 """Verify the existance and validity of the client SSL certificate.
933
934 """
935 create_cert_cmd = "gnt-cluster renew-crypto --new-node-certificates"
936 if not os.path.exists(cert_file):
937 return (constants.CV_ERROR,
938 "The client certificate does not exist. Run '%s' to create"
939 " client certificates for all nodes." % create_cert_cmd)
940
941 (errcode, msg) = utils.VerifyCertificate(cert_file)
942 if errcode is not None:
943 return (errcode, msg)
944 else:
945
946 return (None, utils.GetCertificateDigest(cert_filename=cert_file))
947
948
949 -def VerifyNode(what, cluster_name, all_hvparams, node_groups, groups_cfg):
950 """Verify the status of the local node.
951
952 Based on the input L{what} parameter, various checks are done on the
953 local node.
954
955 If the I{filelist} key is present, this list of
956 files is checksummed and the file/checksum pairs are returned.
957
958 If the I{nodelist} key is present, we check that we have
959 connectivity via ssh with the target nodes (and check the hostname
960 report).
961
962 If the I{node-net-test} key is present, we check that we have
963 connectivity to the given nodes via both primary IP and, if
964 applicable, secondary IPs.
965
966 @type what: C{dict}
967 @param what: a dictionary of things to check:
968 - filelist: list of files for which to compute checksums
969 - nodelist: list of nodes we should check ssh communication with
970 - node-net-test: list of nodes we should check node daemon port
971 connectivity with
972 - hypervisor: list with hypervisors to run the verify for
973 @type cluster_name: string
974 @param cluster_name: the cluster's name
975 @type all_hvparams: dict of dict of strings
976 @param all_hvparams: a dictionary mapping hypervisor names to hvparams
977 @type node_groups: a dict of strings
978 @param node_groups: node _names_ mapped to their group uuids (it's enough to
979 have only those nodes that are in `what["nodelist"]`)
980 @type groups_cfg: a dict of dict of strings
981 @param groups_cfg: a dictionary mapping group uuids to their configuration
982 @rtype: dict
983 @return: a dictionary with the same keys as the input dict, and
984 values representing the result of the checks
985
986 """
987 result = {}
988 my_name = netutils.Hostname.GetSysName()
989 port = netutils.GetDaemonPort(constants.NODED)
990 vm_capable = my_name not in what.get(constants.NV_NONVMNODES, [])
991
992 _VerifyHypervisors(what, vm_capable, result, all_hvparams)
993 _VerifyHvparams(what, vm_capable, result)
994
995 if constants.NV_FILELIST in what:
996 fingerprints = utils.FingerprintFiles(map(vcluster.LocalizeVirtualPath,
997 what[constants.NV_FILELIST]))
998 result[constants.NV_FILELIST] = \
999 dict((vcluster.MakeVirtualPath(key), value)
1000 for (key, value) in fingerprints.items())
1001
1002 if constants.NV_CLIENT_CERT in what:
1003 result[constants.NV_CLIENT_CERT] = _VerifyClientCertificate()
1004
1005 if constants.NV_NODELIST in what:
1006 (nodes, bynode) = what[constants.NV_NODELIST]
1007
1008
1009 try:
1010 nodes.extend(bynode[my_name])
1011 except KeyError:
1012 pass
1013
1014
1015 random.shuffle(nodes)
1016
1017
1018 val = {}
1019 for node in nodes:
1020 params = groups_cfg.get(node_groups.get(node))
1021 ssh_port = params["ndparams"].get(constants.ND_SSH_PORT)
1022 logging.debug("Ssh port %s (None = default) for node %s",
1023 str(ssh_port), node)
1024 success, message = _GetSshRunner(cluster_name). \
1025 VerifyNodeHostname(node, ssh_port)
1026 if not success:
1027 val[node] = message
1028
1029 result[constants.NV_NODELIST] = val
1030
1031 if constants.NV_NODENETTEST in what:
1032 result[constants.NV_NODENETTEST] = tmp = {}
1033 my_pip = my_sip = None
1034 for name, pip, sip in what[constants.NV_NODENETTEST]:
1035 if name == my_name:
1036 my_pip = pip
1037 my_sip = sip
1038 break
1039 if not my_pip:
1040 tmp[my_name] = ("Can't find my own primary/secondary IP"
1041 " in the node list")
1042 else:
1043 for name, pip, sip in what[constants.NV_NODENETTEST]:
1044 fail = []
1045 if not netutils.TcpPing(pip, port, source=my_pip):
1046 fail.append("primary")
1047 if sip != pip:
1048 if not netutils.TcpPing(sip, port, source=my_sip):
1049 fail.append("secondary")
1050 if fail:
1051 tmp[name] = ("failure using the %s interface(s)" %
1052 " and ".join(fail))
1053
1054 if constants.NV_MASTERIP in what:
1055
1056
1057 master_name, master_ip = what[constants.NV_MASTERIP]
1058 if master_name == my_name:
1059 source = constants.IP4_ADDRESS_LOCALHOST
1060 else:
1061 source = None
1062 result[constants.NV_MASTERIP] = netutils.TcpPing(master_ip, port,
1063 source=source)
1064
1065 if constants.NV_USERSCRIPTS in what:
1066 result[constants.NV_USERSCRIPTS] = \
1067 [script for script in what[constants.NV_USERSCRIPTS]
1068 if not utils.IsExecutable(script)]
1069
1070 if constants.NV_OOB_PATHS in what:
1071 result[constants.NV_OOB_PATHS] = tmp = []
1072 for path in what[constants.NV_OOB_PATHS]:
1073 try:
1074 st = os.stat(path)
1075 except OSError, err:
1076 tmp.append("error stating out of band helper: %s" % err)
1077 else:
1078 if stat.S_ISREG(st.st_mode):
1079 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR:
1080 tmp.append(None)
1081 else:
1082 tmp.append("out of band helper %s is not executable" % path)
1083 else:
1084 tmp.append("out of band helper %s is not a file" % path)
1085
1086 if constants.NV_LVLIST in what and vm_capable:
1087 try:
1088 val = GetVolumeList([what[constants.NV_LVLIST]])
1089 except RPCFail, err:
1090 val = str(err)
1091 result[constants.NV_LVLIST] = val
1092
1093 _VerifyInstanceList(what, vm_capable, result, all_hvparams)
1094
1095 if constants.NV_VGLIST in what and vm_capable:
1096 result[constants.NV_VGLIST] = utils.ListVolumeGroups()
1097
1098 if constants.NV_PVLIST in what and vm_capable:
1099 check_exclusive_pvs = constants.NV_EXCLUSIVEPVS in what
1100 val = bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
1101 filter_allocatable=False,
1102 include_lvs=check_exclusive_pvs)
1103 if check_exclusive_pvs:
1104 result[constants.NV_EXCLUSIVEPVS] = _CheckExclusivePvs(val)
1105 for pvi in val:
1106
1107 pvi.lv_list = []
1108 result[constants.NV_PVLIST] = map(objects.LvmPvInfo.ToDict, val)
1109
1110 if constants.NV_VERSION in what:
1111 result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
1112 constants.RELEASE_VERSION)
1113
1114 _VerifyNodeInfo(what, vm_capable, result, all_hvparams)
1115
1116 if constants.NV_DRBDVERSION in what and vm_capable:
1117 try:
1118 drbd_version = DRBD8.GetProcInfo().GetVersionString()
1119 except errors.BlockDeviceError, err:
1120 logging.warning("Can't get DRBD version", exc_info=True)
1121 drbd_version = str(err)
1122 result[constants.NV_DRBDVERSION] = drbd_version
1123
1124 if constants.NV_DRBDLIST in what and vm_capable:
1125 try:
1126 used_minors = drbd.DRBD8.GetUsedDevs()
1127 except errors.BlockDeviceError, err:
1128 logging.warning("Can't get used minors list", exc_info=True)
1129 used_minors = str(err)
1130 result[constants.NV_DRBDLIST] = used_minors
1131
1132 if constants.NV_DRBDHELPER in what and vm_capable:
1133 status = True
1134 try:
1135 payload = drbd.DRBD8.GetUsermodeHelper()
1136 except errors.BlockDeviceError, err:
1137 logging.error("Can't get DRBD usermode helper: %s", str(err))
1138 status = False
1139 payload = str(err)
1140 result[constants.NV_DRBDHELPER] = (status, payload)
1141
1142 if constants.NV_NODESETUP in what:
1143 result[constants.NV_NODESETUP] = tmpr = []
1144 if not os.path.isdir("/sys/block") or not os.path.isdir("/sys/class/net"):
1145 tmpr.append("The sysfs filesytem doesn't seem to be mounted"
1146 " under /sys, missing required directories /sys/block"
1147 " and /sys/class/net")
1148 if (not os.path.isdir("/proc/sys") or
1149 not os.path.isfile("/proc/sysrq-trigger")):
1150 tmpr.append("The procfs filesystem doesn't seem to be mounted"
1151 " under /proc, missing required directory /proc/sys and"
1152 " the file /proc/sysrq-trigger")
1153
1154 if constants.NV_TIME in what:
1155 result[constants.NV_TIME] = utils.SplitTime(time.time())
1156
1157 if constants.NV_OSLIST in what and vm_capable:
1158 result[constants.NV_OSLIST] = DiagnoseOS()
1159
1160 if constants.NV_BRIDGES in what and vm_capable:
1161 result[constants.NV_BRIDGES] = [bridge
1162 for bridge in what[constants.NV_BRIDGES]
1163 if not utils.BridgeExists(bridge)]
1164
1165 if what.get(constants.NV_ACCEPTED_STORAGE_PATHS) == my_name:
1166 result[constants.NV_ACCEPTED_STORAGE_PATHS] = \
1167 filestorage.ComputeWrongFileStoragePaths()
1168
1169 if what.get(constants.NV_FILE_STORAGE_PATH):
1170 pathresult = filestorage.CheckFileStoragePath(
1171 what[constants.NV_FILE_STORAGE_PATH])
1172 if pathresult:
1173 result[constants.NV_FILE_STORAGE_PATH] = pathresult
1174
1175 if what.get(constants.NV_SHARED_FILE_STORAGE_PATH):
1176 pathresult = filestorage.CheckFileStoragePath(
1177 what[constants.NV_SHARED_FILE_STORAGE_PATH])
1178 if pathresult:
1179 result[constants.NV_SHARED_FILE_STORAGE_PATH] = pathresult
1180
1181 return result
1182
1185 """Perform actions on the node's cryptographic tokens.
1186
1187 Token types can be 'ssl' or 'ssh'. So far only some actions are implemented
1188 for 'ssl'. Action 'get' returns the digest of the public client ssl
1189 certificate. Action 'create' creates a new client certificate and private key
1190 and also returns the digest of the certificate. The third parameter of a
1191 token request are optional parameters for the actions, so far only the
1192 filename is supported.
1193
1194 @type token_requests: list of tuples of (string, string, dict), where the
1195 first string is in constants.CRYPTO_TYPES, the second in
1196 constants.CRYPTO_ACTIONS. The third parameter is a dictionary of string
1197 to string.
1198 @param token_requests: list of requests of cryptographic tokens and actions
1199 to perform on them. The actions come with a dictionary of options.
1200 @rtype: list of tuples (string, string)
1201 @return: list of tuples of the token type and the public crypto token
1202
1203 """
1204 getents = runtime.GetEnts()
1205 _VALID_CERT_FILES = [pathutils.NODED_CERT_FILE,
1206 pathutils.NODED_CLIENT_CERT_FILE,
1207 pathutils.NODED_CLIENT_CERT_FILE_TMP]
1208 _DEFAULT_CERT_FILE = pathutils.NODED_CLIENT_CERT_FILE
1209 tokens = []
1210 for (token_type, action, options) in token_requests:
1211 if token_type not in constants.CRYPTO_TYPES:
1212 raise errors.ProgrammerError("Token type '%s' not supported." %
1213 token_type)
1214 if action not in constants.CRYPTO_ACTIONS:
1215 raise errors.ProgrammerError("Action '%s' is not supported." %
1216 action)
1217 if token_type == constants.CRYPTO_TYPE_SSL_DIGEST:
1218 if action == constants.CRYPTO_ACTION_CREATE:
1219
1220
1221 cert_filename = None
1222 if options:
1223 cert_filename = options.get(constants.CRYPTO_OPTION_CERT_FILE)
1224 if not cert_filename:
1225 cert_filename = _DEFAULT_CERT_FILE
1226
1227 if not cert_filename in _VALID_CERT_FILES:
1228 raise errors.ProgrammerError(
1229 "The certificate file name path '%s' is not allowed." %
1230 cert_filename)
1231
1232
1233 serial_no = None
1234 if options:
1235 try:
1236 serial_no = int(options[constants.CRYPTO_OPTION_SERIAL_NO])
1237 except ValueError:
1238 raise errors.ProgrammerError(
1239 "The given serial number is not an intenger: %s." %
1240 options.get(constants.CRYPTO_OPTION_SERIAL_NO))
1241 except KeyError:
1242 raise errors.ProgrammerError("No serial number was provided.")
1243
1244 if not serial_no:
1245 raise errors.ProgrammerError(
1246 "Cannot create an SSL certificate without a serial no.")
1247
1248 utils.GenerateNewSslCert(
1249 True, cert_filename, serial_no,
1250 "Create new client SSL certificate in %s." % cert_filename,
1251 uid=getents.masterd_uid, gid=getents.masterd_gid)
1252 tokens.append((token_type,
1253 utils.GetCertificateDigest(
1254 cert_filename=cert_filename)))
1255 elif action == constants.CRYPTO_ACTION_GET:
1256 tokens.append((token_type,
1257 utils.GetCertificateDigest()))
1258 return tokens
1259
1262 """Ensures the given daemon is running or stopped.
1263
1264 @type daemon_name: string
1265 @param daemon_name: name of the daemon (e.g., constants.KVMD)
1266
1267 @type run: bool
1268 @param run: whether to start or stop the daemon
1269
1270 @rtype: bool
1271 @return: 'True' if daemon successfully started/stopped,
1272 'False' otherwise
1273
1274 """
1275 allowed_daemons = [constants.KVMD]
1276
1277 if daemon_name not in allowed_daemons:
1278 fn = lambda _: False
1279 elif run:
1280 fn = utils.EnsureDaemon
1281 else:
1282 fn = utils.StopDaemon
1283
1284 return fn(daemon_name)
1285
1288 """Return the size of the given block devices
1289
1290 @type devices: list
1291 @param devices: list of block device nodes to query
1292 @rtype: dict
1293 @return:
1294 dictionary of all block devices under /dev (key). The value is their
1295 size in MiB.
1296
1297 {'/dev/disk/by-uuid/123456-12321231-312312-312': 124}
1298
1299 """
1300 DEV_PREFIX = "/dev/"
1301 blockdevs = {}
1302
1303 for devpath in devices:
1304 if not utils.IsBelowDir(DEV_PREFIX, devpath):
1305 continue
1306
1307 try:
1308 st = os.stat(devpath)
1309 except EnvironmentError, err:
1310 logging.warning("Error stat()'ing device %s: %s", devpath, str(err))
1311 continue
1312
1313 if stat.S_ISBLK(st.st_mode):
1314 result = utils.RunCmd(["blockdev", "--getsize64", devpath])
1315 if result.failed:
1316
1317 logging.warning("Cannot get size for block device %s", devpath)
1318 continue
1319
1320 size = int(result.stdout) / (1024 * 1024)
1321 blockdevs[devpath] = size
1322 return blockdevs
1323
1326 """Compute list of logical volumes and their size.
1327
1328 @type vg_names: list
1329 @param vg_names: the volume groups whose LVs we should list, or
1330 empty for all volume groups
1331 @rtype: dict
1332 @return:
1333 dictionary of all partions (key) with value being a tuple of
1334 their size (in MiB), inactive and online status::
1335
1336 {'xenvg/test1': ('20.06', True, True)}
1337
1338 in case of errors, a string is returned with the error
1339 details.
1340
1341 """
1342 lvs = {}
1343 sep = "|"
1344 if not vg_names:
1345 vg_names = []
1346 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
1347 "--separator=%s" % sep,
1348 "-ovg_name,lv_name,lv_size,lv_attr"] + vg_names)
1349 if result.failed:
1350 _Fail("Failed to list logical volumes, lvs output: %s", result.output)
1351
1352 for line in result.stdout.splitlines():
1353 line = line.strip()
1354 match = _LVSLINE_REGEX.match(line)
1355 if not match:
1356 logging.error("Invalid line returned from lvs output: '%s'", line)
1357 continue
1358 vg_name, name, size, attr = match.groups()
1359 inactive = attr[4] == "-"
1360 online = attr[5] == "o"
1361 virtual = attr[0] == "v"
1362 if virtual:
1363
1364
1365 continue
1366 lvs[vg_name + "/" + name] = (size, inactive, online)
1367
1368 return lvs
1369
1372 """List the volume groups and their size.
1373
1374 @rtype: dict
1375 @return: dictionary with keys volume name and values the
1376 size of the volume
1377
1378 """
1379 return utils.ListVolumeGroups()
1380
1383 """List all volumes on this node.
1384
1385 @rtype: list
1386 @return:
1387 A list of dictionaries, each having four keys:
1388 - name: the logical volume name,
1389 - size: the size of the logical volume
1390 - dev: the physical device on which the LV lives
1391 - vg: the volume group to which it belongs
1392
1393 In case of errors, we return an empty list and log the
1394 error.
1395
1396 Note that since a logical volume can live on multiple physical
1397 volumes, the resulting list might include a logical volume
1398 multiple times.
1399
1400 """
1401 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
1402 "--separator=|",
1403 "--options=lv_name,lv_size,devices,vg_name"])
1404 if result.failed:
1405 _Fail("Failed to list logical volumes, lvs output: %s",
1406 result.output)
1407
1408 def parse_dev(dev):
1409 return dev.split("(")[0]
1410
1411 def handle_dev(dev):
1412 return [parse_dev(x) for x in dev.split(",")]
1413
1414 def map_line(line):
1415 line = [v.strip() for v in line]
1416 return [{"name": line[0], "size": line[1],
1417 "dev": dev, "vg": line[3]} for dev in handle_dev(line[2])]
1418
1419 all_devs = []
1420 for line in result.stdout.splitlines():
1421 if line.count("|") >= 3:
1422 all_devs.extend(map_line(line.split("|")))
1423 else:
1424 logging.warning("Strange line in the output from lvs: '%s'", line)
1425 return all_devs
1426
1429 """Check if a list of bridges exist on the current node.
1430
1431 @rtype: boolean
1432 @return: C{True} if all of them exist, C{False} otherwise
1433
1434 """
1435 missing = []
1436 for bridge in bridges_list:
1437 if not utils.BridgeExists(bridge):
1438 missing.append(bridge)
1439
1440 if missing:
1441 _Fail("Missing bridges %s", utils.CommaJoin(missing))
1442
1446 """Provides a list of instances of the given hypervisor.
1447
1448 @type hname: string
1449 @param hname: name of the hypervisor
1450 @type hvparams: dict of strings
1451 @param hvparams: hypervisor parameters for the given hypervisor
1452 @type get_hv_fn: function
1453 @param get_hv_fn: function that returns a hypervisor for the given hypervisor
1454 name; optional parameter to increase testability
1455
1456 @rtype: list
1457 @return: a list of all running instances on the current node
1458 - instance1.example.com
1459 - instance2.example.com
1460
1461 """
1462 try:
1463 return get_hv_fn(hname).ListInstances(hvparams=hvparams)
1464 except errors.HypervisorError, err:
1465 _Fail("Error enumerating instances (hypervisor %s): %s",
1466 hname, err, exc=True)
1467
1471 """Provides a list of instances.
1472
1473 @type hypervisor_list: list
1474 @param hypervisor_list: the list of hypervisors to query information
1475 @type all_hvparams: dict of dict of strings
1476 @param all_hvparams: a dictionary mapping hypervisor types to respective
1477 cluster-wide hypervisor parameters
1478 @type get_hv_fn: function
1479 @param get_hv_fn: function that returns a hypervisor for the given hypervisor
1480 name; optional parameter to increase testability
1481
1482 @rtype: list
1483 @return: a list of all running instances on the current node
1484 - instance1.example.com
1485 - instance2.example.com
1486
1487 """
1488 results = []
1489 for hname in hypervisor_list:
1490 hvparams = all_hvparams[hname]
1491 results.extend(GetInstanceListForHypervisor(hname, hvparams=hvparams,
1492 get_hv_fn=get_hv_fn))
1493 return results
1494
1497 """Gives back the information about an instance as a dictionary.
1498
1499 @type instance: string
1500 @param instance: the instance name
1501 @type hname: string
1502 @param hname: the hypervisor type of the instance
1503 @type hvparams: dict of strings
1504 @param hvparams: the instance's hvparams
1505
1506 @rtype: dict
1507 @return: dictionary with the following keys:
1508 - memory: memory size of instance (int)
1509 - state: state of instance (HvInstanceState)
1510 - time: cpu time of instance (float)
1511 - vcpus: the number of vcpus (int)
1512
1513 """
1514 output = {}
1515
1516 iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance,
1517 hvparams=hvparams)
1518 if iinfo is not None:
1519 output["memory"] = iinfo[2]
1520 output["vcpus"] = iinfo[3]
1521 output["state"] = iinfo[4]
1522 output["time"] = iinfo[5]
1523
1524 return output
1525
1528 """Computes whether an instance can be migrated.
1529
1530 @type instance: L{objects.Instance}
1531 @param instance: object representing the instance to be checked.
1532
1533 @rtype: tuple
1534 @return: tuple of (result, description) where:
1535 - result: whether the instance can be migrated or not
1536 - description: a description of the issue, if relevant
1537
1538 """
1539 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1540 iname = instance.name
1541 if iname not in hyper.ListInstances(hvparams=instance.hvparams):
1542 _Fail("Instance %s is not running", iname)
1543
1544 for idx in range(len(instance.disks)):
1545 link_name = _GetBlockDevSymlinkPath(iname, idx)
1546 if not os.path.islink(link_name):
1547 logging.warning("Instance %s is missing symlink %s for disk %d",
1548 iname, link_name, idx)
1549
1552 """Gather data about all instances.
1553
1554 This is the equivalent of L{GetInstanceInfo}, except that it
1555 computes data for all instances at once, thus being faster if one
1556 needs data about more than one instance.
1557
1558 @type hypervisor_list: list
1559 @param hypervisor_list: list of hypervisors to query for instance data
1560 @type all_hvparams: dict of dict of strings
1561 @param all_hvparams: mapping of hypervisor names to hvparams
1562
1563 @rtype: dict
1564 @return: dictionary of instance: data, with data having the following keys:
1565 - memory: memory size of instance (int)
1566 - state: xen state of instance (string)
1567 - time: cpu time of instance (float)
1568 - vcpus: the number of vcpus
1569
1570 """
1571 output = {}
1572 for hname in hypervisor_list:
1573 hvparams = all_hvparams[hname]
1574 iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
1575 if iinfo:
1576 for name, _, memory, vcpus, state, times in iinfo:
1577 value = {
1578 "memory": memory,
1579 "vcpus": vcpus,
1580 "state": state,
1581 "time": times,
1582 }
1583 if name in output:
1584
1585
1586
1587 for key in "memory", "vcpus":
1588 if value[key] != output[name][key]:
1589 _Fail("Instance %s is running twice"
1590 " with different parameters", name)
1591 output[name] = value
1592
1593 return output
1594
1598 """Gather data about the console access of a set of instances of this node.
1599
1600 This function assumes that the caller already knows which instances are on
1601 this node, by calling a function such as L{GetAllInstancesInfo} or
1602 L{GetInstanceList}.
1603
1604 For every instance, a large amount of configuration data needs to be
1605 provided to the hypervisor interface in order to receive the console
1606 information. Whether this could or should be cut down can be discussed.
1607 The information is provided in a dictionary indexed by instance name,
1608 allowing any number of instance queries to be done.
1609
1610 @type instance_param_dict: dict of string to tuple of dictionaries, where the
1611 dictionaries represent: L{objects.Instance}, L{objects.Node},
1612 L{objects.NodeGroup}, HvParams, BeParams
1613 @param instance_param_dict: mapping of instance name to parameters necessary
1614 for console information retrieval
1615
1616 @rtype: dict
1617 @return: dictionary of instance: data, with data having the following keys:
1618 - instance: instance name
1619 - kind: console kind
1620 - message: used with kind == CONS_MESSAGE, indicates console to be
1621 unavailable, supplies error message
1622 - host: host to connect to
1623 - port: port to use
1624 - user: user for login
1625 - command: the command, broken into parts as an array
1626 - display: unknown, potentially unused?
1627
1628 """
1629
1630 output = {}
1631 for inst_name in instance_param_dict:
1632 instance = instance_param_dict[inst_name]["instance"]
1633 pnode = instance_param_dict[inst_name]["node"]
1634 group = instance_param_dict[inst_name]["group"]
1635 hvparams = instance_param_dict[inst_name]["hvParams"]
1636 beparams = instance_param_dict[inst_name]["beParams"]
1637
1638 instance = objects.Instance.FromDict(instance)
1639 pnode = objects.Node.FromDict(pnode)
1640 group = objects.NodeGroup.FromDict(group)
1641
1642 h = get_hv_fn(instance.hypervisor)
1643 output[inst_name] = h.GetInstanceConsole(instance, pnode, group,
1644 hvparams, beparams).ToDict()
1645
1646 return output
1647
1650 """Compute the OS log filename for a given instance and operation.
1651
1652 The instance name and os name are passed in as strings since not all
1653 operations have these as part of an instance object.
1654
1655 @type kind: string
1656 @param kind: the operation type (e.g. add, import, etc.)
1657 @type os_name: string
1658 @param os_name: the os name
1659 @type instance: string
1660 @param instance: the name of the instance being imported/added/etc.
1661 @type component: string or None
1662 @param component: the name of the component of the instance being
1663 transferred
1664
1665 """
1666
1667 if component:
1668 assert "/" not in component
1669 c_msg = "-%s" % component
1670 else:
1671 c_msg = ""
1672 base = ("%s-%s-%s%s-%s.log" %
1673 (kind, os_name, instance, c_msg, utils.TimestampForFilename()))
1674 return utils.PathJoin(pathutils.LOG_OS_DIR, base)
1675
1678 """Add an OS to an instance.
1679
1680 @type instance: L{objects.Instance}
1681 @param instance: Instance whose OS is to be installed
1682 @type reinstall: boolean
1683 @param reinstall: whether this is an instance reinstall
1684 @type debug: integer
1685 @param debug: debug level, passed to the OS scripts
1686 @rtype: None
1687
1688 """
1689 inst_os = OSFromDisk(instance.os)
1690
1691 create_env = OSEnvironment(instance, inst_os, debug)
1692 if reinstall:
1693 create_env["INSTANCE_REINSTALL"] = "1"
1694
1695 logfile = _InstanceLogName("add", instance.os, instance.name, None)
1696
1697 result = utils.RunCmd([inst_os.create_script], env=create_env,
1698 cwd=inst_os.path, output=logfile, reset_env=True)
1699 if result.failed:
1700 logging.error("os create command '%s' returned error: %s, logfile: %s,"
1701 " output: %s", result.cmd, result.fail_reason, logfile,
1702 result.output)
1703 lines = [utils.SafeEncode(val)
1704 for val in utils.TailFile(logfile, lines=20)]
1705 _Fail("OS create script failed (%s), last lines in the"
1706 " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
1707
1710 """Run the OS rename script for an instance.
1711
1712 @type instance: L{objects.Instance}
1713 @param instance: Instance whose OS is to be installed
1714 @type old_name: string
1715 @param old_name: previous instance name
1716 @type debug: integer
1717 @param debug: debug level, passed to the OS scripts
1718 @rtype: boolean
1719 @return: the success of the operation
1720
1721 """
1722 inst_os = OSFromDisk(instance.os)
1723
1724 rename_env = OSEnvironment(instance, inst_os, debug)
1725 rename_env["OLD_INSTANCE_NAME"] = old_name
1726
1727 logfile = _InstanceLogName("rename", instance.os,
1728 "%s-%s" % (old_name, instance.name), None)
1729
1730 result = utils.RunCmd([inst_os.rename_script], env=rename_env,
1731 cwd=inst_os.path, output=logfile, reset_env=True)
1732
1733 if result.failed:
1734 logging.error("os create command '%s' returned error: %s output: %s",
1735 result.cmd, result.fail_reason, result.output)
1736 lines = [utils.SafeEncode(val)
1737 for val in utils.TailFile(logfile, lines=20)]
1738 _Fail("OS rename script failed (%s), last lines in the"
1739 " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
1740
1752
1755 """Set up symlinks to a instance's block device.
1756
1757 This is an auxiliary function run when an instance is start (on the primary
1758 node) or when an instance is migrated (on the target node).
1759
1760
1761 @param instance_name: the name of the target instance
1762 @param device_path: path of the physical block device, on the node
1763 @param idx: the disk index
1764 @return: absolute path to the disk's symlink
1765
1766 """
1767 link_name = _GetBlockDevSymlinkPath(instance_name, idx)
1768 try:
1769 os.symlink(device_path, link_name)
1770 except OSError, err:
1771 if err.errno == errno.EEXIST:
1772 if (not os.path.islink(link_name) or
1773 os.readlink(link_name) != device_path):
1774 os.remove(link_name)
1775 os.symlink(device_path, link_name)
1776 else:
1777 raise
1778
1779 return link_name
1780
1783 """Remove the block device symlinks belonging to the given instance.
1784
1785 """
1786 for idx, _ in enumerate(disks):
1787 link_name = _GetBlockDevSymlinkPath(instance_name, idx)
1788 if os.path.islink(link_name):
1789 try:
1790 os.remove(link_name)
1791 except OSError:
1792 logging.exception("Can't remove symlink '%s'", link_name)
1793
1796 """Get the URI for the device.
1797
1798 @type instance: L{objects.Instance}
1799 @param instance: the instance which disk belongs to
1800 @type disk: L{objects.Disk}
1801 @param disk: the target disk object
1802 @type device: L{bdev.BlockDev}
1803 @param device: the corresponding BlockDevice
1804 @rtype: string
1805 @return: the device uri if any else None
1806
1807 """
1808 access_mode = disk.params.get(constants.LDP_ACCESS,
1809 constants.DISK_KERNELSPACE)
1810 if access_mode == constants.DISK_USERSPACE:
1811
1812 return device.GetUserspaceAccessUri(instance.hypervisor)
1813 else:
1814 return None
1815
1818 """Set up an instance's block device(s).
1819
1820 This is run on the primary node at instance startup. The block
1821 devices must be already assembled.
1822
1823 @type instance: L{objects.Instance}
1824 @param instance: the instance whose disks we should assemble
1825 @rtype: list
1826 @return: list of (disk_object, link_name, drive_uri)
1827
1828 """
1829 block_devices = []
1830 for idx, disk in enumerate(instance.disks):
1831 device = _RecursiveFindBD(disk)
1832 if device is None:
1833 raise errors.BlockDeviceError("Block device '%s' is not set up." %
1834 str(disk))
1835 device.Open()
1836 try:
1837 link_name = _SymlinkBlockDev(instance.name, device.dev_path, idx)
1838 except OSError, e:
1839 raise errors.BlockDeviceError("Cannot create block device symlink: %s" %
1840 e.strerror)
1841 uri = _CalculateDeviceURI(instance, disk, device)
1842
1843 block_devices.append((disk, link_name, uri))
1844
1845 return block_devices
1846
1852
1858
1859
1860 -def StartInstance(instance, startup_paused, reason, store_reason=True):
1861 """Start an instance.
1862
1863 @type instance: L{objects.Instance}
1864 @param instance: the instance object
1865 @type startup_paused: bool
1866 @param instance: pause instance at startup?
1867 @type reason: list of reasons
1868 @param reason: the reason trail for this startup
1869 @type store_reason: boolean
1870 @param store_reason: whether to store the shutdown reason trail on file
1871 @rtype: None
1872
1873 """
1874 instance_info = _GetInstanceInfo(instance)
1875
1876 if instance_info and not _IsInstanceUserDown(instance_info):
1877 logging.info("Instance '%s' already running, not starting", instance.name)
1878 return
1879
1880 try:
1881 block_devices = _GatherAndLinkBlockDevs(instance)
1882 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1883 hyper.StartInstance(instance, block_devices, startup_paused)
1884 if store_reason:
1885 _StoreInstReasonTrail(instance.name, reason)
1886 except errors.BlockDeviceError, err:
1887 _Fail("Block device error: %s", err, exc=True)
1888 except errors.HypervisorError, err:
1889 _RemoveBlockDevLinks(instance.name, instance.disks)
1890 _Fail("Hypervisor error: %s", err, exc=True)
1891
1894 """Shut an instance down.
1895
1896 @note: this functions uses polling with a hardcoded timeout.
1897
1898 @type instance: L{objects.Instance}
1899 @param instance: the instance object
1900 @type timeout: integer
1901 @param timeout: maximum timeout for soft shutdown
1902 @type reason: list of reasons
1903 @param reason: the reason trail for this shutdown
1904 @type store_reason: boolean
1905 @param store_reason: whether to store the shutdown reason trail on file
1906 @rtype: None
1907
1908 """
1909 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1910
1911 if not _GetInstanceInfo(instance):
1912 logging.info("Instance '%s' not running, doing nothing", instance.name)
1913 return
1914
1915 class _TryShutdown(object):
1916 def __init__(self):
1917 self.tried_once = False
1918
1919 def __call__(self):
1920 if not _GetInstanceInfo(instance):
1921 return
1922
1923 try:
1924 hyper.StopInstance(instance, retry=self.tried_once, timeout=timeout)
1925 if store_reason:
1926 _StoreInstReasonTrail(instance.name, reason)
1927 except errors.HypervisorError, err:
1928
1929
1930 if not _GetInstanceInfo(instance):
1931 return
1932
1933 _Fail("Failed to stop instance '%s': %s", instance.name, err)
1934
1935 self.tried_once = True
1936
1937 raise utils.RetryAgain()
1938
1939 try:
1940 utils.Retry(_TryShutdown(), 5, timeout)
1941 except utils.RetryTimeout:
1942
1943 logging.error("Shutdown of '%s' unsuccessful, forcing", instance.name)
1944
1945 try:
1946 hyper.StopInstance(instance, force=True)
1947 except errors.HypervisorError, err:
1948
1949
1950 if _GetInstanceInfo(instance):
1951 _Fail("Failed to force stop instance '%s': %s", instance.name, err)
1952
1953 time.sleep(1)
1954
1955 if _GetInstanceInfo(instance):
1956 _Fail("Could not shutdown instance '%s' even by destroy", instance.name)
1957
1958 try:
1959 hyper.CleanupInstance(instance.name)
1960 except errors.HypervisorError, err:
1961 logging.warning("Failed to execute post-shutdown cleanup step: %s", err)
1962
1963 _RemoveBlockDevLinks(instance.name, instance.disks)
1964
1965
1966 -def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
1967 """Reboot an instance.
1968
1969 @type instance: L{objects.Instance}
1970 @param instance: the instance object to reboot
1971 @type reboot_type: str
1972 @param reboot_type: the type of reboot, one the following
1973 constants:
1974 - L{constants.INSTANCE_REBOOT_SOFT}: only reboot the
1975 instance OS, do not recreate the VM
1976 - L{constants.INSTANCE_REBOOT_HARD}: tear down and
1977 restart the VM (at the hypervisor level)
1978 - the other reboot type (L{constants.INSTANCE_REBOOT_FULL}) is
1979 not accepted here, since that mode is handled differently, in
1980 cmdlib, and translates into full stop and start of the
1981 instance (instead of a call_instance_reboot RPC)
1982 @type shutdown_timeout: integer
1983 @param shutdown_timeout: maximum timeout for soft shutdown
1984 @type reason: list of reasons
1985 @param reason: the reason trail for this reboot
1986 @rtype: None
1987
1988 """
1989
1990
1991
1992 if not _GetInstanceInfo(instance):
1993 _Fail("Cannot reboot instance '%s' that is not running", instance.name)
1994
1995 hyper = hypervisor.GetHypervisor(instance.hypervisor)
1996 if reboot_type == constants.INSTANCE_REBOOT_SOFT:
1997 try:
1998 hyper.RebootInstance(instance)
1999 except errors.HypervisorError, err:
2000 _Fail("Failed to soft reboot instance '%s': %s", instance.name, err)
2001 elif reboot_type == constants.INSTANCE_REBOOT_HARD:
2002 try:
2003 InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
2004 result = StartInstance(instance, False, reason, store_reason=False)
2005 _StoreInstReasonTrail(instance.name, reason)
2006 return result
2007 except errors.HypervisorError, err:
2008 _Fail("Failed to hard reboot instance '%s': %s", instance.name, err)
2009 else:
2010 _Fail("Invalid reboot_type received: '%s'", reboot_type)
2011
2032
2047
2050 """Prepare the node to accept an instance.
2051
2052 @type instance: L{objects.Instance}
2053 @param instance: the instance definition
2054 @type info: string/data (opaque)
2055 @param info: migration information, from the source node
2056 @type target: string
2057 @param target: target host (usually ip), on this node
2058
2059 """
2060
2061 if instance.disk_template in constants.DTS_EXT_MIRROR:
2062
2063
2064 try:
2065 _GatherAndLinkBlockDevs(instance)
2066 except errors.BlockDeviceError, err:
2067 _Fail("Block device error: %s", err, exc=True)
2068
2069 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2070 try:
2071 hyper.AcceptInstance(instance, info, target)
2072 except errors.HypervisorError, err:
2073 if instance.disk_template in constants.DTS_EXT_MIRROR:
2074 _RemoveBlockDevLinks(instance.name, instance.disks)
2075 _Fail("Failed to accept instance: %s", err, exc=True)
2076
2079 """Finalize any preparation to accept an instance.
2080
2081 @type instance: L{objects.Instance}
2082 @param instance: the instance definition
2083 @type info: string/data (opaque)
2084 @param info: migration information, from the source node
2085 @type success: boolean
2086 @param success: whether the migration was a success or a failure
2087
2088 """
2089 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2090 try:
2091 hyper.FinalizeMigrationDst(instance, info, success)
2092 except errors.HypervisorError, err:
2093 _Fail("Failed to finalize migration on the target node: %s", err, exc=True)
2094
2097 """Migrates an instance to another node.
2098
2099 @type cluster_name: string
2100 @param cluster_name: name of the cluster
2101 @type instance: L{objects.Instance}
2102 @param instance: the instance definition
2103 @type target: string
2104 @param target: the target node name
2105 @type live: boolean
2106 @param live: whether the migration should be done live or not (the
2107 interpretation of this parameter is left to the hypervisor)
2108 @raise RPCFail: if migration fails for some reason
2109
2110 """
2111 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2112
2113 try:
2114 hyper.MigrateInstance(cluster_name, instance, target, live)
2115 except errors.HypervisorError, err:
2116 _Fail("Failed to migrate instance: %s", err, exc=True)
2117
2120 """Finalize the instance migration on the source node.
2121
2122 @type instance: L{objects.Instance}
2123 @param instance: the instance definition of the migrated instance
2124 @type success: bool
2125 @param success: whether the migration succeeded or not
2126 @type live: bool
2127 @param live: whether the user requested a live migration or not
2128 @raise RPCFail: If the execution fails for some reason
2129
2130 """
2131 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2132
2133 try:
2134 hyper.FinalizeMigrationSource(instance, success, live)
2135 except Exception, err:
2136 _Fail("Failed to finalize the migration on the source node: %s", err,
2137 exc=True)
2138
2141 """Get the migration status
2142
2143 @type instance: L{objects.Instance}
2144 @param instance: the instance that is being migrated
2145 @rtype: L{objects.MigrationStatus}
2146 @return: the status of the current migration (one of
2147 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
2148 progress info that can be retrieved from the hypervisor
2149 @raise RPCFail: If the migration status cannot be retrieved
2150
2151 """
2152 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2153 try:
2154 return hyper.GetMigrationStatus(instance)
2155 except Exception, err:
2156 _Fail("Failed to get migration status: %s", err, exc=True)
2157
2158
2159 -def HotplugDevice(instance, action, dev_type, device, extra, seq):
2160 """Hotplug a device
2161
2162 Hotplug is currently supported only for KVM Hypervisor.
2163 @type instance: L{objects.Instance}
2164 @param instance: the instance to which we hotplug a device
2165 @type action: string
2166 @param action: the hotplug action to perform
2167 @type dev_type: string
2168 @param dev_type: the device type to hotplug
2169 @type device: either L{objects.NIC} or L{objects.Disk}
2170 @param device: the device object to hotplug
2171 @type extra: tuple
2172 @param extra: extra info used for disk hotplug (disk link, drive uri)
2173 @type seq: int
2174 @param seq: the index of the device from master perspective
2175 @raise RPCFail: in case instance does not have KVM hypervisor
2176
2177 """
2178 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2179 try:
2180 hyper.VerifyHotplugSupport(instance, action, dev_type)
2181 except errors.HotplugError, err:
2182 _Fail("Hotplug is not supported: %s", err)
2183
2184 if action == constants.HOTPLUG_ACTION_ADD:
2185 fn = hyper.HotAddDevice
2186 elif action == constants.HOTPLUG_ACTION_REMOVE:
2187 fn = hyper.HotDelDevice
2188 elif action == constants.HOTPLUG_ACTION_MODIFY:
2189 fn = hyper.HotModDevice
2190 else:
2191 assert action in constants.HOTPLUG_ALL_ACTIONS
2192
2193 return fn(instance, dev_type, device, extra, seq)
2194
2205
2206
2207 -def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
2208 """Creates a block device for an instance.
2209
2210 @type disk: L{objects.Disk}
2211 @param disk: the object describing the disk we should create
2212 @type size: int
2213 @param size: the size of the physical underlying device, in MiB
2214 @type owner: str
2215 @param owner: the name of the instance for which disk is created,
2216 used for device cache data
2217 @type on_primary: boolean
2218 @param on_primary: indicates if it is the primary node or not
2219 @type info: string
2220 @param info: string that will be sent to the physical device
2221 creation, used for example to set (LVM) tags on LVs
2222 @type excl_stor: boolean
2223 @param excl_stor: Whether exclusive_storage is active
2224
2225 @return: the new unique_id of the device (this can sometime be
2226 computed only after creation), or None. On secondary nodes,
2227 it's not required to return anything.
2228
2229 """
2230
2231
2232 clist = []
2233 if disk.children:
2234 for child in disk.children:
2235 try:
2236 crdev = _RecursiveAssembleBD(child, owner, on_primary)
2237 except errors.BlockDeviceError, err:
2238 _Fail("Can't assemble device %s: %s", child, err)
2239 if on_primary or disk.AssembleOnSecondary():
2240
2241
2242 try:
2243
2244 crdev.Open()
2245 except errors.BlockDeviceError, err:
2246 _Fail("Can't make child '%s' read-write: %s", child, err)
2247 clist.append(crdev)
2248
2249 try:
2250 device = bdev.Create(disk, clist, excl_stor)
2251 except errors.BlockDeviceError, err:
2252 _Fail("Can't create block device: %s", err)
2253
2254 if on_primary or disk.AssembleOnSecondary():
2255 try:
2256 device.Assemble()
2257 except errors.BlockDeviceError, err:
2258 _Fail("Can't assemble device after creation, unusual event: %s", err)
2259 if on_primary or disk.OpenOnSecondary():
2260 try:
2261 device.Open(force=True)
2262 except errors.BlockDeviceError, err:
2263 _Fail("Can't make device r/w after creation, unusual event: %s", err)
2264 DevCacheManager.UpdateCache(device.dev_path, owner,
2265 on_primary, disk.iv_name)
2266
2267 device.SetInfo(info)
2268
2269 return device.unique_id
2270
2273 """This function actually wipes the device.
2274
2275 @param path: The path to the device to wipe
2276 @param offset: The offset in MiB in the file
2277 @param size: The size in MiB to write
2278
2279 """
2280
2281
2282
2283 block_size = 1024 * 1024
2284
2285 cmd = [constants.DD_CMD, "if=/dev/zero", "seek=%d" % offset,
2286 "bs=%s" % block_size, "oflag=direct", "of=%s" % path,
2287 "count=%d" % size]
2288 result = utils.RunCmd(cmd)
2289
2290 if result.failed:
2291 _Fail("Wipe command '%s' exited with error: %s; output: %s", result.cmd,
2292 result.fail_reason, result.output)
2293
2296 """Wipes a block device.
2297
2298 @type disk: L{objects.Disk}
2299 @param disk: the disk object we want to wipe
2300 @type offset: int
2301 @param offset: The offset in MiB in the file
2302 @type size: int
2303 @param size: The size in MiB to write
2304
2305 """
2306 try:
2307 rdev = _RecursiveFindBD(disk)
2308 except errors.BlockDeviceError:
2309 rdev = None
2310
2311 if not rdev:
2312 _Fail("Cannot execute wipe for device %s: device not found", disk.iv_name)
2313
2314
2315 if offset < 0:
2316 _Fail("Negative offset")
2317 if size < 0:
2318 _Fail("Negative size")
2319 if offset > rdev.size:
2320 _Fail("Offset is bigger than device size")
2321 if (offset + size) > rdev.size:
2322 _Fail("The provided offset and size to wipe is bigger than device size")
2323
2324 _WipeDevice(rdev.dev_path, offset, size)
2325
2328 """Pause or resume the sync of the block device.
2329
2330 @type disks: list of L{objects.Disk}
2331 @param disks: the disks object we want to pause/resume
2332 @type pause: bool
2333 @param pause: Wheater to pause or resume
2334
2335 """
2336 success = []
2337 for disk in disks:
2338 try:
2339 rdev = _RecursiveFindBD(disk)
2340 except errors.BlockDeviceError:
2341 rdev = None
2342
2343 if not rdev:
2344 success.append((False, ("Cannot change sync for device %s:"
2345 " device not found" % disk.iv_name)))
2346 continue
2347
2348 result = rdev.PauseResumeSync(pause)
2349
2350 if result:
2351 success.append((result, None))
2352 else:
2353 if pause:
2354 msg = "Pause"
2355 else:
2356 msg = "Resume"
2357 success.append((result, "%s for device %s failed" % (msg, disk.iv_name)))
2358
2359 return success
2360
2363 """Remove a block device.
2364
2365 @note: This is intended to be called recursively.
2366
2367 @type disk: L{objects.Disk}
2368 @param disk: the disk object we should remove
2369 @rtype: boolean
2370 @return: the success of the operation
2371
2372 """
2373 msgs = []
2374 try:
2375 rdev = _RecursiveFindBD(disk)
2376 except errors.BlockDeviceError, err:
2377
2378 logging.info("Can't attach to device %s in remove", disk)
2379 rdev = None
2380 if rdev is not None:
2381 r_path = rdev.dev_path
2382
2383 def _TryRemove():
2384 try:
2385 rdev.Remove()
2386 return []
2387 except errors.BlockDeviceError, err:
2388 return [str(err)]
2389
2390 msgs.extend(utils.SimpleRetry([], _TryRemove,
2391 constants.DISK_REMOVE_RETRY_INTERVAL,
2392 constants.DISK_REMOVE_RETRY_TIMEOUT))
2393
2394 if not msgs:
2395 DevCacheManager.RemoveCache(r_path)
2396
2397 if disk.children:
2398 for child in disk.children:
2399 try:
2400 BlockdevRemove(child)
2401 except RPCFail, err:
2402 msgs.append(str(err))
2403
2404 if msgs:
2405 _Fail("; ".join(msgs))
2406
2409 """Activate a block device for an instance.
2410
2411 This is run on the primary and secondary nodes for an instance.
2412
2413 @note: this function is called recursively.
2414
2415 @type disk: L{objects.Disk}
2416 @param disk: the disk we try to assemble
2417 @type owner: str
2418 @param owner: the name of the instance which owns the disk
2419 @type as_primary: boolean
2420 @param as_primary: if we should make the block device
2421 read/write
2422
2423 @return: the assembled device or None (in case no device
2424 was assembled)
2425 @raise errors.BlockDeviceError: in case there is an error
2426 during the activation of the children or the device
2427 itself
2428
2429 """
2430 children = []
2431 if disk.children:
2432 mcn = disk.ChildrenNeeded()
2433 if mcn == -1:
2434 mcn = 0
2435 else:
2436 mcn = len(disk.children) - mcn
2437 for chld_disk in disk.children:
2438 try:
2439 cdev = _RecursiveAssembleBD(chld_disk, owner, as_primary)
2440 except errors.BlockDeviceError, err:
2441 if children.count(None) >= mcn:
2442 raise
2443 cdev = None
2444 logging.error("Error in child activation (but continuing): %s",
2445 str(err))
2446 children.append(cdev)
2447
2448 if as_primary or disk.AssembleOnSecondary():
2449 r_dev = bdev.Assemble(disk, children)
2450 result = r_dev
2451 if as_primary or disk.OpenOnSecondary():
2452 r_dev.Open()
2453 DevCacheManager.UpdateCache(r_dev.dev_path, owner,
2454 as_primary, disk.iv_name)
2455
2456 else:
2457 result = True
2458 return result
2459
2462 """Activate a block device for an instance.
2463
2464 This is a wrapper over _RecursiveAssembleBD.
2465
2466 @rtype: str or boolean
2467 @return: a tuple with the C{/dev/...} path and the created symlink
2468 for primary nodes, and (C{True}, C{True}) for secondary nodes
2469
2470 """
2471 try:
2472 result = _RecursiveAssembleBD(disk, instance.name, as_primary)
2473 if isinstance(result, BlockDev):
2474
2475 dev_path = result.dev_path
2476 link_name = None
2477 uri = None
2478 if as_primary:
2479 link_name = _SymlinkBlockDev(instance.name, dev_path, idx)
2480 uri = _CalculateDeviceURI(instance, disk, result)
2481 elif result:
2482 return result, result
2483 else:
2484 _Fail("Unexpected result from _RecursiveAssembleBD")
2485 except errors.BlockDeviceError, err:
2486 _Fail("Error while assembling disk: %s", err, exc=True)
2487 except OSError, err:
2488 _Fail("Error while symlinking disk: %s", err, exc=True)
2489
2490 return dev_path, link_name, uri
2491
2494 """Shut down a block device.
2495
2496 First, if the device is assembled (Attach() is successful), then
2497 the device is shutdown. Then the children of the device are
2498 shutdown.
2499
2500 This function is called recursively. Note that we don't cache the
2501 children or such, as oppossed to assemble, shutdown of different
2502 devices doesn't require that the upper device was active.
2503
2504 @type disk: L{objects.Disk}
2505 @param disk: the description of the disk we should
2506 shutdown
2507 @rtype: None
2508
2509 """
2510 msgs = []
2511 r_dev = _RecursiveFindBD(disk)
2512 if r_dev is not None:
2513 r_path = r_dev.dev_path
2514 try:
2515 r_dev.Shutdown()
2516 DevCacheManager.RemoveCache(r_path)
2517 except errors.BlockDeviceError, err:
2518 msgs.append(str(err))
2519
2520 if disk.children:
2521 for child in disk.children:
2522 try:
2523 BlockdevShutdown(child)
2524 except RPCFail, err:
2525 msgs.append(str(err))
2526
2527 if msgs:
2528 _Fail("; ".join(msgs))
2529
2532 """Extend a mirrored block device.
2533
2534 @type parent_cdev: L{objects.Disk}
2535 @param parent_cdev: the disk to which we should add children
2536 @type new_cdevs: list of L{objects.Disk}
2537 @param new_cdevs: the list of children which we should add
2538 @rtype: None
2539
2540 """
2541 parent_bdev = _RecursiveFindBD(parent_cdev)
2542 if parent_bdev is None:
2543 _Fail("Can't find parent device '%s' in add children", parent_cdev)
2544 new_bdevs = [_RecursiveFindBD(disk) for disk in new_cdevs]
2545 if new_bdevs.count(None) > 0:
2546 _Fail("Can't find new device(s) to add: %s:%s", new_bdevs, new_cdevs)
2547 parent_bdev.AddChildren(new_bdevs)
2548
2551 """Shrink a mirrored block device.
2552
2553 @type parent_cdev: L{objects.Disk}
2554 @param parent_cdev: the disk from which we should remove children
2555 @type new_cdevs: list of L{objects.Disk}
2556 @param new_cdevs: the list of children which we should remove
2557 @rtype: None
2558
2559 """
2560 parent_bdev = _RecursiveFindBD(parent_cdev)
2561 if parent_bdev is None:
2562 _Fail("Can't find parent device '%s' in remove children", parent_cdev)
2563 devs = []
2564 for disk in new_cdevs:
2565 rpath = disk.StaticDevPath()
2566 if rpath is None:
2567 bd = _RecursiveFindBD(disk)
2568 if bd is None:
2569 _Fail("Can't find device %s while removing children", disk)
2570 else:
2571 devs.append(bd.dev_path)
2572 else:
2573 if not utils.IsNormAbsPath(rpath):
2574 _Fail("Strange path returned from StaticDevPath: '%s'", rpath)
2575 devs.append(rpath)
2576 parent_bdev.RemoveChildren(devs)
2577
2580 """Get the mirroring status of a list of devices.
2581
2582 @type disks: list of L{objects.Disk}
2583 @param disks: the list of disks which we should query
2584 @rtype: disk
2585 @return: List of L{objects.BlockDevStatus}, one for each disk
2586 @raise errors.BlockDeviceError: if any of the disks cannot be
2587 found
2588
2589 """
2590 stats = []
2591 for dsk in disks:
2592 rbd = _RecursiveFindBD(dsk)
2593 if rbd is None:
2594 _Fail("Can't find device %s", dsk)
2595
2596 stats.append(rbd.CombinedSyncStatus())
2597
2598 return stats
2599
2602 """Get the mirroring status of a list of devices.
2603
2604 @type disks: list of L{objects.Disk}
2605 @param disks: the list of disks which we should query
2606 @rtype: disk
2607 @return: List of tuples, (bool, status), one for each disk; bool denotes
2608 success/failure, status is L{objects.BlockDevStatus} on success, string
2609 otherwise
2610
2611 """
2612 result = []
2613 for disk in disks:
2614 try:
2615 rbd = _RecursiveFindBD(disk)
2616 if rbd is None:
2617 result.append((False, "Can't find device %s" % disk))
2618 continue
2619
2620 status = rbd.CombinedSyncStatus()
2621 except errors.BlockDeviceError, err:
2622 logging.exception("Error while getting disk status")
2623 result.append((False, str(err)))
2624 else:
2625 result.append((True, status))
2626
2627 assert len(disks) == len(result)
2628
2629 return result
2630
2633 """Check if a device is activated.
2634
2635 If so, return information about the real device.
2636
2637 @type disk: L{objects.Disk}
2638 @param disk: the disk object we need to find
2639
2640 @return: None if the device can't be found,
2641 otherwise the device instance
2642
2643 """
2644 children = []
2645 if disk.children:
2646 for chdisk in disk.children:
2647 children.append(_RecursiveFindBD(chdisk))
2648
2649 return bdev.FindDevice(disk, children)
2650
2653 """Opens the underlying block device of a disk.
2654
2655 @type disk: L{objects.Disk}
2656 @param disk: the disk object we want to open
2657
2658 """
2659 real_disk = _RecursiveFindBD(disk)
2660 if real_disk is None:
2661 _Fail("Block device '%s' is not set up", disk)
2662
2663 real_disk.Open()
2664
2665 return real_disk
2666
2669 """Check if a device is activated.
2670
2671 If it is, return information about the real device.
2672
2673 @type disk: L{objects.Disk}
2674 @param disk: the disk to find
2675 @rtype: None or objects.BlockDevStatus
2676 @return: None if the disk cannot be found, otherwise a the current
2677 information
2678
2679 """
2680 try:
2681 rbd = _RecursiveFindBD(disk)
2682 except errors.BlockDeviceError, err:
2683 _Fail("Failed to find device: %s", err, exc=True)
2684
2685 if rbd is None:
2686 return None
2687
2688 return rbd.GetSyncStatus()
2689
2692 """Computes the size of the given disks.
2693
2694 If a disk is not found, returns None instead.
2695
2696 @type disks: list of L{objects.Disk}
2697 @param disks: the list of disk to compute the size for
2698 @rtype: list
2699 @return: list with elements None if the disk cannot be found,
2700 otherwise the pair (size, spindles), where spindles is None if the
2701 device doesn't support that
2702
2703 """
2704 result = []
2705 for cf in disks:
2706 try:
2707 rbd = _RecursiveFindBD(cf)
2708 except errors.BlockDeviceError:
2709 result.append(None)
2710 continue
2711 if rbd is None:
2712 result.append(None)
2713 else:
2714 result.append(rbd.GetActualDimensions())
2715 return result
2716
2717
2718 -def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
2719 """Write a file to the filesystem.
2720
2721 This allows the master to overwrite(!) a file. It will only perform
2722 the operation if the file belongs to a list of configuration files.
2723
2724 @type file_name: str
2725 @param file_name: the target file name
2726 @type data: str
2727 @param data: the new contents of the file
2728 @type mode: int
2729 @param mode: the mode to give the file (can be None)
2730 @type uid: string
2731 @param uid: the owner of the file
2732 @type gid: string
2733 @param gid: the group of the file
2734 @type atime: float
2735 @param atime: the atime to set on the file (can be None)
2736 @type mtime: float
2737 @param mtime: the mtime to set on the file (can be None)
2738 @rtype: None
2739
2740 """
2741 file_name = vcluster.LocalizeVirtualPath(file_name)
2742
2743 if not os.path.isabs(file_name):
2744 _Fail("Filename passed to UploadFile is not absolute: '%s'", file_name)
2745
2746 if file_name not in _ALLOWED_UPLOAD_FILES:
2747 _Fail("Filename passed to UploadFile not in allowed upload targets: '%s'",
2748 file_name)
2749
2750 raw_data = _Decompress(data)
2751
2752 if not (isinstance(uid, basestring) and isinstance(gid, basestring)):
2753 _Fail("Invalid username/groupname type")
2754
2755 getents = runtime.GetEnts()
2756 uid = getents.LookupUser(uid)
2757 gid = getents.LookupGroup(gid)
2758
2759 utils.SafeWriteFile(file_name, None,
2760 data=raw_data, mode=mode, uid=uid, gid=gid,
2761 atime=atime, mtime=mtime)
2762
2763
2764 -def RunOob(oob_program, command, node, timeout):
2765 """Executes oob_program with given command on given node.
2766
2767 @param oob_program: The path to the executable oob_program
2768 @param command: The command to invoke on oob_program
2769 @param node: The node given as an argument to the program
2770 @param timeout: Timeout after which we kill the oob program
2771
2772 @return: stdout
2773 @raise RPCFail: If execution fails for some reason
2774
2775 """
2776 result = utils.RunCmd([oob_program, command, node], timeout=timeout)
2777
2778 if result.failed:
2779 _Fail("'%s' failed with reason '%s'; output: %s", result.cmd,
2780 result.fail_reason, result.output)
2781
2782 return result.stdout
2783
2786 """Compute and return the API version of a given OS.
2787
2788 This function will try to read the API version of the OS residing in
2789 the 'os_dir' directory.
2790
2791 @type os_dir: str
2792 @param os_dir: the directory in which we should look for the OS
2793 @rtype: tuple
2794 @return: tuple (status, data) with status denoting the validity and
2795 data holding either the vaid versions or an error message
2796
2797 """
2798 api_file = utils.PathJoin(os_dir, constants.OS_API_FILE)
2799
2800 try:
2801 st = os.stat(api_file)
2802 except EnvironmentError, err:
2803 return False, ("Required file '%s' not found under path %s: %s" %
2804 (constants.OS_API_FILE, os_dir, utils.ErrnoOrStr(err)))
2805
2806 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
2807 return False, ("File '%s' in %s is not a regular file" %
2808 (constants.OS_API_FILE, os_dir))
2809
2810 try:
2811 api_versions = utils.ReadFile(api_file).splitlines()
2812 except EnvironmentError, err:
2813 return False, ("Error while reading the API version file at %s: %s" %
2814 (api_file, utils.ErrnoOrStr(err)))
2815
2816 try:
2817 api_versions = [int(version.strip()) for version in api_versions]
2818 except (TypeError, ValueError), err:
2819 return False, ("API version(s) can't be converted to integer: %s" %
2820 str(err))
2821
2822 return True, api_versions
2823
2826 """Compute the validity for all OSes.
2827
2828 @type top_dirs: list
2829 @param top_dirs: the list of directories in which to
2830 search (if not given defaults to
2831 L{pathutils.OS_SEARCH_PATH})
2832 @rtype: list of L{objects.OS}
2833 @return: a list of tuples (name, path, status, diagnose, variants,
2834 parameters, api_version) for all (potential) OSes under all
2835 search paths, where:
2836 - name is the (potential) OS name
2837 - path is the full path to the OS
2838 - status True/False is the validity of the OS
2839 - diagnose is the error message for an invalid OS, otherwise empty
2840 - variants is a list of supported OS variants, if any
2841 - parameters is a list of (name, help) parameters, if any
2842 - api_version is a list of support OS API versions
2843
2844 """
2845 if top_dirs is None:
2846 top_dirs = pathutils.OS_SEARCH_PATH
2847
2848 result = []
2849 for dir_name in top_dirs:
2850 if os.path.isdir(dir_name):
2851 try:
2852 f_names = utils.ListVisibleFiles(dir_name)
2853 except EnvironmentError, err:
2854 logging.exception("Can't list the OS directory %s: %s", dir_name, err)
2855 break
2856 for name in f_names:
2857 os_path = utils.PathJoin(dir_name, name)
2858 status, os_inst = _TryOSFromDisk(name, base_dir=dir_name)
2859 if status:
2860 diagnose = ""
2861 variants = os_inst.supported_variants
2862 parameters = os_inst.supported_parameters
2863 api_versions = os_inst.api_versions
2864 else:
2865 diagnose = os_inst
2866 variants = parameters = api_versions = []
2867 result.append((name, os_path, status, diagnose, variants,
2868 parameters, api_versions))
2869
2870 return result
2871
2874 """Create an OS instance from disk.
2875
2876 This function will return an OS instance if the given name is a
2877 valid OS name.
2878
2879 @type base_dir: string
2880 @keyword base_dir: Base directory containing OS installations.
2881 Defaults to a search in all the OS_SEARCH_PATH dirs.
2882 @rtype: tuple
2883 @return: success and either the OS instance if we find a valid one,
2884 or error message
2885
2886 """
2887 if base_dir is None:
2888 os_dir = utils.FindFile(name, pathutils.OS_SEARCH_PATH, os.path.isdir)
2889 else:
2890 os_dir = utils.FindFile(name, [base_dir], os.path.isdir)
2891
2892 if os_dir is None:
2893 return False, "Directory for OS %s not found in search path" % name
2894
2895 status, api_versions = _OSOndiskAPIVersion(os_dir)
2896 if not status:
2897
2898 return status, api_versions
2899
2900 if not constants.OS_API_VERSIONS.intersection(api_versions):
2901 return False, ("API version mismatch for path '%s': found %s, want %s." %
2902 (os_dir, api_versions, constants.OS_API_VERSIONS))
2903
2904
2905
2906
2907 os_files = dict.fromkeys(constants.OS_SCRIPTS, True)
2908
2909 if max(api_versions) >= constants.OS_API_V15:
2910 os_files[constants.OS_VARIANTS_FILE] = False
2911
2912 if max(api_versions) >= constants.OS_API_V20:
2913 os_files[constants.OS_PARAMETERS_FILE] = True
2914 else:
2915 del os_files[constants.OS_SCRIPT_VERIFY]
2916
2917 for (filename, required) in os_files.items():
2918 os_files[filename] = utils.PathJoin(os_dir, filename)
2919
2920 try:
2921 st = os.stat(os_files[filename])
2922 except EnvironmentError, err:
2923 if err.errno == errno.ENOENT and not required:
2924 del os_files[filename]
2925 continue
2926 return False, ("File '%s' under path '%s' is missing (%s)" %
2927 (filename, os_dir, utils.ErrnoOrStr(err)))
2928
2929 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
2930 return False, ("File '%s' under path '%s' is not a regular file" %
2931 (filename, os_dir))
2932
2933 if filename in constants.OS_SCRIPTS:
2934 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
2935 return False, ("File '%s' under path '%s' is not executable" %
2936 (filename, os_dir))
2937
2938 variants = []
2939 if constants.OS_VARIANTS_FILE in os_files:
2940 variants_file = os_files[constants.OS_VARIANTS_FILE]
2941 try:
2942 variants = \
2943 utils.FilterEmptyLinesAndComments(utils.ReadFile(variants_file))
2944 except EnvironmentError, err:
2945
2946 if err.errno != errno.ENOENT:
2947 return False, ("Error while reading the OS variants file at %s: %s" %
2948 (variants_file, utils.ErrnoOrStr(err)))
2949
2950 parameters = []
2951 if constants.OS_PARAMETERS_FILE in os_files:
2952 parameters_file = os_files[constants.OS_PARAMETERS_FILE]
2953 try:
2954 parameters = utils.ReadFile(parameters_file).splitlines()
2955 except EnvironmentError, err:
2956 return False, ("Error while reading the OS parameters file at %s: %s" %
2957 (parameters_file, utils.ErrnoOrStr(err)))
2958 parameters = [v.split(None, 1) for v in parameters]
2959
2960 os_obj = objects.OS(name=name, path=os_dir,
2961 create_script=os_files[constants.OS_SCRIPT_CREATE],
2962 export_script=os_files[constants.OS_SCRIPT_EXPORT],
2963 import_script=os_files[constants.OS_SCRIPT_IMPORT],
2964 rename_script=os_files[constants.OS_SCRIPT_RENAME],
2965 verify_script=os_files.get(constants.OS_SCRIPT_VERIFY,
2966 None),
2967 supported_variants=variants,
2968 supported_parameters=parameters,
2969 api_versions=api_versions)
2970 return True, os_obj
2971
2974 """Create an OS instance from disk.
2975
2976 This function will return an OS instance if the given name is a
2977 valid OS name. Otherwise, it will raise an appropriate
2978 L{RPCFail} exception, detailing why this is not a valid OS.
2979
2980 This is just a wrapper over L{_TryOSFromDisk}, which doesn't raise
2981 an exception but returns true/false status data.
2982
2983 @type base_dir: string
2984 @keyword base_dir: Base directory containing OS installations.
2985 Defaults to a search in all the OS_SEARCH_PATH dirs.
2986 @rtype: L{objects.OS}
2987 @return: the OS instance if we find a valid one
2988 @raise RPCFail: if we don't find a valid OS
2989
2990 """
2991 name_only = objects.OS.GetName(name)
2992 status, payload = _TryOSFromDisk(name_only, base_dir)
2993
2994 if not status:
2995 _Fail(payload)
2996
2997 return payload
2998
2999
3000 -def OSCoreEnv(os_name, inst_os, os_params, debug=0):
3001 """Calculate the basic environment for an os script.
3002
3003 @type os_name: str
3004 @param os_name: full operating system name (including variant)
3005 @type inst_os: L{objects.OS}
3006 @param inst_os: operating system for which the environment is being built
3007 @type os_params: dict
3008 @param os_params: the OS parameters
3009 @type debug: integer
3010 @param debug: debug level (0 or 1, for OS Api 10)
3011 @rtype: dict
3012 @return: dict of environment variables
3013 @raise errors.BlockDeviceError: if the block device
3014 cannot be found
3015
3016 """
3017 result = {}
3018 api_version = \
3019 max(constants.OS_API_VERSIONS.intersection(inst_os.api_versions))
3020 result["OS_API_VERSION"] = "%d" % api_version
3021 result["OS_NAME"] = inst_os.name
3022 result["DEBUG_LEVEL"] = "%d" % debug
3023
3024
3025 if api_version >= constants.OS_API_V15 and inst_os.supported_variants:
3026 variant = objects.OS.GetVariant(os_name)
3027 if not variant:
3028 variant = inst_os.supported_variants[0]
3029 else:
3030 variant = ""
3031 result["OS_VARIANT"] = variant
3032
3033
3034 for pname, pvalue in os_params.items():
3035 result["OSP_%s" % pname.upper()] = pvalue
3036
3037
3038
3039
3040 result["PATH"] = constants.HOOKS_PATH
3041
3042 return result
3043
3046 """Calculate the environment for an os script.
3047
3048 @type instance: L{objects.Instance}
3049 @param instance: target instance for the os script run
3050 @type inst_os: L{objects.OS}
3051 @param inst_os: operating system for which the environment is being built
3052 @type debug: integer
3053 @param debug: debug level (0 or 1, for OS Api 10)
3054 @rtype: dict
3055 @return: dict of environment variables
3056 @raise errors.BlockDeviceError: if the block device
3057 cannot be found
3058
3059 """
3060 result = OSCoreEnv(instance.os, inst_os, instance.osparams, debug=debug)
3061
3062 for attr in ["name", "os", "uuid", "ctime", "mtime", "primary_node"]:
3063 result["INSTANCE_%s" % attr.upper()] = str(getattr(instance, attr))
3064
3065 result["HYPERVISOR"] = instance.hypervisor
3066 result["DISK_COUNT"] = "%d" % len(instance.disks)
3067 result["NIC_COUNT"] = "%d" % len(instance.nics)
3068 result["INSTANCE_SECONDARY_NODES"] = \
3069 ("%s" % " ".join(instance.secondary_nodes))
3070
3071
3072 for idx, disk in enumerate(instance.disks):
3073 real_disk = _OpenRealBD(disk)
3074 result["DISK_%d_PATH" % idx] = real_disk.dev_path
3075 result["DISK_%d_ACCESS" % idx] = disk.mode
3076 result["DISK_%d_UUID" % idx] = disk.uuid
3077 if disk.name:
3078 result["DISK_%d_NAME" % idx] = disk.name
3079 if constants.HV_DISK_TYPE in instance.hvparams:
3080 result["DISK_%d_FRONTEND_TYPE" % idx] = \
3081 instance.hvparams[constants.HV_DISK_TYPE]
3082 if disk.dev_type in constants.DTS_BLOCK:
3083 result["DISK_%d_BACKEND_TYPE" % idx] = "block"
3084 elif disk.dev_type in constants.DTS_FILEBASED:
3085 result["DISK_%d_BACKEND_TYPE" % idx] = \
3086 "file:%s" % disk.logical_id[0]
3087
3088
3089 for idx, nic in enumerate(instance.nics):
3090 result["NIC_%d_MAC" % idx] = nic.mac
3091 result["NIC_%d_UUID" % idx] = nic.uuid
3092 if nic.name:
3093 result["NIC_%d_NAME" % idx] = nic.name
3094 if nic.ip:
3095 result["NIC_%d_IP" % idx] = nic.ip
3096 result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE]
3097 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3098 result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK]
3099 if nic.nicparams[constants.NIC_LINK]:
3100 result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK]
3101 if nic.netinfo:
3102 nobj = objects.Network.FromDict(nic.netinfo)
3103 result.update(nobj.HooksDict("NIC_%d_" % idx))
3104 if constants.HV_NIC_TYPE in instance.hvparams:
3105 result["NIC_%d_FRONTEND_TYPE" % idx] = \
3106 instance.hvparams[constants.HV_NIC_TYPE]
3107
3108
3109 for source, kind in [(instance.beparams, "BE"), (instance.hvparams, "HV")]:
3110 for key, value in source.items():
3111 result["INSTANCE_%s_%s" % (kind, key)] = str(value)
3112
3113 return result
3114
3117 """Compute the validity for all ExtStorage Providers.
3118
3119 @type top_dirs: list
3120 @param top_dirs: the list of directories in which to
3121 search (if not given defaults to
3122 L{pathutils.ES_SEARCH_PATH})
3123 @rtype: list of L{objects.ExtStorage}
3124 @return: a list of tuples (name, path, status, diagnose, parameters)
3125 for all (potential) ExtStorage Providers under all
3126 search paths, where:
3127 - name is the (potential) ExtStorage Provider
3128 - path is the full path to the ExtStorage Provider
3129 - status True/False is the validity of the ExtStorage Provider
3130 - diagnose is the error message for an invalid ExtStorage Provider,
3131 otherwise empty
3132 - parameters is a list of (name, help) parameters, if any
3133
3134 """
3135 if top_dirs is None:
3136 top_dirs = pathutils.ES_SEARCH_PATH
3137
3138 result = []
3139 for dir_name in top_dirs:
3140 if os.path.isdir(dir_name):
3141 try:
3142 f_names = utils.ListVisibleFiles(dir_name)
3143 except EnvironmentError, err:
3144 logging.exception("Can't list the ExtStorage directory %s: %s",
3145 dir_name, err)
3146 break
3147 for name in f_names:
3148 es_path = utils.PathJoin(dir_name, name)
3149 status, es_inst = bdev.ExtStorageFromDisk(name, base_dir=dir_name)
3150 if status:
3151 diagnose = ""
3152 parameters = es_inst.supported_parameters
3153 else:
3154 diagnose = es_inst
3155 parameters = []
3156 result.append((name, es_path, status, diagnose, parameters))
3157
3158 return result
3159
3160
3161 -def BlockdevGrow(disk, amount, dryrun, backingstore, excl_stor):
3162 """Grow a stack of block devices.
3163
3164 This function is called recursively, with the childrens being the
3165 first ones to resize.
3166
3167 @type disk: L{objects.Disk}
3168 @param disk: the disk to be grown
3169 @type amount: integer
3170 @param amount: the amount (in mebibytes) to grow with
3171 @type dryrun: boolean
3172 @param dryrun: whether to execute the operation in simulation mode
3173 only, without actually increasing the size
3174 @param backingstore: whether to execute the operation on backing storage
3175 only, or on "logical" storage only; e.g. DRBD is logical storage,
3176 whereas LVM, file, RBD are backing storage
3177 @rtype: (status, result)
3178 @type excl_stor: boolean
3179 @param excl_stor: Whether exclusive_storage is active
3180 @return: a tuple with the status of the operation (True/False), and
3181 the errors message if status is False
3182
3183 """
3184 r_dev = _RecursiveFindBD(disk)
3185 if r_dev is None:
3186 _Fail("Cannot find block device %s", disk)
3187
3188 try:
3189 r_dev.Grow(amount, dryrun, backingstore, excl_stor)
3190 except errors.BlockDeviceError, err:
3191 _Fail("Failed to grow block device: %s", err, exc=True)
3192
3195 """Create a snapshot copy of a block device.
3196
3197 This function is called recursively, and the snapshot is actually created
3198 just for the leaf lvm backend device.
3199
3200 @type disk: L{objects.Disk}
3201 @param disk: the disk to be snapshotted
3202 @rtype: string
3203 @return: snapshot disk ID as (vg, lv)
3204
3205 """
3206 if disk.dev_type == constants.DT_DRBD8:
3207 if not disk.children:
3208 _Fail("DRBD device '%s' without backing storage cannot be snapshotted",
3209 disk.unique_id)
3210 return BlockdevSnapshot(disk.children[0])
3211 elif disk.dev_type == constants.DT_PLAIN:
3212 r_dev = _RecursiveFindBD(disk)
3213 if r_dev is not None:
3214
3215
3216 return r_dev.Snapshot(disk.size)
3217 else:
3218 _Fail("Cannot find block device %s", disk)
3219 else:
3220 _Fail("Cannot snapshot non-lvm block device '%s' of type '%s'",
3221 disk.logical_id, disk.dev_type)
3222
3225 """Sets 'metadata' information on block devices.
3226
3227 This function sets 'info' metadata on block devices. Initial
3228 information is set at device creation; this function should be used
3229 for example after renames.
3230
3231 @type disk: L{objects.Disk}
3232 @param disk: the disk to be grown
3233 @type info: string
3234 @param info: new 'info' metadata
3235 @rtype: (status, result)
3236 @return: a tuple with the status of the operation (True/False), and
3237 the errors message if status is False
3238
3239 """
3240 r_dev = _RecursiveFindBD(disk)
3241 if r_dev is None:
3242 _Fail("Cannot find block device %s", disk)
3243
3244 try:
3245 r_dev.SetInfo(info)
3246 except errors.BlockDeviceError, err:
3247 _Fail("Failed to set information on block device: %s", err, exc=True)
3248
3251 """Write out the export configuration information.
3252
3253 @type instance: L{objects.Instance}
3254 @param instance: the instance which we export, used for
3255 saving configuration
3256 @type snap_disks: list of L{objects.Disk}
3257 @param snap_disks: list of snapshot block devices, which
3258 will be used to get the actual name of the dump file
3259
3260 @rtype: None
3261
3262 """
3263 destdir = utils.PathJoin(pathutils.EXPORT_DIR, instance.name + ".new")
3264 finaldestdir = utils.PathJoin(pathutils.EXPORT_DIR, instance.name)
3265
3266 config = objects.SerializableConfigParser()
3267
3268 config.add_section(constants.INISECT_EXP)
3269 config.set(constants.INISECT_EXP, "version", "0")
3270 config.set(constants.INISECT_EXP, "timestamp", "%d" % int(time.time()))
3271 config.set(constants.INISECT_EXP, "source", instance.primary_node)
3272 config.set(constants.INISECT_EXP, "os", instance.os)
3273 config.set(constants.INISECT_EXP, "compression", "none")
3274
3275 config.add_section(constants.INISECT_INS)
3276 config.set(constants.INISECT_INS, "name", instance.name)
3277 config.set(constants.INISECT_INS, "maxmem", "%d" %
3278 instance.beparams[constants.BE_MAXMEM])
3279 config.set(constants.INISECT_INS, "minmem", "%d" %
3280 instance.beparams[constants.BE_MINMEM])
3281
3282 config.set(constants.INISECT_INS, "memory", "%d" %
3283 instance.beparams[constants.BE_MAXMEM])
3284 config.set(constants.INISECT_INS, "vcpus", "%d" %
3285 instance.beparams[constants.BE_VCPUS])
3286 config.set(constants.INISECT_INS, "disk_template", instance.disk_template)
3287 config.set(constants.INISECT_INS, "hypervisor", instance.hypervisor)
3288 config.set(constants.INISECT_INS, "tags", " ".join(instance.GetTags()))
3289
3290 nic_total = 0
3291 for nic_count, nic in enumerate(instance.nics):
3292 nic_total += 1
3293 config.set(constants.INISECT_INS, "nic%d_mac" %
3294 nic_count, "%s" % nic.mac)
3295 config.set(constants.INISECT_INS, "nic%d_ip" % nic_count, "%s" % nic.ip)
3296 config.set(constants.INISECT_INS, "nic%d_network" % nic_count,
3297 "%s" % nic.network)
3298 config.set(constants.INISECT_INS, "nic%d_name" % nic_count,
3299 "%s" % nic.name)
3300 for param in constants.NICS_PARAMETER_TYPES:
3301 config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param),
3302 "%s" % nic.nicparams.get(param, None))
3303
3304 config.set(constants.INISECT_INS, "nic_count", "%d" % nic_total)
3305
3306 disk_total = 0
3307 for disk_count, disk in enumerate(snap_disks):
3308 if disk:
3309 disk_total += 1
3310 config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count,
3311 ("%s" % disk.iv_name))
3312 config.set(constants.INISECT_INS, "disk%d_dump" % disk_count,
3313 ("%s" % disk.logical_id[1]))
3314 config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
3315 ("%d" % disk.size))
3316 config.set(constants.INISECT_INS, "disk%d_name" % disk_count,
3317 "%s" % disk.name)
3318
3319 config.set(constants.INISECT_INS, "disk_count", "%d" % disk_total)
3320
3321
3322
3323 config.add_section(constants.INISECT_HYP)
3324 for name, value in instance.hvparams.items():
3325 if name not in constants.HVC_GLOBALS:
3326 config.set(constants.INISECT_HYP, name, str(value))
3327
3328 config.add_section(constants.INISECT_BEP)
3329 for name, value in instance.beparams.items():
3330 config.set(constants.INISECT_BEP, name, str(value))
3331
3332 config.add_section(constants.INISECT_OSP)
3333 for name, value in instance.osparams.items():
3334 config.set(constants.INISECT_OSP, name, str(value))
3335
3336 utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE),
3337 data=config.Dumps())
3338 shutil.rmtree(finaldestdir, ignore_errors=True)
3339 shutil.move(destdir, finaldestdir)
3340
3363
3366 """Return a list of exports currently available on this machine.
3367
3368 @rtype: list
3369 @return: list of the exports
3370
3371 """
3372 if os.path.isdir(pathutils.EXPORT_DIR):
3373 return sorted(utils.ListVisibleFiles(pathutils.EXPORT_DIR))
3374 else:
3375 _Fail("No exports directory")
3376
3379 """Remove an existing export from the node.
3380
3381 @type export: str
3382 @param export: the name of the export to remove
3383 @rtype: None
3384
3385 """
3386 target = utils.PathJoin(pathutils.EXPORT_DIR, export)
3387
3388 try:
3389 shutil.rmtree(target)
3390 except EnvironmentError, err:
3391 _Fail("Error while removing the export: %s", err, exc=True)
3392
3395 """Rename a list of block devices.
3396
3397 @type devlist: list of tuples
3398 @param devlist: list of tuples of the form (disk, new_unique_id); disk is
3399 an L{objects.Disk} object describing the current disk, and new
3400 unique_id is the name we rename it to
3401 @rtype: boolean
3402 @return: True if all renames succeeded, False otherwise
3403
3404 """
3405 msgs = []
3406 result = True
3407 for disk, unique_id in devlist:
3408 dev = _RecursiveFindBD(disk)
3409 if dev is None:
3410 msgs.append("Can't find device %s in rename" % str(disk))
3411 result = False
3412 continue
3413 try:
3414 old_rpath = dev.dev_path
3415 dev.Rename(unique_id)
3416 new_rpath = dev.dev_path
3417 if old_rpath != new_rpath:
3418 DevCacheManager.RemoveCache(old_rpath)
3419
3420
3421
3422
3423
3424 except errors.BlockDeviceError, err:
3425 msgs.append("Can't rename device '%s' to '%s': %s" %
3426 (dev, unique_id, err))
3427 logging.exception("Can't rename device '%s' to '%s'", dev, unique_id)
3428 result = False
3429 if not result:
3430 _Fail("; ".join(msgs))
3431
3449
3452 """Create file storage directory.
3453
3454 @type file_storage_dir: str
3455 @param file_storage_dir: directory to create
3456
3457 @rtype: tuple
3458 @return: tuple with first element a boolean indicating wheter dir
3459 creation was successful or not
3460
3461 """
3462 file_storage_dir = _TransformFileStorageDir(file_storage_dir)
3463 if os.path.exists(file_storage_dir):
3464 if not os.path.isdir(file_storage_dir):
3465 _Fail("Specified storage dir '%s' is not a directory",
3466 file_storage_dir)
3467 else:
3468 try:
3469 os.makedirs(file_storage_dir, 0750)
3470 except OSError, err:
3471 _Fail("Cannot create file storage directory '%s': %s",
3472 file_storage_dir, err, exc=True)
3473
3476 """Remove file storage directory.
3477
3478 Remove it only if it's empty. If not log an error and return.
3479
3480 @type file_storage_dir: str
3481 @param file_storage_dir: the directory we should cleanup
3482 @rtype: tuple (success,)
3483 @return: tuple of one element, C{success}, denoting
3484 whether the operation was successful
3485
3486 """
3487 file_storage_dir = _TransformFileStorageDir(file_storage_dir)
3488 if os.path.exists(file_storage_dir):
3489 if not os.path.isdir(file_storage_dir):
3490 _Fail("Specified Storage directory '%s' is not a directory",
3491 file_storage_dir)
3492
3493 try:
3494 os.rmdir(file_storage_dir)
3495 except OSError, err:
3496 _Fail("Cannot remove file storage directory '%s': %s",
3497 file_storage_dir, err)
3498
3501 """Rename the file storage directory.
3502
3503 @type old_file_storage_dir: str
3504 @param old_file_storage_dir: the current path
3505 @type new_file_storage_dir: str
3506 @param new_file_storage_dir: the name we should rename to
3507 @rtype: tuple (success,)
3508 @return: tuple of one element, C{success}, denoting
3509 whether the operation was successful
3510
3511 """
3512 old_file_storage_dir = _TransformFileStorageDir(old_file_storage_dir)
3513 new_file_storage_dir = _TransformFileStorageDir(new_file_storage_dir)
3514 if not os.path.exists(new_file_storage_dir):
3515 if os.path.isdir(old_file_storage_dir):
3516 try:
3517 os.rename(old_file_storage_dir, new_file_storage_dir)
3518 except OSError, err:
3519 _Fail("Cannot rename '%s' to '%s': %s",
3520 old_file_storage_dir, new_file_storage_dir, err)
3521 else:
3522 _Fail("Specified storage dir '%s' is not a directory",
3523 old_file_storage_dir)
3524 else:
3525 if os.path.exists(old_file_storage_dir):
3526 _Fail("Cannot rename '%s' to '%s': both locations exist",
3527 old_file_storage_dir, new_file_storage_dir)
3528
3531 """Checks whether the given filename is in the queue directory.
3532
3533 @type file_name: str
3534 @param file_name: the file name we should check
3535 @rtype: None
3536 @raises RPCFail: if the file is not valid
3537
3538 """
3539 if not utils.IsBelowDir(pathutils.QUEUE_DIR, file_name):
3540 _Fail("Passed job queue file '%s' does not belong to"
3541 " the queue directory '%s'", file_name, pathutils.QUEUE_DIR)
3542
3545 """Updates a file in the queue directory.
3546
3547 This is just a wrapper over L{utils.io.WriteFile}, with proper
3548 checking.
3549
3550 @type file_name: str
3551 @param file_name: the job file name
3552 @type content: str
3553 @param content: the new job contents
3554 @rtype: boolean
3555 @return: the success of the operation
3556
3557 """
3558 file_name = vcluster.LocalizeVirtualPath(file_name)
3559
3560 _EnsureJobQueueFile(file_name)
3561 getents = runtime.GetEnts()
3562
3563
3564 utils.WriteFile(file_name, data=_Decompress(content), uid=getents.masterd_uid,
3565 gid=getents.daemons_gid, mode=constants.JOB_QUEUE_FILES_PERMS)
3566
3569 """Renames a job queue file.
3570
3571 This is just a wrapper over os.rename with proper checking.
3572
3573 @type old: str
3574 @param old: the old (actual) file name
3575 @type new: str
3576 @param new: the desired file name
3577 @rtype: tuple
3578 @return: the success of the operation and payload
3579
3580 """
3581 old = vcluster.LocalizeVirtualPath(old)
3582 new = vcluster.LocalizeVirtualPath(new)
3583
3584 _EnsureJobQueueFile(old)
3585 _EnsureJobQueueFile(new)
3586
3587 getents = runtime.GetEnts()
3588
3589 utils.RenameFile(old, new, mkdir=True, mkdir_mode=0750,
3590 dir_uid=getents.masterd_uid, dir_gid=getents.daemons_gid)
3591
3594 """Closes the given block devices.
3595
3596 This means they will be switched to secondary mode (in case of
3597 DRBD).
3598
3599 @param instance_name: if the argument is not empty, the symlinks
3600 of this instance will be removed
3601 @type disks: list of L{objects.Disk}
3602 @param disks: the list of disks to be closed
3603 @rtype: tuple (success, message)
3604 @return: a tuple of success and message, where success
3605 indicates the succes of the operation, and message
3606 which will contain the error details in case we
3607 failed
3608
3609 """
3610 bdevs = []
3611 for cf in disks:
3612 rd = _RecursiveFindBD(cf)
3613 if rd is None:
3614 _Fail("Can't find device %s", cf)
3615 bdevs.append(rd)
3616
3617 msg = []
3618 for rd in bdevs:
3619 try:
3620 rd.Close()
3621 except errors.BlockDeviceError, err:
3622 msg.append(str(err))
3623 if msg:
3624 _Fail("Can't make devices secondary: %s", ",".join(msg))
3625 else:
3626 if instance_name:
3627 _RemoveBlockDevLinks(instance_name, disks)
3628
3631 """Validates the given hypervisor parameters.
3632
3633 @type hvname: string
3634 @param hvname: the hypervisor name
3635 @type hvparams: dict
3636 @param hvparams: the hypervisor parameters to be validated
3637 @rtype: None
3638
3639 """
3640 try:
3641 hv_type = hypervisor.GetHypervisor(hvname)
3642 hv_type.ValidateParameters(hvparams)
3643 except errors.HypervisorError, err:
3644 _Fail(str(err), log=False)
3645
3648 """Check whether a list of parameters is supported by the OS.
3649
3650 @type os_obj: L{objects.OS}
3651 @param os_obj: OS object to check
3652 @type parameters: list
3653 @param parameters: the list of parameters to check
3654
3655 """
3656 supported = [v[0] for v in os_obj.supported_parameters]
3657 delta = frozenset(parameters).difference(supported)
3658 if delta:
3659 _Fail("The following parameters are not supported"
3660 " by the OS %s: %s" % (os_obj.name, utils.CommaJoin(delta)))
3661
3662
3663 -def ValidateOS(required, osname, checks, osparams):
3664 """Validate the given OS' parameters.
3665
3666 @type required: boolean
3667 @param required: whether absence of the OS should translate into
3668 failure or not
3669 @type osname: string
3670 @param osname: the OS to be validated
3671 @type checks: list
3672 @param checks: list of the checks to run (currently only 'parameters')
3673 @type osparams: dict
3674 @param osparams: dictionary with OS parameters
3675 @rtype: boolean
3676 @return: True if the validation passed, or False if the OS was not
3677 found and L{required} was false
3678
3679 """
3680 if not constants.OS_VALIDATE_CALLS.issuperset(checks):
3681 _Fail("Unknown checks required for OS %s: %s", osname,
3682 set(checks).difference(constants.OS_VALIDATE_CALLS))
3683
3684 name_only = objects.OS.GetName(osname)
3685 status, tbv = _TryOSFromDisk(name_only, None)
3686
3687 if not status:
3688 if required:
3689 _Fail(tbv)
3690 else:
3691 return False
3692
3693 if max(tbv.api_versions) < constants.OS_API_V20:
3694 return True
3695
3696 if constants.OS_VALIDATE_PARAMETERS in checks:
3697 _CheckOSPList(tbv, osparams.keys())
3698
3699 validate_env = OSCoreEnv(osname, tbv, osparams)
3700 result = utils.RunCmd([tbv.verify_script] + checks, env=validate_env,
3701 cwd=tbv.path, reset_env=True)
3702 if result.failed:
3703 logging.error("os validate command '%s' returned error: %s output: %s",
3704 result.cmd, result.fail_reason, result.output)
3705 _Fail("OS validation script failed (%s), output: %s",
3706 result.fail_reason, result.output, log=False)
3707
3708 return True
3709
3732
3741
3744 """Creates a new X509 certificate for SSL/TLS.
3745
3746 @type validity: int
3747 @param validity: Validity in seconds
3748 @rtype: tuple; (string, string)
3749 @return: Certificate name and public part
3750
3751 """
3752 (key_pem, cert_pem) = \
3753 utils.GenerateSelfSignedX509Cert(netutils.Hostname.GetSysName(),
3754 min(validity, _MAX_SSL_CERT_VALIDITY), 1)
3755
3756 cert_dir = tempfile.mkdtemp(dir=cryptodir,
3757 prefix="x509-%s-" % utils.TimestampForFilename())
3758 try:
3759 name = os.path.basename(cert_dir)
3760 assert len(name) > 5
3761
3762 (_, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
3763
3764 utils.WriteFile(key_file, mode=0400, data=key_pem)
3765 utils.WriteFile(cert_file, mode=0400, data=cert_pem)
3766
3767
3768 return (name, cert_pem)
3769 except Exception:
3770 shutil.rmtree(cert_dir, ignore_errors=True)
3771 raise
3772
3775 """Removes a X509 certificate.
3776
3777 @type name: string
3778 @param name: Certificate name
3779
3780 """
3781 (cert_dir, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
3782
3783 utils.RemoveFile(key_file)
3784 utils.RemoveFile(cert_file)
3785
3786 try:
3787 os.rmdir(cert_dir)
3788 except EnvironmentError, err:
3789 _Fail("Cannot remove certificate directory '%s': %s",
3790 cert_dir, err)
3791
3794 """Returns the command for the requested input/output.
3795
3796 @type instance: L{objects.Instance}
3797 @param instance: The instance object
3798 @param mode: Import/export mode
3799 @param ieio: Input/output type
3800 @param ieargs: Input/output arguments
3801
3802 """
3803 assert mode in (constants.IEM_IMPORT, constants.IEM_EXPORT)
3804
3805 env = None
3806 prefix = None
3807 suffix = None
3808 exp_size = None
3809
3810 if ieio == constants.IEIO_FILE:
3811 (filename, ) = ieargs
3812
3813 if not utils.IsNormAbsPath(filename):
3814 _Fail("Path '%s' is not normalized or absolute", filename)
3815
3816 real_filename = os.path.realpath(filename)
3817 directory = os.path.dirname(real_filename)
3818
3819 if not utils.IsBelowDir(pathutils.EXPORT_DIR, real_filename):
3820 _Fail("File '%s' is not under exports directory '%s': %s",
3821 filename, pathutils.EXPORT_DIR, real_filename)
3822
3823
3824 utils.Makedirs(directory, mode=0750)
3825
3826 quoted_filename = utils.ShellQuote(filename)
3827
3828 if mode == constants.IEM_IMPORT:
3829 suffix = "> %s" % quoted_filename
3830 elif mode == constants.IEM_EXPORT:
3831 suffix = "< %s" % quoted_filename
3832
3833
3834 try:
3835 st = os.stat(filename)
3836 except EnvironmentError, err:
3837 logging.error("Can't stat(2) %s: %s", filename, err)
3838 else:
3839 exp_size = utils.BytesToMebibyte(st.st_size)
3840
3841 elif ieio == constants.IEIO_RAW_DISK:
3842 (disk, ) = ieargs
3843
3844 real_disk = _OpenRealBD(disk)
3845
3846 if mode == constants.IEM_IMPORT:
3847
3848
3849 suffix = utils.BuildShellCmd("| dd of=%s conv=nocreat,notrunc bs=%s",
3850 real_disk.dev_path,
3851 str(1024 * 1024))
3852
3853 elif mode == constants.IEM_EXPORT:
3854
3855 prefix = utils.BuildShellCmd("dd if=%s bs=%s count=%s |",
3856 real_disk.dev_path,
3857 str(1024 * 1024),
3858 str(disk.size))
3859 exp_size = disk.size
3860
3861 elif ieio == constants.IEIO_SCRIPT:
3862 (disk, disk_index, ) = ieargs
3863
3864 assert isinstance(disk_index, (int, long))
3865
3866 inst_os = OSFromDisk(instance.os)
3867 env = OSEnvironment(instance, inst_os)
3868
3869 if mode == constants.IEM_IMPORT:
3870 env["IMPORT_DEVICE"] = env["DISK_%d_PATH" % disk_index]
3871 env["IMPORT_INDEX"] = str(disk_index)
3872 script = inst_os.import_script
3873
3874 elif mode == constants.IEM_EXPORT:
3875 real_disk = _OpenRealBD(disk)
3876 env["EXPORT_DEVICE"] = real_disk.dev_path
3877 env["EXPORT_INDEX"] = str(disk_index)
3878 script = inst_os.export_script
3879
3880
3881 script_cmd = utils.BuildShellCmd("( cd %s && %s; )", inst_os.path, script)
3882
3883 if mode == constants.IEM_IMPORT:
3884 suffix = "| %s" % script_cmd
3885
3886 elif mode == constants.IEM_EXPORT:
3887 prefix = "%s |" % script_cmd
3888
3889
3890 exp_size = constants.IE_CUSTOM_SIZE
3891
3892 else:
3893 _Fail("Invalid %s I/O mode %r", mode, ieio)
3894
3895 return (env, prefix, suffix, exp_size)
3896
3899 """Creates status directory for import/export.
3900
3901 """
3902 return tempfile.mkdtemp(dir=pathutils.IMPORT_EXPORT_DIR,
3903 prefix=("%s-%s-" %
3904 (prefix, utils.TimestampForFilename())))
3905
3909 """Starts an import or export daemon.
3910
3911 @param mode: Import/output mode
3912 @type opts: L{objects.ImportExportOptions}
3913 @param opts: Daemon options
3914 @type host: string
3915 @param host: Remote host for export (None for import)
3916 @type port: int
3917 @param port: Remote port for export (None for import)
3918 @type instance: L{objects.Instance}
3919 @param instance: Instance object
3920 @type component: string
3921 @param component: which part of the instance is transferred now,
3922 e.g. 'disk/0'
3923 @param ieio: Input/output type
3924 @param ieioargs: Input/output arguments
3925
3926 """
3927 if mode == constants.IEM_IMPORT:
3928 prefix = "import"
3929
3930 if not (host is None and port is None):
3931 _Fail("Can not specify host or port on import")
3932
3933 elif mode == constants.IEM_EXPORT:
3934 prefix = "export"
3935
3936 if host is None or port is None:
3937 _Fail("Host and port must be specified for an export")
3938
3939 else:
3940 _Fail("Invalid mode %r", mode)
3941
3942 if (opts.key_name is None) ^ (opts.ca_pem is None):
3943 _Fail("Cluster certificate can only be used for both key and CA")
3944
3945 (cmd_env, cmd_prefix, cmd_suffix, exp_size) = \
3946 _GetImportExportIoCommand(instance, mode, ieio, ieioargs)
3947
3948 if opts.key_name is None:
3949
3950 key_path = pathutils.NODED_CERT_FILE
3951 cert_path = pathutils.NODED_CERT_FILE
3952 assert opts.ca_pem is None
3953 else:
3954 (_, key_path, cert_path) = _GetX509Filenames(pathutils.CRYPTO_KEYS_DIR,
3955 opts.key_name)
3956 assert opts.ca_pem is not None
3957
3958 for i in [key_path, cert_path]:
3959 if not os.path.exists(i):
3960 _Fail("File '%s' does not exist" % i)
3961
3962 status_dir = _CreateImportExportStatusDir("%s-%s" % (prefix, component))
3963 try:
3964 status_file = utils.PathJoin(status_dir, _IES_STATUS_FILE)
3965 pid_file = utils.PathJoin(status_dir, _IES_PID_FILE)
3966 ca_file = utils.PathJoin(status_dir, _IES_CA_FILE)
3967
3968 if opts.ca_pem is None:
3969
3970 ca = utils.ReadFile(pathutils.NODED_CERT_FILE)
3971 else:
3972 ca = opts.ca_pem
3973
3974
3975 utils.WriteFile(ca_file, data=ca, mode=0400)
3976
3977 cmd = [
3978 pathutils.IMPORT_EXPORT_DAEMON,
3979 status_file, mode,
3980 "--key=%s" % key_path,
3981 "--cert=%s" % cert_path,
3982 "--ca=%s" % ca_file,
3983 ]
3984
3985 if host:
3986 cmd.append("--host=%s" % host)
3987
3988 if port:
3989 cmd.append("--port=%s" % port)
3990
3991 if opts.ipv6:
3992 cmd.append("--ipv6")
3993 else:
3994 cmd.append("--ipv4")
3995
3996 if opts.compress:
3997 cmd.append("--compress=%s" % opts.compress)
3998
3999 if opts.magic:
4000 cmd.append("--magic=%s" % opts.magic)
4001
4002 if exp_size is not None:
4003 cmd.append("--expected-size=%s" % exp_size)
4004
4005 if cmd_prefix:
4006 cmd.append("--cmd-prefix=%s" % cmd_prefix)
4007
4008 if cmd_suffix:
4009 cmd.append("--cmd-suffix=%s" % cmd_suffix)
4010
4011 if mode == constants.IEM_EXPORT:
4012
4013 cmd.append("--connect-retries=%s" % constants.RIE_CONNECT_RETRIES)
4014 cmd.append("--connect-timeout=%s" % constants.RIE_CONNECT_ATTEMPT_TIMEOUT)
4015 elif opts.connect_timeout is not None:
4016 assert mode == constants.IEM_IMPORT
4017
4018 cmd.append("--connect-timeout=%s" % opts.connect_timeout)
4019
4020 logfile = _InstanceLogName(prefix, instance.os, instance.name, component)
4021
4022
4023
4024 utils.StartDaemon(cmd, env=cmd_env, pidfile=pid_file,
4025 output=logfile)
4026
4027
4028 return os.path.basename(status_dir)
4029
4030 except Exception:
4031 shutil.rmtree(status_dir, ignore_errors=True)
4032 raise
4033
4036 """Returns import/export daemon status.
4037
4038 @type names: sequence
4039 @param names: List of names
4040 @rtype: List of dicts
4041 @return: Returns a list of the state of each named import/export or None if a
4042 status couldn't be read
4043
4044 """
4045 result = []
4046
4047 for name in names:
4048 status_file = utils.PathJoin(pathutils.IMPORT_EXPORT_DIR, name,
4049 _IES_STATUS_FILE)
4050
4051 try:
4052 data = utils.ReadFile(status_file)
4053 except EnvironmentError, err:
4054 if err.errno != errno.ENOENT:
4055 raise
4056 data = None
4057
4058 if not data:
4059 result.append(None)
4060 continue
4061
4062 result.append(serializer.LoadJson(data))
4063
4064 return result
4065
4080
4083 """Cleanup after an import or export.
4084
4085 If the import/export daemon is still running it's killed. Afterwards the
4086 whole status directory is removed.
4087
4088 """
4089 logging.info("Finalizing import/export %s", name)
4090
4091 status_dir = utils.PathJoin(pathutils.IMPORT_EXPORT_DIR, name)
4092
4093 pid = utils.ReadLockedPidFile(utils.PathJoin(status_dir, _IES_PID_FILE))
4094
4095 if pid:
4096 logging.info("Import/export %s is still running with PID %s",
4097 name, pid)
4098 utils.KillProcess(pid, waitpid=False)
4099
4100 shutil.rmtree(status_dir, ignore_errors=True)
4101
4104 """Finds attached L{BlockDev}s for the given disks.
4105
4106 @type disks: list of L{objects.Disk}
4107 @param disks: the disk objects we need to find
4108
4109 @return: list of L{BlockDev} objects or C{None} if a given disk
4110 was not found or was no attached.
4111
4112 """
4113 bdevs = []
4114
4115 for disk in disks:
4116 rd = _RecursiveFindBD(disk)
4117 if rd is None:
4118 _Fail("Can't find device %s", disk)
4119 bdevs.append(rd)
4120 return bdevs
4121
4124 """Disconnects the network on a list of drbd devices.
4125
4126 """
4127 bdevs = _FindDisks(disks)
4128
4129
4130 for rd in bdevs:
4131 try:
4132 rd.DisconnectNet()
4133 except errors.BlockDeviceError, err:
4134 _Fail("Can't change network configuration to standalone mode: %s",
4135 err, exc=True)
4136
4139 """Attaches the network on a list of drbd devices.
4140
4141 """
4142 bdevs = _FindDisks(disks)
4143
4144 if multimaster:
4145 for idx, rd in enumerate(bdevs):
4146 try:
4147 _SymlinkBlockDev(instance_name, rd.dev_path, idx)
4148 except EnvironmentError, err:
4149 _Fail("Can't create symlink: %s", err)
4150
4151
4152 for rd in bdevs:
4153 try:
4154 rd.AttachNet(multimaster)
4155 except errors.BlockDeviceError, err:
4156 _Fail("Can't change network configuration: %s", err)
4157
4158
4159
4160
4161
4162
4163
4164 def _Attach():
4165 all_connected = True
4166
4167 for rd in bdevs:
4168 stats = rd.GetProcStatus()
4169
4170 if multimaster:
4171
4172
4173
4174
4175
4176
4177 all_connected = (all_connected and
4178 stats.is_connected and
4179 stats.is_disk_uptodate and
4180 stats.peer_disk_uptodate)
4181 else:
4182 all_connected = (all_connected and
4183 (stats.is_connected or stats.is_in_resync))
4184
4185 if stats.is_standalone:
4186
4187
4188
4189 try:
4190 rd.AttachNet(multimaster)
4191 except errors.BlockDeviceError, err:
4192 _Fail("Can't change network configuration: %s", err)
4193
4194 if not all_connected:
4195 raise utils.RetryAgain()
4196
4197 try:
4198
4199 utils.Retry(_Attach, (0.1, 1.5, 5.0), 2 * 60)
4200 except utils.RetryTimeout:
4201 _Fail("Timeout in disk reconnecting")
4202
4203 if multimaster:
4204
4205 for rd in bdevs:
4206 try:
4207 rd.Open()
4208 except errors.BlockDeviceError, err:
4209 _Fail("Can't change to primary mode: %s", err)
4210
4213 """Wait until DRBDs have synchronized.
4214
4215 """
4216 def _helper(rd):
4217 stats = rd.GetProcStatus()
4218 if not (stats.is_connected or stats.is_in_resync):
4219 raise utils.RetryAgain()
4220 return stats
4221
4222 bdevs = _FindDisks(disks)
4223
4224 min_resync = 100
4225 alldone = True
4226 for rd in bdevs:
4227 try:
4228
4229 stats = utils.Retry(_helper, 1, 15, args=[rd])
4230 except utils.RetryTimeout:
4231 stats = rd.GetProcStatus()
4232
4233 if not (stats.is_connected or stats.is_in_resync):
4234 _Fail("DRBD device %s is not in sync: stats=%s", rd, stats)
4235 alldone = alldone and (not stats.is_in_resync)
4236 if stats.sync_percent is not None:
4237 min_resync = min(min_resync, stats.sync_percent)
4238
4239 return (alldone, min_resync)
4240
4243 """Checks which of the passed disks needs activation and returns their UUIDs.
4244
4245 """
4246 faulty_disks = []
4247
4248 for disk in disks:
4249 rd = _RecursiveFindBD(disk)
4250 if rd is None:
4251 faulty_disks.append(disk)
4252 continue
4253
4254 stats = rd.GetProcStatus()
4255 if stats.is_standalone or stats.is_diskless:
4256 faulty_disks.append(disk)
4257
4258 return [disk.uuid for disk in faulty_disks]
4259
4269
4272 """Hard-powercycle the node.
4273
4274 Because we need to return first, and schedule the powercycle in the
4275 background, we won't be able to report failures nicely.
4276
4277 """
4278 hyper = hypervisor.GetHypervisor(hypervisor_type)
4279 try:
4280 pid = os.fork()
4281 except OSError:
4282
4283 pid = 0
4284 if pid > 0:
4285 return "Reboot scheduled in 5 seconds"
4286
4287 try:
4288 utils.Mlockall()
4289 except Exception:
4290 pass
4291 time.sleep(5)
4292 hyper.PowercycleNode(hvparams=hvparams)
4293
4296 """Verifies a restricted command name.
4297
4298 @type cmd: string
4299 @param cmd: Command name
4300 @rtype: tuple; (boolean, string or None)
4301 @return: The tuple's first element is the status; if C{False}, the second
4302 element is an error message string, otherwise it's C{None}
4303
4304 """
4305 if not cmd.strip():
4306 return (False, "Missing command name")
4307
4308 if os.path.basename(cmd) != cmd:
4309 return (False, "Invalid command name")
4310
4311 if not constants.EXT_PLUGIN_MASK.match(cmd):
4312 return (False, "Command name contains forbidden characters")
4313
4314 return (True, None)
4315
4318 """Common checks for restricted command file system directories and files.
4319
4320 @type path: string
4321 @param path: Path to check
4322 @param owner: C{None} or tuple containing UID and GID
4323 @rtype: tuple; (boolean, string or C{os.stat} result)
4324 @return: The tuple's first element is the status; if C{False}, the second
4325 element is an error message string, otherwise it's the result of C{os.stat}
4326
4327 """
4328 if owner is None:
4329
4330 owner = (0, 0)
4331
4332 try:
4333 st = os.stat(path)
4334 except EnvironmentError, err:
4335 return (False, "Can't stat(2) '%s': %s" % (path, err))
4336
4337 if stat.S_IMODE(st.st_mode) & (~_RCMD_MAX_MODE):
4338 return (False, "Permissions on '%s' are too permissive" % path)
4339
4340 if (st.st_uid, st.st_gid) != owner:
4341 (owner_uid, owner_gid) = owner
4342 return (False, "'%s' is not owned by %s:%s" % (path, owner_uid, owner_gid))
4343
4344 return (True, st)
4345
4348 """Verifies restricted command directory.
4349
4350 @type path: string
4351 @param path: Path to check
4352 @rtype: tuple; (boolean, string or None)
4353 @return: The tuple's first element is the status; if C{False}, the second
4354 element is an error message string, otherwise it's C{None}
4355
4356 """
4357 (status, value) = _CommonRestrictedCmdCheck(path, _owner)
4358
4359 if not status:
4360 return (False, value)
4361
4362 if not stat.S_ISDIR(value.st_mode):
4363 return (False, "Path '%s' is not a directory" % path)
4364
4365 return (True, None)
4366
4369 """Verifies a whole restricted command and returns its executable filename.
4370
4371 @type path: string
4372 @param path: Directory containing restricted commands
4373 @type cmd: string
4374 @param cmd: Command name
4375 @rtype: tuple; (boolean, string)
4376 @return: The tuple's first element is the status; if C{False}, the second
4377 element is an error message string, otherwise the second element is the
4378 absolute path to the executable
4379
4380 """
4381 executable = utils.PathJoin(path, cmd)
4382
4383 (status, msg) = _CommonRestrictedCmdCheck(executable, _owner)
4384
4385 if not status:
4386 return (False, msg)
4387
4388 if not utils.IsExecutable(executable):
4389 return (False, "access(2) thinks '%s' can't be executed" % executable)
4390
4391 return (True, executable)
4392
4398 """Performs a number of tests on a restricted command.
4399
4400 @type path: string
4401 @param path: Directory containing restricted commands
4402 @type cmd: string
4403 @param cmd: Command name
4404 @return: Same as L{_VerifyRestrictedCmd}
4405
4406 """
4407
4408 (status, msg) = _verify_dir(path)
4409 if status:
4410
4411 (status, msg) = _verify_name(cmd)
4412
4413 if not status:
4414 return (False, msg)
4415
4416
4417 return _verify_cmd(path, cmd)
4418
4428 """Executes a restricted command after performing strict tests.
4429
4430 @type cmd: string
4431 @param cmd: Command name
4432 @rtype: string
4433 @return: Command output
4434 @raise RPCFail: In case of an error
4435
4436 """
4437 logging.info("Preparing to run restricted command '%s'", cmd)
4438
4439 if not _enabled:
4440 _Fail("Restricted commands disabled at configure time")
4441
4442 lock = None
4443 try:
4444 cmdresult = None
4445 try:
4446 lock = utils.FileLock.Open(_lock_file)
4447 lock.Exclusive(blocking=True, timeout=_lock_timeout)
4448
4449 (status, value) = _prepare_fn(_path, cmd)
4450
4451 if status:
4452 cmdresult = _runcmd_fn([value], env={}, reset_env=True,
4453 postfork_fn=lambda _: lock.Unlock())
4454 else:
4455 logging.error(value)
4456 except Exception:
4457
4458 logging.exception("Caught exception")
4459
4460 if cmdresult is None:
4461 logging.info("Sleeping for %0.1f seconds before returning",
4462 _RCMD_INVALID_DELAY)
4463 _sleep_fn(_RCMD_INVALID_DELAY)
4464
4465
4466 _Fail("Executing command '%s' failed" % cmd)
4467 elif cmdresult.failed or cmdresult.fail_reason:
4468 _Fail("Restricted command '%s' failed: %s; output: %s",
4469 cmd, cmdresult.fail_reason, cmdresult.output)
4470 else:
4471 return cmdresult.output
4472 finally:
4473 if lock is not None:
4474
4475 lock.Close()
4476 lock = None
4477
4480 """Creates or removes the watcher pause file.
4481
4482 @type until: None or number
4483 @param until: Unix timestamp saying until when the watcher shouldn't run
4484
4485 """
4486 if until is None:
4487 logging.info("Received request to no longer pause watcher")
4488 utils.RemoveFile(_filename)
4489 else:
4490 logging.info("Received request to pause watcher until %s", until)
4491
4492 if not ht.TNumber(until):
4493 _Fail("Duration must be numeric")
4494
4495 utils.WriteFile(_filename, data="%d\n" % (until, ), mode=0644)
4496
4523
4526 """Hook runner.
4527
4528 This class is instantiated on the node side (ganeti-noded) and not
4529 on the master side.
4530
4531 """
4532 - def __init__(self, hooks_base_dir=None):
4533 """Constructor for hooks runner.
4534
4535 @type hooks_base_dir: str or None
4536 @param hooks_base_dir: if not None, this overrides the
4537 L{pathutils.HOOKS_BASE_DIR} (useful for unittests)
4538
4539 """
4540 if hooks_base_dir is None:
4541 hooks_base_dir = pathutils.HOOKS_BASE_DIR
4542
4543
4544 self._BASE_DIR = hooks_base_dir
4545
4547 """Check that the hooks will be run only locally and then run them.
4548
4549 """
4550 assert len(node_list) == 1
4551 node = node_list[0]
4552 _, myself = ssconf.GetMasterAndMyself()
4553 assert node == myself
4554
4555 results = self.RunHooks(hpath, phase, env)
4556
4557
4558 return {node: (None, False, results)}
4559
4560 - def RunHooks(self, hpath, phase, env):
4561 """Run the scripts in the hooks directory.
4562
4563 @type hpath: str
4564 @param hpath: the path to the hooks directory which
4565 holds the scripts
4566 @type phase: str
4567 @param phase: either L{constants.HOOKS_PHASE_PRE} or
4568 L{constants.HOOKS_PHASE_POST}
4569 @type env: dict
4570 @param env: dictionary with the environment for the hook
4571 @rtype: list
4572 @return: list of 3-element tuples:
4573 - script path
4574 - script result, either L{constants.HKR_SUCCESS} or
4575 L{constants.HKR_FAIL}
4576 - output of the script
4577
4578 @raise errors.ProgrammerError: for invalid input
4579 parameters
4580
4581 """
4582 if phase == constants.HOOKS_PHASE_PRE:
4583 suffix = "pre"
4584 elif phase == constants.HOOKS_PHASE_POST:
4585 suffix = "post"
4586 else:
4587 _Fail("Unknown hooks phase '%s'", phase)
4588
4589 subdir = "%s-%s.d" % (hpath, suffix)
4590 dir_name = utils.PathJoin(self._BASE_DIR, subdir)
4591
4592 results = []
4593
4594 if not os.path.isdir(dir_name):
4595
4596
4597 return results
4598
4599 runparts_results = utils.RunParts(dir_name, env=env, reset_env=True)
4600
4601 for (relname, relstatus, runresult) in runparts_results:
4602 if relstatus == constants.RUNPARTS_SKIP:
4603 rrval = constants.HKR_SKIP
4604 output = ""
4605 elif relstatus == constants.RUNPARTS_ERR:
4606 rrval = constants.HKR_FAIL
4607 output = "Hook script execution error: %s" % runresult
4608 elif relstatus == constants.RUNPARTS_RUN:
4609 if runresult.failed:
4610 rrval = constants.HKR_FAIL
4611 else:
4612 rrval = constants.HKR_SUCCESS
4613 output = utils.SafeEncode(runresult.output.strip())
4614 results.append(("%s/%s" % (subdir, relname), rrval, output))
4615
4616 return results
4617
4620 """IAllocator runner.
4621
4622 This class is instantiated on the node side (ganeti-noded) and not on
4623 the master side.
4624
4625 """
4626 @staticmethod
4627 - def Run(name, idata, ial_params):
4628 """Run an iallocator script.
4629
4630 @type name: str
4631 @param name: the iallocator script name
4632 @type idata: str
4633 @param idata: the allocator input data
4634 @type ial_params: list
4635 @param ial_params: the iallocator parameters
4636
4637 @rtype: tuple
4638 @return: two element tuple of:
4639 - status
4640 - either error message or stdout of allocator (for success)
4641
4642 """
4643 alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
4644 os.path.isfile)
4645 if alloc_script is None:
4646 _Fail("iallocator module '%s' not found in the search path", name)
4647
4648 fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
4649 try:
4650 os.write(fd, idata)
4651 os.close(fd)
4652 result = utils.RunCmd([alloc_script, fin_name] + ial_params)
4653 if result.failed:
4654 _Fail("iallocator module '%s' failed: %s, output '%s'",
4655 name, result.fail_reason, result.output)
4656 finally:
4657 os.unlink(fin_name)
4658
4659 return result.stdout
4660
4663 """Simple class for managing a cache of block device information.
4664
4665 """
4666 _DEV_PREFIX = "/dev/"
4667 _ROOT_DIR = pathutils.BDEV_CACHE_DIR
4668
4669 @classmethod
4671 """Converts a /dev/name path to the cache file name.
4672
4673 This replaces slashes with underscores and strips the /dev
4674 prefix. It then returns the full path to the cache file.
4675
4676 @type dev_path: str
4677 @param dev_path: the C{/dev/} path name
4678 @rtype: str
4679 @return: the converted path name
4680
4681 """
4682 if dev_path.startswith(cls._DEV_PREFIX):
4683 dev_path = dev_path[len(cls._DEV_PREFIX):]
4684 dev_path = dev_path.replace("/", "_")
4685 fpath = utils.PathJoin(cls._ROOT_DIR, "bdev_%s" % dev_path)
4686 return fpath
4687
4688 @classmethod
4689 - def UpdateCache(cls, dev_path, owner, on_primary, iv_name):
4690 """Updates the cache information for a given device.
4691
4692 @type dev_path: str
4693 @param dev_path: the pathname of the device
4694 @type owner: str
4695 @param owner: the owner (instance name) of the device
4696 @type on_primary: bool
4697 @param on_primary: whether this is the primary
4698 node nor not
4699 @type iv_name: str
4700 @param iv_name: the instance-visible name of the
4701 device, as in objects.Disk.iv_name
4702
4703 @rtype: None
4704
4705 """
4706 if dev_path is None:
4707 logging.error("DevCacheManager.UpdateCache got a None dev_path")
4708 return
4709 fpath = cls._ConvertPath(dev_path)
4710 if on_primary:
4711 state = "primary"
4712 else:
4713 state = "secondary"
4714 if iv_name is None:
4715 iv_name = "not_visible"
4716 fdata = "%s %s %s\n" % (str(owner), state, iv_name)
4717 try:
4718 utils.WriteFile(fpath, data=fdata)
4719 except EnvironmentError, err:
4720 logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
4721
4722 @classmethod
4724 """Remove data for a dev_path.
4725
4726 This is just a wrapper over L{utils.io.RemoveFile} with a converted
4727 path name and logging.
4728
4729 @type dev_path: str
4730 @param dev_path: the pathname of the device
4731
4732 @rtype: None
4733
4734 """
4735 if dev_path is None:
4736 logging.error("DevCacheManager.RemoveCache got a None dev_path")
4737 return
4738 fpath = cls._ConvertPath(dev_path)
4739 try:
4740 utils.RemoveFile(fpath)
4741 except EnvironmentError, err:
4742 logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
4743