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 base64
50 import errno
51 import logging
52 import os
53 import os.path
54 import pycurl
55 import random
56 import re
57 import shutil
58 import signal
59 import stat
60 import tempfile
61 import time
62 import zlib
63 import contextlib
64 import collections
65
66 from ganeti import errors
67 from ganeti import http
68 from ganeti import utils
69 from ganeti import ssh
70 from ganeti import hypervisor
71 from ganeti.hypervisor import hv_base
72 from ganeti import constants
73 from ganeti.storage import bdev
74 from ganeti.storage import drbd
75 from ganeti.storage import extstorage
76 from ganeti.storage import filestorage
77 from ganeti import objects
78 from ganeti import ssconf
79 from ganeti import serializer
80 from ganeti import netutils
81 from ganeti import runtime
82 from ganeti import compat
83 from ganeti import pathutils
84 from ganeti import vcluster
85 from ganeti import ht
86 from ganeti.storage.base import BlockDev
87 from ganeti.storage.drbd import DRBD8
88 from ganeti import hooksmaster
89 import ganeti.metad as metad
90
91
92 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
93 _ALLOWED_CLEAN_DIRS = compat.UniqueFrozenset([
94 pathutils.DATA_DIR,
95 pathutils.JOB_QUEUE_ARCHIVE_DIR,
96 pathutils.QUEUE_DIR,
97 pathutils.CRYPTO_KEYS_DIR,
98 ])
99 _MAX_SSL_CERT_VALIDITY = 7 * 24 * 60 * 60
100 _X509_KEY_FILE = "key"
101 _X509_CERT_FILE = "cert"
102 _IES_STATUS_FILE = "status"
103 _IES_PID_FILE = "pid"
104 _IES_CA_FILE = "ca"
105
106
107 _LVSLINE_REGEX = re.compile(r"^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
108
109
110 _MASTER_START = "start"
111 _MASTER_STOP = "stop"
112
113
114 _RCMD_MAX_MODE = (stat.S_IRWXU |
115 stat.S_IRGRP | stat.S_IXGRP |
116 stat.S_IROTH | stat.S_IXOTH)
117
118
119 _RCMD_INVALID_DELAY = 10
120
121
122
123
124 _RCMD_LOCK_TIMEOUT = _RCMD_INVALID_DELAY * 0.8
128 """Class denoting RPC failure.
129
130 Its argument is the error message.
131
132 """
133
136 """Path of the file containing the reason of the instance status change.
137
138 @type instance_name: string
139 @param instance_name: The name of the instance
140 @rtype: string
141 @return: The path of the file
142
143 """
144 return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name)
145
148 """Serialize a reason trail related to an instance change of state to file.
149
150 The exact location of the file depends on the name of the instance and on
151 the configuration of the Ganeti cluster defined at deploy time.
152
153 @type instance_name: string
154 @param instance_name: The name of the instance
155
156 @type trail: list of reasons
157 @param trail: reason trail
158
159 @rtype: None
160
161 """
162 json = serializer.DumpJson(trail)
163 filename = _GetInstReasonFilename(instance_name)
164 utils.WriteFile(filename, data=json)
165
166
167 -def _Fail(msg, *args, **kwargs):
168 """Log an error and the raise an RPCFail exception.
169
170 This exception is then handled specially in the ganeti daemon and
171 turned into a 'failed' return type. As such, this function is a
172 useful shortcut for logging the error and returning it to the master
173 daemon.
174
175 @type msg: string
176 @param msg: the text of the exception
177 @raise RPCFail
178
179 """
180 if args:
181 msg = msg % args
182 if "log" not in kwargs or kwargs["log"]:
183 if "exc" in kwargs and kwargs["exc"]:
184 logging.exception(msg)
185 else:
186 logging.error(msg)
187 raise RPCFail(msg)
188
191 """Simple wrapper to return a SimpleStore.
192
193 @rtype: L{ssconf.SimpleStore}
194 @return: a SimpleStore instance
195
196 """
197 return ssconf.SimpleStore()
198
201 """Simple wrapper to return an SshRunner.
202
203 @type cluster_name: str
204 @param cluster_name: the cluster name, which is needed
205 by the SshRunner constructor
206 @rtype: L{ssh.SshRunner}
207 @return: an SshRunner instance
208
209 """
210 return ssh.SshRunner(cluster_name)
211
214 """Unpacks data compressed by the RPC client.
215
216 @type data: list or tuple
217 @param data: Data sent by RPC client
218 @rtype: str
219 @return: Decompressed data
220
221 """
222 assert isinstance(data, (list, tuple))
223 assert len(data) == 2
224 (encoding, content) = data
225 if encoding == constants.RPC_ENCODING_NONE:
226 return content
227 elif encoding == constants.RPC_ENCODING_ZLIB_BASE64:
228 return zlib.decompress(base64.b64decode(content))
229 else:
230 raise AssertionError("Unknown data encoding")
231
234 """Removes all regular files in a directory.
235
236 @type path: str
237 @param path: the directory to clean
238 @type exclude: list
239 @param exclude: list of files to be excluded, defaults
240 to the empty list
241
242 """
243 if path not in _ALLOWED_CLEAN_DIRS:
244 _Fail("Path passed to _CleanDirectory not in allowed clean targets: '%s'",
245 path)
246
247 if not os.path.isdir(path):
248 return
249 if exclude is None:
250 exclude = []
251 else:
252
253 exclude = [os.path.normpath(i) for i in exclude]
254
255 for rel_name in utils.ListVisibleFiles(path):
256 full_name = utils.PathJoin(path, rel_name)
257 if full_name in exclude:
258 continue
259 if os.path.isfile(full_name) and not os.path.islink(full_name):
260 utils.RemoveFile(full_name)
261
264 """Build the list of allowed upload files.
265
266 This is abstracted so that it's built only once at module import time.
267
268 """
269 allowed_files = set([
270 pathutils.CLUSTER_CONF_FILE,
271 pathutils.ETC_HOSTS,
272 pathutils.SSH_KNOWN_HOSTS_FILE,
273 pathutils.VNC_PASSWORD_FILE,
274 pathutils.RAPI_CERT_FILE,
275 pathutils.SPICE_CERT_FILE,
276 pathutils.SPICE_CACERT_FILE,
277 pathutils.RAPI_USERS_FILE,
278 pathutils.CONFD_HMAC_KEY,
279 pathutils.CLUSTER_DOMAIN_SECRET_FILE,
280 ])
281
282 for hv_name in constants.HYPER_TYPES:
283 hv_class = hypervisor.GetHypervisorClass(hv_name)
284 allowed_files.update(hv_class.GetAncillaryFiles()[0])
285
286 assert pathutils.FILE_STORAGE_PATHS_FILE not in allowed_files, \
287 "Allowed file storage paths should never be uploaded via RPC"
288
289 return frozenset(allowed_files)
290
291
292 _ALLOWED_UPLOAD_FILES = _BuildUploadFileList()
304
307 """Returns the master node name.
308
309 @rtype: string
310 @return: name of the master node
311 @raise RPCFail: in case of errors
312
313 """
314 try:
315 return _GetConfig().GetMasterNode()
316 except errors.ConfigurationError, err:
317 _Fail("Cluster configuration incomplete: %s", err, exc=True)
318
321 """Decorator that runs hooks before and after the decorated function.
322
323 @type hook_opcode: string
324 @param hook_opcode: opcode of the hook
325 @type hooks_path: string
326 @param hooks_path: path of the hooks
327 @type env_builder_fn: function
328 @param env_builder_fn: function that returns a dictionary containing the
329 environment variables for the hooks. Will get all the parameters of the
330 decorated function.
331 @raise RPCFail: in case of pre-hook failure
332
333 """
334 def decorator(fn):
335 def wrapper(*args, **kwargs):
336
337
338 nodes = ([constants.DUMMY_UUID], [constants.DUMMY_UUID])
339
340 env_fn = compat.partial(env_builder_fn, *args, **kwargs)
341
342 cfg = _GetConfig()
343 hr = HooksRunner()
344 hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes,
345 hr.RunLocalHooks, None, env_fn, None,
346 logging.warning, cfg.GetClusterName(),
347 cfg.GetMasterNode())
348 hm.RunPhase(constants.HOOKS_PHASE_PRE)
349 result = fn(*args, **kwargs)
350 hm.RunPhase(constants.HOOKS_PHASE_POST)
351
352 return result
353 return wrapper
354 return decorator
355
358 """Builds environment variables for master IP hooks.
359
360 @type master_params: L{objects.MasterNetworkParameters}
361 @param master_params: network parameters of the master
362 @type use_external_mip_script: boolean
363 @param use_external_mip_script: whether to use an external master IP
364 address setup script (unused, but necessary per the implementation of the
365 _RunLocalHooks decorator)
366
367 """
368
369 ver = netutils.IPAddress.GetVersionFromAddressFamily(master_params.ip_family)
370 env = {
371 "MASTER_NETDEV": master_params.netdev,
372 "MASTER_IP": master_params.ip,
373 "MASTER_NETMASK": str(master_params.netmask),
374 "CLUSTER_IP_VERSION": str(ver),
375 }
376
377 return env
378
381 """Execute the master IP address setup script.
382
383 @type master_params: L{objects.MasterNetworkParameters}
384 @param master_params: network parameters of the master
385 @type action: string
386 @param action: action to pass to the script. Must be one of
387 L{backend._MASTER_START} or L{backend._MASTER_STOP}
388 @type use_external_mip_script: boolean
389 @param use_external_mip_script: whether to use an external master IP
390 address setup script
391 @raise backend.RPCFail: if there are errors during the execution of the
392 script
393
394 """
395 env = _BuildMasterIpEnv(master_params)
396
397 if use_external_mip_script:
398 setup_script = pathutils.EXTERNAL_MASTER_SETUP_SCRIPT
399 else:
400 setup_script = pathutils.DEFAULT_MASTER_SETUP_SCRIPT
401
402 result = utils.RunCmd([setup_script, action], env=env, reset_env=True)
403
404 if result.failed:
405 _Fail("Failed to %s the master IP. Script return value: %s, output: '%s'" %
406 (action, result.exit_code, result.output), log=True)
407
408
409 @RunLocalHooks(constants.FAKE_OP_MASTER_TURNUP, "master-ip-turnup",
410 _BuildMasterIpEnv)
412 """Activate the IP address of the master daemon.
413
414 @type master_params: L{objects.MasterNetworkParameters}
415 @param master_params: network parameters of the master
416 @type use_external_mip_script: boolean
417 @param use_external_mip_script: whether to use an external master IP
418 address setup script
419 @raise RPCFail: in case of errors during the IP startup
420
421 """
422 _RunMasterSetupScript(master_params, _MASTER_START,
423 use_external_mip_script)
424
427 """Activate local node as master node.
428
429 The function will start the master daemons (ganeti-masterd and ganeti-rapi).
430
431 @type no_voting: boolean
432 @param no_voting: whether to start ganeti-masterd without a node vote
433 but still non-interactively
434 @rtype: None
435
436 """
437
438 if no_voting:
439 daemon_args = "--no-voting --yes-do-it"
440 else:
441 daemon_args = ""
442
443 env = {
444 "EXTRA_LUXID_ARGS": daemon_args,
445 "EXTRA_WCONFD_ARGS": daemon_args,
446 }
447
448 result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-master"], env=env)
449 if result.failed:
450 msg = "Can't start Ganeti master: %s" % result.output
451 logging.error(msg)
452 _Fail(msg)
453
454
455 @RunLocalHooks(constants.FAKE_OP_MASTER_TURNDOWN, "master-ip-turndown",
456 _BuildMasterIpEnv)
458 """Deactivate the master IP on this node.
459
460 @type master_params: L{objects.MasterNetworkParameters}
461 @param master_params: network parameters of the master
462 @type use_external_mip_script: boolean
463 @param use_external_mip_script: whether to use an external master IP
464 address setup script
465 @raise RPCFail: in case of errors during the IP turndown
466
467 """
468 _RunMasterSetupScript(master_params, _MASTER_STOP,
469 use_external_mip_script)
470
473 """Stop the master daemons on this node.
474
475 Stop the master daemons (ganeti-masterd and ganeti-rapi) on this node.
476
477 @rtype: None
478
479 """
480
481
482
483 result = utils.RunCmd([pathutils.DAEMON_UTIL, "stop-master"])
484 if result.failed:
485 logging.error("Could not stop Ganeti master, command %s had exitcode %s"
486 " and error %s",
487 result.cmd, result.exit_code, result.output)
488
491 """Change the netmask of the master IP.
492
493 @param old_netmask: the old value of the netmask
494 @param netmask: the new value of the netmask
495 @param master_ip: the master IP
496 @param master_netdev: the master network device
497
498 """
499 if old_netmask == netmask:
500 return
501
502 if not netutils.IPAddress.Own(master_ip):
503 _Fail("The master IP address is not up, not attempting to change its"
504 " netmask")
505
506 result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add",
507 "%s/%s" % (master_ip, netmask),
508 "dev", master_netdev, "label",
509 "%s:0" % master_netdev])
510 if result.failed:
511 _Fail("Could not set the new netmask on the master IP address")
512
513 result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del",
514 "%s/%s" % (master_ip, old_netmask),
515 "dev", master_netdev, "label",
516 "%s:0" % master_netdev])
517 if result.failed:
518 _Fail("Could not bring down the master IP address with the old netmask")
519
522 """Modify a host entry in /etc/hosts.
523
524 @param mode: The mode to operate. Either add or remove entry
525 @param host: The host to operate on
526 @param ip: The ip associated with the entry
527
528 """
529 if mode == constants.ETC_HOSTS_ADD:
530 if not ip:
531 RPCFail("Mode 'add' needs 'ip' parameter, but parameter not"
532 " present")
533 utils.AddHostToEtcHosts(host, ip)
534 elif mode == constants.ETC_HOSTS_REMOVE:
535 if ip:
536 RPCFail("Mode 'remove' does not allow 'ip' parameter, but"
537 " parameter is present")
538 utils.RemoveHostFromEtcHosts(host)
539 else:
540 RPCFail("Mode not supported")
541
588
591 """Performs sanity checks for storage parameters.
592
593 @type params: list
594 @param params: list of storage parameters
595 @type num_params: int
596 @param num_params: expected number of parameters
597
598 """
599 if params is None:
600 raise errors.ProgrammerError("No storage parameters for storage"
601 " reporting is provided.")
602 if not isinstance(params, list):
603 raise errors.ProgrammerError("The storage parameters are not of type"
604 " list: '%s'" % params)
605 if not len(params) == num_params:
606 raise errors.ProgrammerError("Did not receive the expected number of"
607 "storage parameters: expected %s,"
608 " received '%s'" % (num_params, len(params)))
609
612 """Performs sanity check for the 'exclusive storage' flag.
613
614 @see: C{_CheckStorageParams}
615
616 """
617 _CheckStorageParams(params, 1)
618 excl_stor = params[0]
619 if not isinstance(params[0], bool):
620 raise errors.ProgrammerError("Exclusive storage parameter is not"
621 " boolean: '%s'." % excl_stor)
622 return excl_stor
623
626 """Wrapper around C{_GetVgInfo} which checks the storage parameters.
627
628 @type name: string
629 @param name: name of the volume group
630 @type params: list
631 @param params: list of storage parameters, which in this case should be
632 containing only one for exclusive storage
633
634 """
635 excl_stor = _CheckLvmStorageParams(params)
636 return _GetVgInfo(name, excl_stor)
637
641 """Retrieves information about a LVM volume group.
642
643 """
644
645 vginfo = info_fn([name], excl_stor)
646 if vginfo:
647 vg_free = int(round(vginfo[0][0], 0))
648 vg_size = int(round(vginfo[0][1], 0))
649 else:
650 vg_free = None
651 vg_size = None
652
653 return {
654 "type": constants.ST_LVM_VG,
655 "name": name,
656 "storage_free": vg_free,
657 "storage_size": vg_size,
658 }
659
669
673 """Retrieves information about spindles in an LVM volume group.
674
675 @type name: string
676 @param name: VG name
677 @type excl_stor: bool
678 @param excl_stor: exclusive storage
679 @rtype: dict
680 @return: dictionary whose keys are "name", "vg_free", "vg_size" for VG name,
681 free spindles, total spindles respectively
682
683 """
684 if excl_stor:
685 (vg_free, vg_size) = info_fn(name)
686 else:
687 vg_free = 0
688 vg_size = 0
689 return {
690 "type": constants.ST_LVM_PV,
691 "name": name,
692 "storage_free": vg_free,
693 "storage_size": vg_size,
694 }
695
698 """Retrieves node information from a hypervisor.
699
700 The information returned depends on the hypervisor. Common items:
701
702 - vg_size is the size of the configured volume group in MiB
703 - vg_free is the free size of the volume group in MiB
704 - memory_dom0 is the memory allocated for domain0 in MiB
705 - memory_free is the currently available (free) ram in MiB
706 - memory_total is the total number of ram in MiB
707 - hv_version: the hypervisor version, if available
708
709 @type hvparams: dict of string
710 @param hvparams: the hypervisor's hvparams
711
712 """
713 return get_hv_fn(name).GetNodeInfo(hvparams=hvparams)
714
717 """Retrieves node information for all hypervisors.
718
719 See C{_GetHvInfo} for information on the output.
720
721 @type hv_specs: list of pairs (string, dict of strings)
722 @param hv_specs: list of pairs of a hypervisor's name and its hvparams
723
724 """
725 if hv_specs is None:
726 return None
727
728 result = []
729 for hvname, hvparams in hv_specs:
730 result.append(_GetHvInfo(hvname, hvparams, get_hv_fn))
731 return result
732
735 """Calls C{fn} for all names in C{names} and returns a dictionary.
736
737 @rtype: None or dict
738
739 """
740 if names is None:
741 return None
742 else:
743 return map(fn, names)
744
747 """Gives back a hash with different information about the node.
748
749 @type storage_units: list of tuples (string, string, list)
750 @param storage_units: List of tuples (storage unit, identifier, parameters) to
751 ask for disk space information. In case of lvm-vg, the identifier is
752 the VG name. The parameters can contain additional, storage-type-specific
753 parameters, for example exclusive storage for lvm storage.
754 @type hv_specs: list of pairs (string, dict of strings)
755 @param hv_specs: list of pairs of a hypervisor's name and its hvparams
756 @rtype: tuple; (string, None/dict, None/dict)
757 @return: Tuple containing boot ID, volume group information and hypervisor
758 information
759
760 """
761 bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
762 storage_info = _GetNamedNodeInfo(
763 storage_units,
764 (lambda (storage_type, storage_key, storage_params):
765 _ApplyStorageInfoFunction(storage_type, storage_key, storage_params)))
766 hv_info = _GetHvInfoAll(hv_specs)
767 return (bootid, storage_info, hv_info)
768
771 """Wrapper around filestorage.GetSpaceInfo.
772
773 The purpose of this wrapper is to call filestorage.GetFileStorageSpaceInfo
774 and ignore the *args parameter to not leak it into the filestorage
775 module's code.
776
777 @see: C{filestorage.GetFileStorageSpaceInfo} for description of the
778 parameters.
779
780 """
781 _CheckStorageParams(params, 0)
782 return filestorage.GetFileStorageSpaceInfo(path)
783
784
785
786 _STORAGE_TYPE_INFO_FN = {
787 constants.ST_BLOCK: None,
788 constants.ST_DISKLESS: None,
789 constants.ST_EXT: None,
790 constants.ST_FILE: _GetFileStorageSpaceInfo,
791 constants.ST_LVM_PV: _GetLvmPvSpaceInfo,
792 constants.ST_LVM_VG: _GetLvmVgSpaceInfo,
793 constants.ST_SHARED_FILE: None,
794 constants.ST_GLUSTER: None,
795 constants.ST_RADOS: None,
796 }
800 """Looks up and applies the correct function to calculate free and total
801 storage for the given storage type.
802
803 @type storage_type: string
804 @param storage_type: the storage type for which the storage shall be reported.
805 @type storage_key: string
806 @param storage_key: identifier of a storage unit, e.g. the volume group name
807 of an LVM storage unit
808 @type args: any
809 @param args: various parameters that can be used for storage reporting. These
810 parameters and their semantics vary from storage type to storage type and
811 are just propagated in this function.
812 @return: the results of the application of the storage space function (see
813 _STORAGE_TYPE_INFO_FN) if storage space reporting is implemented for that
814 storage type
815 @raises NotImplementedError: for storage types who don't support space
816 reporting yet
817 """
818 fn = _STORAGE_TYPE_INFO_FN[storage_type]
819 if fn is not None:
820 return fn(storage_key, *args)
821 else:
822 raise NotImplementedError
823
826 """Check that PVs are not shared among LVs
827
828 @type pvi_list: list of L{objects.LvmPvInfo} objects
829 @param pvi_list: information about the PVs
830
831 @rtype: list of tuples (string, list of strings)
832 @return: offending volumes, as tuples: (pv_name, [lv1_name, lv2_name...])
833
834 """
835 res = []
836 for pvi in pvi_list:
837 if len(pvi.lv_list) > 1:
838 res.append((pvi.name, pvi.lv_list))
839 return res
840
844 """Verifies the hypervisor. Appends the results to the 'results' list.
845
846 @type what: C{dict}
847 @param what: a dictionary of things to check
848 @type vm_capable: boolean
849 @param vm_capable: whether or not this node is vm capable
850 @type result: dict
851 @param result: dictionary of verification results; results of the
852 verifications in this function will be added here
853 @type all_hvparams: dict of dict of string
854 @param all_hvparams: dictionary mapping hypervisor names to hvparams
855 @type get_hv_fn: function
856 @param get_hv_fn: function to retrieve the hypervisor, to improve testability
857
858 """
859 if not vm_capable:
860 return
861
862 if constants.NV_HYPERVISOR in what:
863 result[constants.NV_HYPERVISOR] = {}
864 for hv_name in what[constants.NV_HYPERVISOR]:
865 hvparams = all_hvparams[hv_name]
866 try:
867 val = get_hv_fn(hv_name).Verify(hvparams=hvparams)
868 except errors.HypervisorError, err:
869 val = "Error while checking hypervisor: %s" % str(err)
870 result[constants.NV_HYPERVISOR][hv_name] = val
871
875 """Verifies the hvparams. Appends the results to the 'results' list.
876
877 @type what: C{dict}
878 @param what: a dictionary of things to check
879 @type vm_capable: boolean
880 @param vm_capable: whether or not this node is vm capable
881 @type result: dict
882 @param result: dictionary of verification results; results of the
883 verifications in this function will be added here
884 @type get_hv_fn: function
885 @param get_hv_fn: function to retrieve the hypervisor, to improve testability
886
887 """
888 if not vm_capable:
889 return
890
891 if constants.NV_HVPARAMS in what:
892 result[constants.NV_HVPARAMS] = []
893 for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
894 try:
895 logging.info("Validating hv %s, %s", hv_name, hvparms)
896 get_hv_fn(hv_name).ValidateParameters(hvparms)
897 except errors.HypervisorError, err:
898 result[constants.NV_HVPARAMS].append((source, hv_name, str(err)))
899
902 """Verifies the instance list.
903
904 @type what: C{dict}
905 @param what: a dictionary of things to check
906 @type vm_capable: boolean
907 @param vm_capable: whether or not this node is vm capable
908 @type result: dict
909 @param result: dictionary of verification results; results of the
910 verifications in this function will be added here
911 @type all_hvparams: dict of dict of string
912 @param all_hvparams: dictionary mapping hypervisor names to hvparams
913
914 """
915 if constants.NV_INSTANCELIST in what and vm_capable:
916
917 try:
918 val = GetInstanceList(what[constants.NV_INSTANCELIST],
919 all_hvparams=all_hvparams)
920 except RPCFail, err:
921 val = str(err)
922 result[constants.NV_INSTANCELIST] = val
923
926 """Verifies the node info.
927
928 @type what: C{dict}
929 @param what: a dictionary of things to check
930 @type vm_capable: boolean
931 @param vm_capable: whether or not this node is vm capable
932 @type result: dict
933 @param result: dictionary of verification results; results of the
934 verifications in this function will be added here
935 @type all_hvparams: dict of dict of string
936 @param all_hvparams: dictionary mapping hypervisor names to hvparams
937
938 """
939 if constants.NV_HVINFO in what and vm_capable:
940 hvname = what[constants.NV_HVINFO]
941 hyper = hypervisor.GetHypervisor(hvname)
942 hvparams = all_hvparams[hvname]
943 result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams)
944
947 """Verify the existance and validity of the client SSL certificate.
948
949 Also, verify that the client certificate is not self-signed. Self-
950 signed client certificates stem from Ganeti versions 2.12.0 - 2.12.4
951 and should be replaced by client certificates signed by the server
952 certificate. Hence we output a warning when we encounter a self-signed
953 one.
954
955 """
956 create_cert_cmd = "gnt-cluster renew-crypto --new-node-certificates"
957 if not os.path.exists(cert_file):
958 return (constants.CV_ERROR,
959 "The client certificate does not exist. Run '%s' to create"
960 " client certificates for all nodes." % create_cert_cmd)
961
962 (errcode, msg) = utils.VerifyCertificate(cert_file)
963 if errcode is not None:
964 return (errcode, msg)
965
966 (errcode, msg) = utils.IsCertificateSelfSigned(cert_file)
967 if errcode is not None:
968 return (errcode, msg)
969
970
971 return (None, utils.GetCertificateDigest(cert_filename=cert_file))
972
976 """Verifies the state of the SSH key files.
977
978 @type node_status_list: list of tuples
979 @param node_status_list: list of nodes of the cluster associated with a
980 couple of flags: (uuid, name, is_master_candidate,
981 is_potential_master_candidate, online)
982 @type my_name: str
983 @param my_name: name of this node
984 @type ssh_key_type: one of L{constants.SSHK_ALL}
985 @param ssh_key_type: type of key used on nodes
986 @type ganeti_pub_keys_file: str
987 @param ganeti_pub_keys_file: filename of the public keys file
988
989 """
990 if node_status_list is None:
991 return ["No node list to check against the pub_key_file received."]
992
993 my_status_list = [(my_uuid, name, mc, pot_mc, online) for
994 (my_uuid, name, mc, pot_mc, online)
995 in node_status_list if name == my_name]
996 if len(my_status_list) == 0:
997 return ["Cannot find node information for node '%s'." % my_name]
998 (my_uuid, _, _, potential_master_candidate, online) = \
999 my_status_list[0]
1000
1001 result = []
1002
1003 if not os.path.exists(ganeti_pub_keys_file):
1004 result.append("The public key file '%s' does not exist. Consider running"
1005 " 'gnt-cluster renew-crypto --new-ssh-keys"
1006 " [--no-ssh-key-check]' to fix this." % ganeti_pub_keys_file)
1007 return result
1008
1009 pot_mc_uuids = [uuid for (uuid, _, _, _, _) in node_status_list]
1010 offline_nodes = [uuid for (uuid, _, _, _, online) in node_status_list
1011 if not online]
1012 pub_keys = ssh.QueryPubKeyFile(None, key_file=ganeti_pub_keys_file)
1013
1014 if potential_master_candidate:
1015
1016
1017 pub_uuids_set = set(pub_keys.keys()) - set(offline_nodes)
1018 pot_mc_uuids_set = set(pot_mc_uuids) - set(offline_nodes)
1019 missing_uuids = set([])
1020 if pub_uuids_set != pot_mc_uuids_set:
1021 unknown_uuids = pub_uuids_set - pot_mc_uuids_set
1022 if unknown_uuids:
1023 result.append("The following node UUIDs are listed in the public key"
1024 " file on node '%s', but are not potential master"
1025 " candidates: %s."
1026 % (my_name, ", ".join(list(unknown_uuids))))
1027 missing_uuids = pot_mc_uuids_set - pub_uuids_set
1028 if missing_uuids:
1029 result.append("The following node UUIDs of potential master candidates"
1030 " are missing in the public key file on node %s: %s."
1031 % (my_name, ", ".join(list(missing_uuids))))
1032
1033 (_, key_files) = \
1034 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
1035 (_, node_pub_key_file) = key_files[ssh_key_type]
1036
1037 my_keys = pub_keys[my_uuid]
1038
1039 node_pub_key = utils.ReadFile(node_pub_key_file)
1040 if node_pub_key.strip() not in my_keys:
1041 result.append("The dsa key of node %s does not match this node's key"
1042 " in the pub key file." % my_name)
1043 if len(my_keys) != 1:
1044 result.append("There is more than one key for node %s in the public key"
1045 " file." % my_name)
1046 else:
1047 if len(pub_keys.keys()) > 0:
1048 result.append("The public key file of node '%s' is not empty, although"
1049 " the node is not a potential master candidate."
1050 % my_name)
1051
1052
1053 (auth_key_file, _) = \
1054 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
1055 for (uuid, name, mc, _, online) in node_status_list:
1056 if not online:
1057 continue
1058 if uuid in missing_uuids:
1059 continue
1060 if mc:
1061 for key in pub_keys[uuid]:
1062 if not ssh.HasAuthorizedKey(auth_key_file, key):
1063 result.append("A SSH key of master candidate '%s' (UUID: '%s') is"
1064 " not in the 'authorized_keys' file of node '%s'."
1065 % (name, uuid, my_name))
1066 else:
1067 for key in pub_keys[uuid]:
1068 if name != my_name and ssh.HasAuthorizedKey(auth_key_file, key):
1069 result.append("A SSH key of normal node '%s' (UUID: '%s') is in the"
1070 " 'authorized_keys' file of node '%s'."
1071 % (name, uuid, my_name))
1072 if name == my_name and not ssh.HasAuthorizedKey(auth_key_file, key):
1073 result.append("A SSH key of normal node '%s' (UUID: '%s') is not"
1074 " in the 'authorized_keys' file of itself."
1075 % (my_name, uuid))
1076
1077 return result
1078
1081 """Verifies that the 'authorized_keys' files are not cluttered up.
1082
1083 @type node_status_list: list of tuples
1084 @param node_status_list: list of nodes of the cluster associated with a
1085 couple of flags: (uuid, name, is_master_candidate,
1086 is_potential_master_candidate, online)
1087 @type my_name: str
1088 @param my_name: name of this node
1089
1090 """
1091 result = []
1092 (auth_key_file, _) = \
1093 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
1094 node_names = [name for (_, name, _, _) in node_status_list]
1095 multiple_occurrences = ssh.CheckForMultipleKeys(auth_key_file, node_names)
1096 if multiple_occurrences:
1097 msg = "There are hosts which have more than one SSH key stored for the" \
1098 " same user in the 'authorized_keys' file of node %s. This can be" \
1099 " due to an unsuccessful operation which cluttered up the" \
1100 " 'authorized_keys' file. We recommend to clean this up manually. " \
1101 % my_name
1102 for host, occ in multiple_occurrences.items():
1103 msg += "Entry for '%s' in lines %s. " % (host, utils.CommaJoin(occ))
1104 result.append(msg)
1105
1106 return result
1107
1108
1109 -def VerifyNode(what, cluster_name, all_hvparams):
1110 """Verify the status of the local node.
1111
1112 Based on the input L{what} parameter, various checks are done on the
1113 local node.
1114
1115 If the I{filelist} key is present, this list of
1116 files is checksummed and the file/checksum pairs are returned.
1117
1118 If the I{nodelist} key is present, we check that we have
1119 connectivity via ssh with the target nodes (and check the hostname
1120 report).
1121
1122 If the I{node-net-test} key is present, we check that we have
1123 connectivity to the given nodes via both primary IP and, if
1124 applicable, secondary IPs.
1125
1126 @type what: C{dict}
1127 @param what: a dictionary of things to check:
1128 - filelist: list of files for which to compute checksums
1129 - nodelist: list of nodes we should check ssh communication with
1130 - node-net-test: list of nodes we should check node daemon port
1131 connectivity with
1132 - hypervisor: list with hypervisors to run the verify for
1133 @type cluster_name: string
1134 @param cluster_name: the cluster's name
1135 @type all_hvparams: dict of dict of strings
1136 @param all_hvparams: a dictionary mapping hypervisor names to hvparams
1137 @rtype: dict
1138 @return: a dictionary with the same keys as the input dict, and
1139 values representing the result of the checks
1140
1141 """
1142 result = {}
1143 my_name = netutils.Hostname.GetSysName()
1144 port = netutils.GetDaemonPort(constants.NODED)
1145 vm_capable = my_name not in what.get(constants.NV_NONVMNODES, [])
1146
1147 _VerifyHypervisors(what, vm_capable, result, all_hvparams)
1148 _VerifyHvparams(what, vm_capable, result)
1149
1150 if constants.NV_FILELIST in what:
1151 fingerprints = utils.FingerprintFiles(map(vcluster.LocalizeVirtualPath,
1152 what[constants.NV_FILELIST]))
1153 result[constants.NV_FILELIST] = \
1154 dict((vcluster.MakeVirtualPath(key), value)
1155 for (key, value) in fingerprints.items())
1156
1157 if constants.NV_CLIENT_CERT in what:
1158 result[constants.NV_CLIENT_CERT] = _VerifyClientCertificate()
1159
1160 if constants.NV_SSH_SETUP in what:
1161 node_status_list, key_type = what[constants.NV_SSH_SETUP]
1162 result[constants.NV_SSH_SETUP] = \
1163 _VerifySshSetup(node_status_list, my_name, key_type)
1164 if constants.NV_SSH_CLUTTER in what:
1165 result[constants.NV_SSH_CLUTTER] = \
1166 _VerifySshClutter(what[constants.NV_SSH_SETUP], my_name)
1167
1168 if constants.NV_NODELIST in what:
1169 (nodes, bynode, mcs) = what[constants.NV_NODELIST]
1170
1171
1172 try:
1173 nodes.extend(bynode[my_name])
1174 except KeyError:
1175 pass
1176
1177
1178 random.shuffle(nodes)
1179
1180
1181 val = {}
1182 ssh_port_map = ssconf.SimpleStore().GetSshPortMap()
1183 for node in nodes:
1184
1185
1186
1187
1188 if my_name in mcs:
1189 success, message = _GetSshRunner(cluster_name). \
1190 VerifyNodeHostname(node, ssh_port_map[node])
1191 if not success:
1192 val[node] = message
1193
1194 result[constants.NV_NODELIST] = val
1195
1196 if constants.NV_NODENETTEST in what:
1197 result[constants.NV_NODENETTEST] = tmp = {}
1198 my_pip = my_sip = None
1199 for name, pip, sip in what[constants.NV_NODENETTEST]:
1200 if name == my_name:
1201 my_pip = pip
1202 my_sip = sip
1203 break
1204 if not my_pip:
1205 tmp[my_name] = ("Can't find my own primary/secondary IP"
1206 " in the node list")
1207 else:
1208 for name, pip, sip in what[constants.NV_NODENETTEST]:
1209 fail = []
1210 if not netutils.TcpPing(pip, port, source=my_pip):
1211 fail.append("primary")
1212 if sip != pip:
1213 if not netutils.TcpPing(sip, port, source=my_sip):
1214 fail.append("secondary")
1215 if fail:
1216 tmp[name] = ("failure using the %s interface(s)" %
1217 " and ".join(fail))
1218
1219 if constants.NV_MASTERIP in what:
1220
1221
1222 master_name, master_ip = what[constants.NV_MASTERIP]
1223 if master_name == my_name:
1224 source = constants.IP4_ADDRESS_LOCALHOST
1225 else:
1226 source = None
1227 result[constants.NV_MASTERIP] = netutils.TcpPing(master_ip, port,
1228 source=source)
1229
1230 if constants.NV_USERSCRIPTS in what:
1231 result[constants.NV_USERSCRIPTS] = \
1232 [script for script in what[constants.NV_USERSCRIPTS]
1233 if not utils.IsExecutable(script)]
1234
1235 if constants.NV_OOB_PATHS in what:
1236 result[constants.NV_OOB_PATHS] = tmp = []
1237 for path in what[constants.NV_OOB_PATHS]:
1238 try:
1239 st = os.stat(path)
1240 except OSError, err:
1241 tmp.append("error stating out of band helper: %s" % err)
1242 else:
1243 if stat.S_ISREG(st.st_mode):
1244 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR:
1245 tmp.append(None)
1246 else:
1247 tmp.append("out of band helper %s is not executable" % path)
1248 else:
1249 tmp.append("out of band helper %s is not a file" % path)
1250
1251 if constants.NV_LVLIST in what and vm_capable:
1252 try:
1253 val = GetVolumeList(utils.ListVolumeGroups().keys())
1254 except RPCFail, err:
1255 val = str(err)
1256 result[constants.NV_LVLIST] = val
1257
1258 _VerifyInstanceList(what, vm_capable, result, all_hvparams)
1259
1260 if constants.NV_VGLIST in what and vm_capable:
1261 result[constants.NV_VGLIST] = utils.ListVolumeGroups()
1262
1263 if constants.NV_PVLIST in what and vm_capable:
1264 check_exclusive_pvs = constants.NV_EXCLUSIVEPVS in what
1265 val = bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
1266 filter_allocatable=False,
1267 include_lvs=check_exclusive_pvs)
1268 if check_exclusive_pvs:
1269 result[constants.NV_EXCLUSIVEPVS] = _CheckExclusivePvs(val)
1270 for pvi in val:
1271
1272 pvi.lv_list = []
1273 result[constants.NV_PVLIST] = map(objects.LvmPvInfo.ToDict, val)
1274
1275 if constants.NV_VERSION in what:
1276 result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
1277 constants.RELEASE_VERSION)
1278
1279 _VerifyNodeInfo(what, vm_capable, result, all_hvparams)
1280
1281 if constants.NV_DRBDVERSION in what and vm_capable:
1282 try:
1283 drbd_version = DRBD8.GetProcInfo().GetVersionString()
1284 except errors.BlockDeviceError, err:
1285 logging.warning("Can't get DRBD version", exc_info=True)
1286 drbd_version = str(err)
1287 result[constants.NV_DRBDVERSION] = drbd_version
1288
1289 if constants.NV_DRBDLIST in what and vm_capable:
1290 try:
1291 used_minors = drbd.DRBD8.GetUsedDevs()
1292 except errors.BlockDeviceError, err:
1293 logging.warning("Can't get used minors list", exc_info=True)
1294 used_minors = str(err)
1295 result[constants.NV_DRBDLIST] = used_minors
1296
1297 if constants.NV_DRBDHELPER in what and vm_capable:
1298 status = True
1299 try:
1300 payload = drbd.DRBD8.GetUsermodeHelper()
1301 except errors.BlockDeviceError, err:
1302 logging.error("Can't get DRBD usermode helper: %s", str(err))
1303 status = False
1304 payload = str(err)
1305 result[constants.NV_DRBDHELPER] = (status, payload)
1306
1307 if constants.NV_NODESETUP in what:
1308 result[constants.NV_NODESETUP] = tmpr = []
1309 if not os.path.isdir("/sys/block") or not os.path.isdir("/sys/class/net"):
1310 tmpr.append("The sysfs filesytem doesn't seem to be mounted"
1311 " under /sys, missing required directories /sys/block"
1312 " and /sys/class/net")
1313 if (not os.path.isdir("/proc/sys") or
1314 not os.path.isfile("/proc/sysrq-trigger")):
1315 tmpr.append("The procfs filesystem doesn't seem to be mounted"
1316 " under /proc, missing required directory /proc/sys and"
1317 " the file /proc/sysrq-trigger")
1318
1319 if constants.NV_TIME in what:
1320 result[constants.NV_TIME] = utils.SplitTime(time.time())
1321
1322 if constants.NV_OSLIST in what and vm_capable:
1323 result[constants.NV_OSLIST] = DiagnoseOS()
1324
1325 if constants.NV_BRIDGES in what and vm_capable:
1326 result[constants.NV_BRIDGES] = [bridge
1327 for bridge in what[constants.NV_BRIDGES]
1328 if not utils.BridgeExists(bridge)]
1329
1330 if what.get(constants.NV_ACCEPTED_STORAGE_PATHS) == my_name:
1331 result[constants.NV_ACCEPTED_STORAGE_PATHS] = \
1332 filestorage.ComputeWrongFileStoragePaths()
1333
1334 if what.get(constants.NV_FILE_STORAGE_PATH):
1335 pathresult = filestorage.CheckFileStoragePath(
1336 what[constants.NV_FILE_STORAGE_PATH])
1337 if pathresult:
1338 result[constants.NV_FILE_STORAGE_PATH] = pathresult
1339
1340 if what.get(constants.NV_SHARED_FILE_STORAGE_PATH):
1341 pathresult = filestorage.CheckFileStoragePath(
1342 what[constants.NV_SHARED_FILE_STORAGE_PATH])
1343 if pathresult:
1344 result[constants.NV_SHARED_FILE_STORAGE_PATH] = pathresult
1345
1346 return result
1347
1350 """Perform actions on the node's cryptographic tokens.
1351
1352 Token types can be 'ssl' or 'ssh'. So far only some actions are implemented
1353 for 'ssl'. Action 'get' returns the digest of the public client ssl
1354 certificate. Action 'create' creates a new client certificate and private key
1355 and also returns the digest of the certificate. The third parameter of a
1356 token request are optional parameters for the actions, so far only the
1357 filename is supported.
1358
1359 @type token_requests: list of tuples of (string, string, dict), where the
1360 first string is in constants.CRYPTO_TYPES, the second in
1361 constants.CRYPTO_ACTIONS. The third parameter is a dictionary of string
1362 to string.
1363 @param token_requests: list of requests of cryptographic tokens and actions
1364 to perform on them. The actions come with a dictionary of options.
1365 @rtype: list of tuples (string, string)
1366 @return: list of tuples of the token type and the public crypto token
1367
1368 """
1369 tokens = []
1370 for (token_type, action, _) in token_requests:
1371 if token_type not in constants.CRYPTO_TYPES:
1372 raise errors.ProgrammerError("Token type '%s' not supported." %
1373 token_type)
1374 if action not in constants.CRYPTO_ACTIONS:
1375 raise errors.ProgrammerError("Action '%s' is not supported." %
1376 action)
1377 if token_type == constants.CRYPTO_TYPE_SSL_DIGEST:
1378 tokens.append((token_type,
1379 utils.GetCertificateDigest()))
1380 return tokens
1381
1384 """Ensures the given daemon is running or stopped.
1385
1386 @type daemon_name: string
1387 @param daemon_name: name of the daemon (e.g., constants.KVMD)
1388
1389 @type run: bool
1390 @param run: whether to start or stop the daemon
1391
1392 @rtype: bool
1393 @return: 'True' if daemon successfully started/stopped,
1394 'False' otherwise
1395
1396 """
1397 allowed_daemons = [constants.KVMD]
1398
1399 if daemon_name not in allowed_daemons:
1400 fn = lambda _: False
1401 elif run:
1402 fn = utils.EnsureDaemon
1403 else:
1404 fn = utils.StopDaemon
1405
1406 return fn(daemon_name)
1407
1416
1417
1418 -def AddNodeSshKey(node_uuid, node_name,
1419 potential_master_candidates,
1420 to_authorized_keys=False,
1421 to_public_keys=False,
1422 get_public_keys=False,
1423 pub_key_file=pathutils.SSH_PUB_KEYS,
1424 ssconf_store=None,
1425 noded_cert_file=pathutils.NODED_CERT_FILE,
1426 run_cmd_fn=ssh.RunSshCmdWithStdin,
1427 ssh_update_debug=False,
1428 ssh_update_verbose=False):
1429 """Distributes a node's public SSH key across the cluster.
1430
1431 Note that this function should only be executed on the master node, which
1432 then will copy the new node's key to all nodes in the cluster via SSH.
1433
1434 Also note: at least one of the flags C{to_authorized_keys},
1435 C{to_public_keys}, and C{get_public_keys} has to be set to C{True} for
1436 the function to actually perform any actions.
1437
1438 @type node_uuid: str
1439 @param node_uuid: the UUID of the node whose key is added
1440 @type node_name: str
1441 @param node_name: the name of the node whose key is added
1442 @type potential_master_candidates: list of str
1443 @param potential_master_candidates: list of node names of potential master
1444 candidates; this should match the list of uuids in the public key file
1445 @type to_authorized_keys: boolean
1446 @param to_authorized_keys: whether the key should be added to the
1447 C{authorized_keys} file of all nodes
1448 @type to_public_keys: boolean
1449 @param to_public_keys: whether the keys should be added to the public key file
1450 @type get_public_keys: boolean
1451 @param get_public_keys: whether the node should add the clusters' public keys
1452 to its {ganeti_pub_keys} file
1453
1454 """
1455 node_list = [SshAddNodeInfo(name=node_name, uuid=node_uuid,
1456 to_authorized_keys=to_authorized_keys,
1457 to_public_keys=to_public_keys,
1458 get_public_keys=get_public_keys)]
1459 return AddNodeSshKeyBulk(node_list,
1460 potential_master_candidates,
1461 pub_key_file=pub_key_file,
1462 ssconf_store=ssconf_store,
1463 noded_cert_file=noded_cert_file,
1464 run_cmd_fn=run_cmd_fn,
1465 ssh_update_debug=ssh_update_debug,
1466 ssh_update_verbose=ssh_update_verbose)
1467
1468
1469
1470 SshAddNodeInfo = collections.namedtuple(
1471 "SshAddNodeInfo",
1472 ["uuid",
1473 "name",
1474 "to_authorized_keys",
1475 "to_public_keys",
1476 "get_public_keys"])
1487 """Distributes a node's public SSH key across the cluster.
1488
1489 Note that this function should only be executed on the master node, which
1490 then will copy the new node's key to all nodes in the cluster via SSH.
1491
1492 Also note: at least one of the flags C{to_authorized_keys},
1493 C{to_public_keys}, and C{get_public_keys} has to be set to C{True} for
1494 the function to actually perform any actions.
1495
1496 @type node_list: list of SshAddNodeInfo tuples
1497 @param node_list: list of tuples containing the necessary node information for
1498 adding their keys
1499 @type potential_master_candidates: list of str
1500 @param potential_master_candidates: list of node names of potential master
1501 candidates; this should match the list of uuids in the public key file
1502
1503 """
1504
1505 to_authorized_keys = any([node_info.to_authorized_keys for node_info in
1506 node_list])
1507 to_public_keys = any([node_info.to_public_keys for node_info in
1508 node_list])
1509
1510 if not ssconf_store:
1511 ssconf_store = ssconf.SimpleStore()
1512
1513 for node_info in node_list:
1514
1515
1516 if not node_info.to_public_keys:
1517 continue
1518
1519 keys_by_name = ssh.QueryPubKeyFile([node_info.name], key_file=pub_key_file)
1520 keys_by_uuid = ssh.QueryPubKeyFile([node_info.uuid], key_file=pub_key_file)
1521
1522 if (not keys_by_name or node_info.name not in keys_by_name) \
1523 and (not keys_by_uuid or node_info.uuid not in keys_by_uuid):
1524 raise errors.SshUpdateError(
1525 "No keys found for the new node '%s' (UUID %s) in the list of public"
1526 " SSH keys, neither for the name or the UUID" %
1527 (node_info.name, node_info.uuid))
1528 else:
1529 if node_info.name in keys_by_name:
1530
1531
1532 ssh.ReplaceNameByUuid(node_info.uuid, node_info.name,
1533 error_fn=errors.SshUpdateError,
1534 key_file=pub_key_file)
1535
1536
1537 keys_by_uuid = ssh.QueryPubKeyFile(
1538 [node_info.uuid for node_info in node_list], key_file=pub_key_file)
1539
1540
1541 (auth_key_file, _) = \
1542 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
1543 for node_info in node_list:
1544 if node_info.to_authorized_keys:
1545 ssh.AddAuthorizedKeys(auth_key_file, keys_by_uuid[node_info.uuid])
1546
1547 base_data = {}
1548 _InitSshUpdateData(base_data, noded_cert_file, ssconf_store)
1549 cluster_name = base_data[constants.SSHS_CLUSTER_NAME]
1550
1551 ssh_port_map = ssconf_store.GetSshPortMap()
1552
1553
1554 for node_info in node_list:
1555 logging.debug("Updating SSH key files of target node '%s'.", node_info.name)
1556 if node_info.get_public_keys:
1557 node_data = {}
1558 _InitSshUpdateData(node_data, noded_cert_file, ssconf_store)
1559 all_keys = ssh.QueryPubKeyFile(None, key_file=pub_key_file)
1560 node_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
1561 (constants.SSHS_OVERRIDE, all_keys)
1562
1563 try:
1564 backoff = 5
1565 utils.RetryByNumberOfTimes(
1566 constants.SSHS_MAX_RETRIES, backoff,
1567 errors.SshUpdateError,
1568 run_cmd_fn, cluster_name, node_info.name, pathutils.SSH_UPDATE,
1569 ssh_port_map.get(node_info.name), node_data,
1570 debug=ssh_update_debug, verbose=ssh_update_verbose,
1571 use_cluster_key=False, ask_key=False, strict_host_check=False)
1572 except errors.SshUpdateError as e:
1573
1574 if node_info.to_public_keys:
1575 ssh.RemovePublicKey(node_info.uuid)
1576 raise e
1577
1578
1579 keys_by_uuid_auth = ssh.QueryPubKeyFile(
1580 [node_info.uuid for node_info in node_list
1581 if node_info.to_authorized_keys],
1582 key_file=pub_key_file)
1583 if to_authorized_keys:
1584 base_data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
1585 (constants.SSHS_ADD, keys_by_uuid_auth)
1586
1587 pot_mc_data = base_data.copy()
1588 keys_by_uuid_pub = ssh.QueryPubKeyFile(
1589 [node_info.uuid for node_info in node_list
1590 if node_info.to_public_keys],
1591 key_file=pub_key_file)
1592 if to_public_keys:
1593 pot_mc_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
1594 (constants.SSHS_REPLACE_OR_ADD, keys_by_uuid_pub)
1595
1596 all_nodes = ssconf_store.GetNodeList()
1597 master_node = ssconf_store.GetMasterNode()
1598 online_nodes = ssconf_store.GetOnlineNodeList()
1599
1600 node_errors = []
1601 for node in all_nodes:
1602 if node == master_node:
1603 logging.debug("Skipping master node '%s'.", master_node)
1604 continue
1605 if node not in online_nodes:
1606 logging.debug("Skipping offline node '%s'.", node)
1607 continue
1608 if node in potential_master_candidates:
1609 logging.debug("Updating SSH key files of node '%s'.", node)
1610 try:
1611 backoff = 5
1612 utils.RetryByNumberOfTimes(
1613 constants.SSHS_MAX_RETRIES, backoff, errors.SshUpdateError,
1614 run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
1615 ssh_port_map.get(node), pot_mc_data,
1616 debug=ssh_update_debug, verbose=ssh_update_verbose,
1617 use_cluster_key=False, ask_key=False, strict_host_check=False)
1618 except errors.SshUpdateError as last_exception:
1619 error_msg = ("When adding the key of node '%s', updating SSH key"
1620 " files of node '%s' failed after %s retries."
1621 " Not trying again. Last error was: %s." %
1622 (node, node_info.name, constants.SSHS_MAX_RETRIES,
1623 last_exception))
1624 node_errors.append((node, error_msg))
1625
1626
1627 logging.error(error_msg)
1628
1629 else:
1630 if to_authorized_keys:
1631 run_cmd_fn(cluster_name, node, pathutils.SSH_UPDATE,
1632 ssh_port_map.get(node), base_data,
1633 debug=ssh_update_debug, verbose=ssh_update_verbose,
1634 use_cluster_key=False, ask_key=False,
1635 strict_host_check=False)
1636
1637 return node_errors
1638
1639
1640
1641
1642 -def RemoveNodeSshKey(node_uuid, node_name,
1643 master_candidate_uuids,
1644 potential_master_candidates,
1645 master_uuid=None,
1646 keys_to_remove=None,
1647 from_authorized_keys=False,
1648 from_public_keys=False,
1649 clear_authorized_keys=False,
1650 clear_public_keys=False,
1651 pub_key_file=pathutils.SSH_PUB_KEYS,
1652 ssconf_store=None,
1653 noded_cert_file=pathutils.NODED_CERT_FILE,
1654 readd=False,
1655 run_cmd_fn=ssh.RunSshCmdWithStdin,
1656 ssh_update_debug=False,
1657 ssh_update_verbose=False):
1658 """Removes the node's SSH keys from the key files and distributes those.
1659
1660 Note that at least one of the flags C{from_authorized_keys},
1661 C{from_public_keys}, C{clear_authorized_keys}, and C{clear_public_keys}
1662 has to be set to C{True} for the function to perform any action at all.
1663 Not doing so will trigger an assertion in the function.
1664
1665 @type node_uuid: str
1666 @param node_uuid: UUID of the node whose key is removed
1667 @type node_name: str
1668 @param node_name: name of the node whose key is remove
1669 @type master_candidate_uuids: list of str
1670 @param master_candidate_uuids: list of UUIDs of the current master candidates
1671 @type potential_master_candidates: list of str
1672 @param potential_master_candidates: list of names of potential master
1673 candidates
1674 @type keys_to_remove: dict of str to list of str
1675 @param keys_to_remove: a dictionary mapping node UUIDS to lists of SSH keys
1676 to be removed. This list is supposed to be used only if the keys are not
1677 in the public keys file. This is for example the case when removing a
1678 master node's key.
1679 @type from_authorized_keys: boolean
1680 @param from_authorized_keys: whether or not the key should be removed
1681 from the C{authorized_keys} file
1682 @type from_public_keys: boolean
1683 @param from_public_keys: whether or not the key should be remove from
1684 the C{ganeti_pub_keys} file
1685 @type clear_authorized_keys: boolean
1686 @param clear_authorized_keys: whether or not the C{authorized_keys} file
1687 should be cleared on the node whose keys are removed
1688 @type clear_public_keys: boolean
1689 @param clear_public_keys: whether to clear the node's C{ganeti_pub_key} file
1690 @type readd: boolean
1691 @param readd: whether this is called during a readd operation.
1692 @rtype: list of string
1693 @returns: list of feedback messages
1694
1695 """
1696 node_list = [SshRemoveNodeInfo(uuid=node_uuid,
1697 name=node_name,
1698 from_authorized_keys=from_authorized_keys,
1699 from_public_keys=from_public_keys,
1700 clear_authorized_keys=clear_authorized_keys,
1701 clear_public_keys=clear_public_keys)]
1702 return RemoveNodeSshKeyBulk(node_list,
1703 master_candidate_uuids,
1704 potential_master_candidates,
1705 master_uuid=master_uuid,
1706 keys_to_remove=keys_to_remove,
1707 pub_key_file=pub_key_file,
1708 ssconf_store=ssconf_store,
1709 noded_cert_file=noded_cert_file,
1710 readd=readd,
1711 run_cmd_fn=run_cmd_fn,
1712 ssh_update_debug=ssh_update_debug,
1713 ssh_update_verbose=ssh_update_verbose)
1714
1715
1716
1717 SshRemoveNodeInfo = collections.namedtuple(
1718 "SshRemoveNodeInfo",
1719 ["uuid",
1720 "name",
1721 "from_authorized_keys",
1722 "from_public_keys",
1723 "clear_authorized_keys",
1724 "clear_public_keys"])
1725
1726
1727 -def RemoveNodeSshKeyBulk(node_list,
1728 master_candidate_uuids,
1729 potential_master_candidates,
1730 master_uuid=None,
1731 keys_to_remove=None,
1732 pub_key_file=pathutils.SSH_PUB_KEYS,
1733 ssconf_store=None,
1734 noded_cert_file=pathutils.NODED_CERT_FILE,
1735 readd=False,
1736 run_cmd_fn=ssh.RunSshCmdWithStdin,
1737 ssh_update_debug=False,
1738 ssh_update_verbose=False):
1739 """Removes the node's SSH keys from the key files and distributes those.
1740
1741 Note that at least one of the flags C{from_authorized_keys},
1742 C{from_public_keys}, C{clear_authorized_keys}, and C{clear_public_keys}
1743 of at least one node has to be set to C{True} for the function to perform any
1744 action at all. Not doing so will trigger an assertion in the function.
1745
1746 @type node_list: list of C{SshRemoveNodeInfo}.
1747 @param node_list: list of information about nodes whose keys are being removed
1748 @type master_candidate_uuids: list of str
1749 @param master_candidate_uuids: list of UUIDs of the current master candidates
1750 @type potential_master_candidates: list of str
1751 @param potential_master_candidates: list of names of potential master
1752 candidates
1753 @type keys_to_remove: dict of str to list of str
1754 @param keys_to_remove: a dictionary mapping node UUIDS to lists of SSH keys
1755 to be removed. This list is supposed to be used only if the keys are not
1756 in the public keys file. This is for example the case when removing a
1757 master node's key.
1758 @type readd: boolean
1759 @param readd: whether this is called during a readd operation.
1760 @rtype: list of string
1761 @returns: list of feedback messages
1762
1763 """
1764
1765 result_msgs = []
1766
1767
1768 from_authorized_keys = any([node_info.from_authorized_keys for node_info in
1769 node_list])
1770 from_public_keys = any([node_info.from_public_keys for node_info in
1771 node_list])
1772 clear_authorized_keys = any([node_info.clear_authorized_keys for node_info in
1773 node_list])
1774 clear_public_keys = any([node_info.clear_public_keys for node_info in
1775 node_list])
1776
1777
1778 if not (from_authorized_keys or from_public_keys or clear_authorized_keys
1779 or clear_public_keys):
1780 raise errors.SshUpdateError("No removal from any key file was requested.")
1781
1782 if not ssconf_store:
1783 ssconf_store = ssconf.SimpleStore()
1784
1785 master_node = ssconf_store.GetMasterNode()
1786 ssh_port_map = ssconf_store.GetSshPortMap()
1787
1788 all_keys_to_remove = {}
1789 if from_authorized_keys or from_public_keys:
1790 for node_info in node_list:
1791
1792 if not (node_info.from_authorized_keys or node_info.from_public_keys):
1793 continue
1794 if node_info.name == master_node and not keys_to_remove:
1795 raise errors.SshUpdateError("Cannot remove the master node's keys.")
1796 if keys_to_remove:
1797 keys = keys_to_remove
1798 else:
1799 keys = ssh.QueryPubKeyFile([node_info.uuid], key_file=pub_key_file)
1800 if (not keys or node_info.uuid not in keys) and not readd:
1801 raise errors.SshUpdateError("Node '%s' not found in the list of"
1802 " public SSH keys. It seems someone"
1803 " tries to remove a key from outside"
1804 " the cluster!" % node_info.uuid)
1805
1806
1807
1808 master_keys = None
1809 if master_uuid:
1810 master_keys = ssh.QueryPubKeyFile([master_uuid],
1811 key_file=pub_key_file)
1812 for master_key in master_keys:
1813 if master_key in keys[node_info.uuid]:
1814 keys[node_info.uuid].remove(master_key)
1815
1816 all_keys_to_remove.update(keys)
1817
1818 if all_keys_to_remove:
1819 base_data = {}
1820 _InitSshUpdateData(base_data, noded_cert_file, ssconf_store)
1821 cluster_name = base_data[constants.SSHS_CLUSTER_NAME]
1822
1823 if from_authorized_keys:
1824
1825
1826 nodes_remove_from_authorized_keys = [
1827 node_info.uuid for node_info in node_list
1828 if node_info.from_authorized_keys]
1829 keys_to_remove_from_authorized_keys = dict([
1830 (uuid, keys) for (uuid, keys) in all_keys_to_remove.items()
1831 if uuid in nodes_remove_from_authorized_keys])
1832 base_data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
1833 (constants.SSHS_REMOVE, keys_to_remove_from_authorized_keys)
1834 (auth_key_file, _) = \
1835 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False,
1836 dircheck=False)
1837
1838 for uuid in nodes_remove_from_authorized_keys:
1839 ssh.RemoveAuthorizedKeys(auth_key_file,
1840 keys_to_remove_from_authorized_keys[uuid])
1841
1842 pot_mc_data = base_data.copy()
1843
1844 if from_public_keys:
1845 nodes_remove_from_public_keys = [
1846 node_info.uuid for node_info in node_list
1847 if node_info.from_public_keys]
1848 keys_to_remove_from_public_keys = dict([
1849 (uuid, keys) for (uuid, keys) in all_keys_to_remove.items()
1850 if uuid in nodes_remove_from_public_keys])
1851 pot_mc_data[constants.SSHS_SSH_PUBLIC_KEYS] = \
1852 (constants.SSHS_REMOVE, keys_to_remove_from_public_keys)
1853
1854 all_nodes = ssconf_store.GetNodeList()
1855 online_nodes = ssconf_store.GetOnlineNodeList()
1856 all_nodes_to_remove = [node_info.name for node_info in node_list]
1857 logging.debug("Removing keys of nodes '%s' from all nodes but itself and"
1858 " master.", ", ".join(all_nodes_to_remove))
1859 for node in all_nodes:
1860 if node == master_node:
1861 logging.debug("Skipping master node '%s'.", master_node)
1862 continue
1863 if node not in online_nodes:
1864 logging.debug("Skipping offline node '%s'.", node)
1865 continue
1866 if node in all_nodes_to_remove:
1867 logging.debug("Skipping node whose key is removed itself '%s'.", node)
1868 continue
1869 ssh_port = ssh_port_map.get(node)
1870 if not ssh_port:
1871 raise errors.OpExecError("No SSH port information available for"
1872 " node '%s', map: %s." %
1873 (node, ssh_port_map))
1874 error_msg_final = ("When removing the key of node '%s', updating the"
1875 " SSH key files of node '%s' failed. Last error"
1876 " was: %s.")
1877 if node in potential_master_candidates:
1878 logging.debug("Updating key setup of potential master candidate node"
1879 " %s.", node)
1880 try:
1881 backoff = 5
1882 utils.RetryByNumberOfTimes(
1883 constants.SSHS_MAX_RETRIES, backoff, errors.SshUpdateError,
1884 run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
1885 ssh_port, pot_mc_data,
1886 debug=ssh_update_debug, verbose=ssh_update_verbose,
1887 use_cluster_key=False, ask_key=False, strict_host_check=False)
1888 except errors.SshUpdateError as last_exception:
1889 error_msg = error_msg_final % (
1890 node_info.name, node, last_exception)
1891 result_msgs.append((node, error_msg))
1892 logging.error(error_msg)
1893
1894 else:
1895 if from_authorized_keys:
1896 logging.debug("Updating key setup of normal node %s.", node)
1897 try:
1898 backoff = 5
1899 utils.RetryByNumberOfTimes(
1900 constants.SSHS_MAX_RETRIES, backoff, errors.SshUpdateError,
1901 run_cmd_fn, cluster_name, node, pathutils.SSH_UPDATE,
1902 ssh_port, base_data,
1903 debug=ssh_update_debug, verbose=ssh_update_verbose,
1904 use_cluster_key=False, ask_key=False, strict_host_check=False)
1905 except errors.SshUpdateError as last_exception:
1906 error_msg = error_msg_final % (
1907 node_info.name, node, last_exception)
1908 result_msgs.append((node, error_msg))
1909 logging.error(error_msg)
1910
1911 for node_info in node_list:
1912 if node_info.clear_authorized_keys or node_info.from_public_keys or \
1913 node_info.clear_public_keys:
1914 data = {}
1915 _InitSshUpdateData(data, noded_cert_file, ssconf_store)
1916 cluster_name = data[constants.SSHS_CLUSTER_NAME]
1917 ssh_port = ssh_port_map.get(node_info.name)
1918 if not ssh_port:
1919 raise errors.OpExecError("No SSH port information available for"
1920 " node '%s', which is leaving the cluster.")
1921
1922 if node_info.clear_authorized_keys:
1923
1924
1925
1926 other_master_candidate_uuids = [uuid for uuid in master_candidate_uuids
1927 if uuid != node_info.uuid]
1928 candidate_keys = ssh.QueryPubKeyFile(other_master_candidate_uuids,
1929 key_file=pub_key_file)
1930 data[constants.SSHS_SSH_AUTHORIZED_KEYS] = \
1931 (constants.SSHS_REMOVE, candidate_keys)
1932
1933 if node_info.clear_public_keys:
1934 data[constants.SSHS_SSH_PUBLIC_KEYS] = \
1935 (constants.SSHS_CLEAR, {})
1936 elif node_info.from_public_keys:
1937
1938
1939
1940 if all_keys_to_remove:
1941 data[constants.SSHS_SSH_PUBLIC_KEYS] = \
1942 (constants.SSHS_REMOVE, all_keys_to_remove)
1943
1944
1945 if not (constants.SSHS_SSH_PUBLIC_KEYS in data or
1946 constants.SSHS_SSH_AUTHORIZED_KEYS in data):
1947 return
1948
1949 logging.debug("Updating SSH key setup of target node '%s'.",
1950 node_info.name)
1951 try:
1952 backoff = 5
1953 utils.RetryByNumberOfTimes(
1954 constants.SSHS_MAX_RETRIES, backoff,
1955 errors.SshUpdateError,
1956 run_cmd_fn, cluster_name, node_info.name, pathutils.SSH_UPDATE,
1957 ssh_port, data,
1958 debug=ssh_update_debug, verbose=ssh_update_verbose,
1959 use_cluster_key=False, ask_key=False, strict_host_check=False)
1960 except errors.SshUpdateError as last_exception:
1961 result_msgs.append(
1962 (node_info.name,
1963 ("Removing SSH keys from node '%s' failed."
1964 " This can happen when the node is already unreachable."
1965 " Error: %s" % (node_info.name, last_exception))))
1966
1967 if all_keys_to_remove and from_public_keys:
1968 for node_uuid in nodes_remove_from_public_keys:
1969 ssh.RemovePublicKey(node_uuid, key_file=pub_key_file)
1970
1971 return result_msgs
1972
1978 """Removes a SSH key from the master's public key file.
1979
1980 This is an operation that is only used to clean up after failed operations
1981 (for example failed hooks before adding a node). To avoid abuse of this
1982 function (and the matching RPC call), we add a safety check to make sure
1983 that only stray keys can be removed that belong to nodes that are not
1984 in the cluster (anymore).
1985
1986 @type node_name: string
1987 @param node_name: the name of the node whose key is removed
1988
1989 """
1990 if not ssconf_store:
1991 ssconf_store = ssconf.SimpleStore()
1992
1993 node_list = ssconf_store.GetNodeList()
1994
1995 if node_name in node_list:
1996 raise errors.SshUpdateError("Cannot remove key of node '%s',"
1997 " because it still belongs to the cluster."
1998 % node_name)
1999
2000 keys_by_name = ssh.QueryPubKeyFile([node_name], key_file=pub_key_file)
2001 if not keys_by_name or node_name not in keys_by_name:
2002 logging.info("The node '%s' whose key is supposed to be removed does not"
2003 " have an entry in the public key file. Hence, there is"
2004 " nothing left to do.", node_name)
2005
2006 ssh.RemovePublicKey(node_name, key_file=pub_key_file)
2007
2016 """Generates the root SSH key pair on the node.
2017
2018 @type node_name: str
2019 @param node_name: name of the node whose key is remove
2020 @type ssh_port_map: dict of str to int
2021 @param ssh_port_map: mapping of node names to their SSH port
2022 @type ssh_key_type: One of L{constants.SSHK_ALL}
2023 @param ssh_key_type: the type of SSH key to be generated
2024 @type ssh_key_bits: int
2025 @param ssh_key_bits: the length of the key to be generated
2026
2027 """
2028 if not ssconf_store:
2029 ssconf_store = ssconf.SimpleStore()
2030
2031 data = {}
2032 _InitSshUpdateData(data, noded_cert_file, ssconf_store)
2033 cluster_name = data[constants.SSHS_CLUSTER_NAME]
2034 data[constants.SSHS_GENERATE] = (ssh_key_type, ssh_key_bits, suffix)
2035
2036 run_cmd_fn(cluster_name, node_name, pathutils.SSH_UPDATE,
2037 ssh_port_map.get(node_name), data,
2038 debug=ssh_update_debug, verbose=ssh_update_verbose,
2039 use_cluster_key=False, ask_key=False, strict_host_check=False)
2040
2043 master_node_uuids = [node_uuid for (node_uuid, node_name)
2044 in node_uuid_name_map
2045 if node_name == master_node_name]
2046 if len(master_node_uuids) != 1:
2047 raise errors.SshUpdateError("No (unique) master UUID found. Master node"
2048 " name: '%s', Master UUID: '%s'" %
2049 (master_node_name, master_node_uuids))
2050 return master_node_uuids[0]
2051
2054 old_master_keys_by_uuid = ssh.QueryPubKeyFile([master_node_uuid],
2055 key_file=pub_key_file)
2056 if not old_master_keys_by_uuid:
2057 raise errors.SshUpdateError("No public key of the master node (UUID '%s')"
2058 " found, not generating a new key."
2059 % master_node_uuid)
2060 return old_master_keys_by_uuid
2061
2062
2063 -def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
2064 potential_master_candidates, old_key_type, new_key_type,
2065 new_key_bits,
2066 ganeti_pub_keys_file=pathutils.SSH_PUB_KEYS,
2067 ssconf_store=None,
2068 noded_cert_file=pathutils.NODED_CERT_FILE,
2069 run_cmd_fn=ssh.RunSshCmdWithStdin,
2070 ssh_update_debug=False,
2071 ssh_update_verbose=False):
2072 """Renews all SSH keys and updates authorized_keys and ganeti_pub_keys.
2073
2074 @type node_uuids: list of str
2075 @param node_uuids: list of node UUIDs whose keys should be renewed
2076 @type node_names: list of str
2077 @param node_names: list of node names whose keys should be removed. This list
2078 should match the C{node_uuids} parameter
2079 @type master_candidate_uuids: list of str
2080 @param master_candidate_uuids: list of UUIDs of master candidates or
2081 master node
2082 @type old_key_type: One of L{constants.SSHK_ALL}
2083 @param old_key_type: the type of SSH key already present on nodes
2084 @type new_key_type: One of L{constants.SSHK_ALL}
2085 @param new_key_type: the type of SSH key to be generated
2086 @type new_key_bits: int
2087 @param new_key_bits: the length of the key to be generated
2088 @type ganeti_pub_keys_file: str
2089 @param ganeti_pub_keys_file: file path of the the public key file
2090 @type noded_cert_file: str
2091 @param noded_cert_file: path of the noded SSL certificate file
2092 @type run_cmd_fn: function
2093 @param run_cmd_fn: function to run commands on remote nodes via SSH
2094 @raises ProgrammerError: if node_uuids and node_names don't match;
2095 SshUpdateError if a node's key is missing from the public key file,
2096 if a node's new SSH key could not be fetched from it, if there is
2097 none or more than one entry in the public key list for the master
2098 node.
2099
2100 """
2101 if not ssconf_store:
2102 ssconf_store = ssconf.SimpleStore()
2103 cluster_name = ssconf_store.GetClusterName()
2104
2105 if not len(node_uuids) == len(node_names):
2106 raise errors.ProgrammerError("List of nodes UUIDs and node names"
2107 " does not match in length.")
2108
2109 old_pub_keyfile = ssh.GetSshPubKeyFilename(old_key_type)
2110 new_pub_keyfile = ssh.GetSshPubKeyFilename(new_key_type)
2111 old_master_key = ssh.ReadLocalSshPubKeys([old_key_type])
2112
2113 node_uuid_name_map = zip(node_uuids, node_names)
2114
2115 master_node_name = ssconf_store.GetMasterNode()
2116 master_node_uuid = _GetMasterNodeUUID(node_uuid_name_map, master_node_name)
2117 ssh_port_map = ssconf_store.GetSshPortMap()
2118
2119
2120
2121 all_node_errors = []
2122
2123
2124
2125
2126 node_keys_to_add = []
2127
2128
2129 node_list = []
2130
2131
2132 node_info_to_remove = []
2133
2134 for node_uuid, node_name in node_uuid_name_map:
2135 if node_name == master_node_name:
2136 continue
2137 master_candidate = node_uuid in master_candidate_uuids
2138 potential_master_candidate = node_name in potential_master_candidates
2139 node_list.append((node_uuid, node_name, master_candidate,
2140 potential_master_candidate))
2141
2142 if master_candidate:
2143 logging.debug("Fetching old SSH key from node '%s'.", node_name)
2144 old_pub_key = ssh.ReadRemoteSshPubKey(old_pub_keyfile,
2145 node_name, cluster_name,
2146 ssh_port_map[node_name],
2147 False,
2148 False)
2149 if old_pub_key != old_master_key:
2150
2151
2152
2153
2154
2155 node_info_to_remove.append(SshRemoveNodeInfo(
2156 uuid=node_uuid,
2157 name=node_name,
2158 from_authorized_keys=master_candidate,
2159 from_public_keys=False,
2160 clear_authorized_keys=False,
2161 clear_public_keys=False))
2162 else:
2163 logging.debug("Old key of node '%s' is the same as the current master"
2164 " key. Not deleting that key on the node.", node_name)
2165
2166 logging.debug("Removing old SSH keys of all master candidates.")
2167 if node_info_to_remove:
2168 node_errors = RemoveNodeSshKeyBulk(
2169 node_info_to_remove,
2170 master_candidate_uuids,
2171 potential_master_candidates,
2172 master_uuid=master_node_uuid,
2173 pub_key_file=ganeti_pub_keys_file,
2174 ssconf_store=ssconf_store,
2175 noded_cert_file=noded_cert_file,
2176 run_cmd_fn=run_cmd_fn,
2177 ssh_update_debug=ssh_update_debug,
2178 ssh_update_verbose=ssh_update_verbose)
2179 if node_errors:
2180 all_node_errors = all_node_errors + node_errors
2181
2182 for (node_uuid, node_name, master_candidate, potential_master_candidate) \
2183 in node_list:
2184
2185 logging.debug("Generating new SSH key for node '%s'.", node_name)
2186 _GenerateNodeSshKey(node_name, ssh_port_map, new_key_type, new_key_bits,
2187 ssconf_store=ssconf_store,
2188 noded_cert_file=noded_cert_file,
2189 run_cmd_fn=run_cmd_fn,
2190 ssh_update_verbose=ssh_update_verbose,
2191 ssh_update_debug=ssh_update_debug)
2192
2193 try:
2194 logging.debug("Fetching newly created SSH key from node '%s'.", node_name)
2195 pub_key = ssh.ReadRemoteSshPubKey(new_pub_keyfile,
2196 node_name, cluster_name,
2197 ssh_port_map[node_name],
2198 False,
2199 False)
2200 except:
2201 raise errors.SshUpdateError("Could not fetch key of node %s"
2202 " (UUID %s)" % (node_name, node_uuid))
2203
2204 if potential_master_candidate:
2205 ssh.RemovePublicKey(node_uuid, key_file=ganeti_pub_keys_file)
2206 ssh.AddPublicKey(node_uuid, pub_key, key_file=ganeti_pub_keys_file)
2207
2208 node_info = SshAddNodeInfo(name=node_name,
2209 uuid=node_uuid,
2210 to_authorized_keys=master_candidate,
2211 to_public_keys=potential_master_candidate,
2212 get_public_keys=True)
2213 node_keys_to_add.append(node_info)
2214
2215 node_errors = AddNodeSshKeyBulk(
2216 node_keys_to_add, potential_master_candidates,
2217 pub_key_file=ganeti_pub_keys_file, ssconf_store=ssconf_store,
2218 noded_cert_file=noded_cert_file,
2219 run_cmd_fn=run_cmd_fn,
2220 ssh_update_debug=ssh_update_debug,
2221 ssh_update_verbose=ssh_update_verbose)
2222 if node_errors:
2223 all_node_errors = all_node_errors + node_errors
2224
2225
2226
2227
2228 old_master_keys_by_uuid = _GetOldMasterKeys(master_node_uuid,
2229 ganeti_pub_keys_file)
2230
2231
2232 logging.debug("Generate new ssh key of master.")
2233 _GenerateNodeSshKey(master_node_name, ssh_port_map,
2234 new_key_type, new_key_bits,
2235 ssconf_store=ssconf_store,
2236 noded_cert_file=noded_cert_file,
2237 run_cmd_fn=run_cmd_fn,
2238 suffix=constants.SSHS_MASTER_SUFFIX,
2239 ssh_update_debug=ssh_update_debug,
2240 ssh_update_verbose=ssh_update_verbose)
2241
2242 new_master_keys = ssh.ReadLocalSshPubKeys(
2243 [new_key_type], suffix=constants.SSHS_MASTER_SUFFIX)
2244
2245
2246 ssh.RemovePublicKey(master_node_uuid, key_file=ganeti_pub_keys_file)
2247 for pub_key in new_master_keys:
2248 ssh.AddPublicKey(master_node_uuid, pub_key, key_file=ganeti_pub_keys_file)
2249
2250
2251 logging.debug("Add new master key to all nodes.")
2252 node_errors = AddNodeSshKey(
2253 master_node_uuid, master_node_name, potential_master_candidates,
2254 to_authorized_keys=True, to_public_keys=True,
2255 get_public_keys=False, pub_key_file=ganeti_pub_keys_file,
2256 ssconf_store=ssconf_store, noded_cert_file=noded_cert_file,
2257 run_cmd_fn=run_cmd_fn,
2258 ssh_update_debug=ssh_update_debug,
2259 ssh_update_verbose=ssh_update_verbose)
2260 if node_errors:
2261 all_node_errors = all_node_errors + node_errors
2262
2263
2264 ssh.ReplaceSshKeys(new_key_type, new_key_type,
2265 src_key_suffix=constants.SSHS_MASTER_SUFFIX)
2266
2267
2268 (auth_key_file, _) = \
2269 ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
2270 ssh.RemoveAuthorizedKeys(auth_key_file,
2271 old_master_keys_by_uuid[master_node_uuid])
2272
2273
2274 logging.debug("Remove the old master key from all nodes.")
2275 node_errors = RemoveNodeSshKey(
2276 master_node_uuid, master_node_name, master_candidate_uuids,
2277 potential_master_candidates,
2278 keys_to_remove=old_master_keys_by_uuid, from_authorized_keys=True,
2279 from_public_keys=False, clear_authorized_keys=False,
2280 clear_public_keys=False,
2281 pub_key_file=ganeti_pub_keys_file,
2282 ssconf_store=ssconf_store,
2283 noded_cert_file=noded_cert_file,
2284 run_cmd_fn=run_cmd_fn,
2285 ssh_update_debug=ssh_update_debug,
2286 ssh_update_verbose=ssh_update_verbose)
2287 if node_errors:
2288 all_node_errors = all_node_errors + node_errors
2289
2290 return all_node_errors
2291
2294 """Return the size of the given block devices
2295
2296 @type devices: list
2297 @param devices: list of block device nodes to query
2298 @rtype: dict
2299 @return:
2300 dictionary of all block devices under /dev (key). The value is their
2301 size in MiB.
2302
2303 {'/dev/disk/by-uuid/123456-12321231-312312-312': 124}
2304
2305 """
2306 DEV_PREFIX = "/dev/"
2307 blockdevs = {}
2308
2309 for devpath in devices:
2310 if not utils.IsBelowDir(DEV_PREFIX, devpath):
2311 continue
2312
2313 try:
2314 st = os.stat(devpath)
2315 except EnvironmentError, err:
2316 logging.warning("Error stat()'ing device %s: %s", devpath, str(err))
2317 continue
2318
2319 if stat.S_ISBLK(st.st_mode):
2320 result = utils.RunCmd(["blockdev", "--getsize64", devpath])
2321 if result.failed:
2322
2323 logging.warning("Cannot get size for block device %s", devpath)
2324 continue
2325
2326 size = int(result.stdout) / (1024 * 1024)
2327 blockdevs[devpath] = size
2328 return blockdevs
2329
2332 """Compute list of logical volumes and their size.
2333
2334 @type vg_names: list
2335 @param vg_names: the volume groups whose LVs we should list, or
2336 empty for all volume groups
2337 @rtype: dict
2338 @return:
2339 dictionary of all partions (key) with value being a tuple of
2340 their size (in MiB), inactive and online status::
2341
2342 {'xenvg/test1': ('20.06', True, True)}
2343
2344 in case of errors, a string is returned with the error
2345 details.
2346
2347 """
2348 lvs = {}
2349 sep = "|"
2350 if not vg_names:
2351 vg_names = []
2352 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
2353 "--separator=%s" % sep,
2354 "-ovg_name,lv_name,lv_size,lv_attr"] + vg_names)
2355 if result.failed:
2356 _Fail("Failed to list logical volumes, lvs output: %s", result.output)
2357
2358 for line in result.stdout.splitlines():
2359 line = line.strip()
2360 match = _LVSLINE_REGEX.match(line)
2361 if not match:
2362 logging.error("Invalid line returned from lvs output: '%s'", line)
2363 continue
2364 vg_name, name, size, attr = match.groups()
2365 inactive = attr[4] == "-"
2366 online = attr[5] == "o"
2367 virtual = attr[0] == "v"
2368 if virtual:
2369
2370
2371 continue
2372 lvs[vg_name + "/" + name] = (size, inactive, online)
2373
2374 return lvs
2375
2378 """List the volume groups and their size.
2379
2380 @rtype: dict
2381 @return: dictionary with keys volume name and values the
2382 size of the volume
2383
2384 """
2385 return utils.ListVolumeGroups()
2386
2389 """List all volumes on this node.
2390
2391 @rtype: list
2392 @return:
2393 A list of dictionaries, each having four keys:
2394 - name: the logical volume name,
2395 - size: the size of the logical volume
2396 - dev: the physical device on which the LV lives
2397 - vg: the volume group to which it belongs
2398
2399 In case of errors, we return an empty list and log the
2400 error.
2401
2402 Note that since a logical volume can live on multiple physical
2403 volumes, the resulting list might include a logical volume
2404 multiple times.
2405
2406 """
2407 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
2408 "--separator=|",
2409 "--options=lv_name,lv_size,devices,vg_name"])
2410 if result.failed:
2411 _Fail("Failed to list logical volumes, lvs output: %s",
2412 result.output)
2413
2414 def parse_dev(dev):
2415 return dev.split("(")[0]
2416
2417 def handle_dev(dev):
2418 return [parse_dev(x) for x in dev.split(",")]
2419
2420 def map_line(line):
2421 line = [v.strip() for v in line]
2422 return [{"name": line[0], "size": line[1],
2423 "dev": dev, "vg": line[3]} for dev in handle_dev(line[2])]
2424
2425 all_devs = []
2426 for line in result.stdout.splitlines():
2427 if line.count("|") >= 3:
2428 all_devs.extend(map_line(line.split("|")))
2429 else:
2430 logging.warning("Strange line in the output from lvs: '%s'", line)
2431 return all_devs
2432
2435 """Check if a list of bridges exist on the current node.
2436
2437 @rtype: boolean
2438 @return: C{True} if all of them exist, C{False} otherwise
2439
2440 """
2441 missing = []
2442 for bridge in bridges_list:
2443 if not utils.BridgeExists(bridge):
2444 missing.append(bridge)
2445
2446 if missing:
2447 _Fail("Missing bridges %s", utils.CommaJoin(missing))
2448
2452 """Provides a list of instances of the given hypervisor.
2453
2454 @type hname: string
2455 @param hname: name of the hypervisor
2456 @type hvparams: dict of strings
2457 @param hvparams: hypervisor parameters for the given hypervisor
2458 @type get_hv_fn: function
2459 @param get_hv_fn: function that returns a hypervisor for the given hypervisor
2460 name; optional parameter to increase testability
2461
2462 @rtype: list
2463 @return: a list of all running instances on the current node
2464 - instance1.example.com
2465 - instance2.example.com
2466
2467 """
2468 try:
2469 return get_hv_fn(hname).ListInstances(hvparams=hvparams)
2470 except errors.HypervisorError, err:
2471 _Fail("Error enumerating instances (hypervisor %s): %s",
2472 hname, err, exc=True)
2473
2477 """Provides a list of instances.
2478
2479 @type hypervisor_list: list
2480 @param hypervisor_list: the list of hypervisors to query information
2481 @type all_hvparams: dict of dict of strings
2482 @param all_hvparams: a dictionary mapping hypervisor types to respective
2483 cluster-wide hypervisor parameters
2484 @type get_hv_fn: function
2485 @param get_hv_fn: function that returns a hypervisor for the given hypervisor
2486 name; optional parameter to increase testability
2487
2488 @rtype: list
2489 @return: a list of all running instances on the current node
2490 - instance1.example.com
2491 - instance2.example.com
2492
2493 """
2494 results = []
2495 for hname in hypervisor_list:
2496 hvparams = all_hvparams[hname]
2497 results.extend(GetInstanceListForHypervisor(hname, hvparams=hvparams,
2498 get_hv_fn=get_hv_fn))
2499 return results
2500
2503 """Gives back the information about an instance as a dictionary.
2504
2505 @type instance: string
2506 @param instance: the instance name
2507 @type hname: string
2508 @param hname: the hypervisor type of the instance
2509 @type hvparams: dict of strings
2510 @param hvparams: the instance's hvparams
2511
2512 @rtype: dict
2513 @return: dictionary with the following keys:
2514 - memory: memory size of instance (int)
2515 - state: state of instance (HvInstanceState)
2516 - time: cpu time of instance (float)
2517 - vcpus: the number of vcpus (int)
2518
2519 """
2520 output = {}
2521
2522 iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance,
2523 hvparams=hvparams)
2524 if iinfo is not None:
2525 output["memory"] = iinfo[2]
2526 output["vcpus"] = iinfo[3]
2527 output["state"] = iinfo[4]
2528 output["time"] = iinfo[5]
2529
2530 return output
2531
2534 """Computes whether an instance can be migrated.
2535
2536 @type instance: L{objects.Instance}
2537 @param instance: object representing the instance to be checked.
2538
2539 @rtype: tuple
2540 @return: tuple of (result, description) where:
2541 - result: whether the instance can be migrated or not
2542 - description: a description of the issue, if relevant
2543
2544 """
2545 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2546 iname = instance.name
2547 if iname not in hyper.ListInstances(hvparams=instance.hvparams):
2548 _Fail("Instance %s is not running", iname)
2549
2552 """Gather data about all instances.
2553
2554 This is the equivalent of L{GetInstanceInfo}, except that it
2555 computes data for all instances at once, thus being faster if one
2556 needs data about more than one instance.
2557
2558 @type hypervisor_list: list
2559 @param hypervisor_list: list of hypervisors to query for instance data
2560 @type all_hvparams: dict of dict of strings
2561 @param all_hvparams: mapping of hypervisor names to hvparams
2562
2563 @rtype: dict
2564 @return: dictionary of instance: data, with data having the following keys:
2565 - memory: memory size of instance (int)
2566 - state: xen state of instance (string)
2567 - time: cpu time of instance (float)
2568 - vcpus: the number of vcpus
2569
2570 """
2571 output = {}
2572 for hname in hypervisor_list:
2573 hvparams = all_hvparams[hname]
2574 iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
2575 if iinfo:
2576 for name, _, memory, vcpus, state, times in iinfo:
2577 value = {
2578 "memory": memory,
2579 "vcpus": vcpus,
2580 "state": state,
2581 "time": times,
2582 }
2583 if name in output:
2584
2585
2586
2587 for key in "memory", "vcpus":
2588 if value[key] != output[name][key]:
2589 _Fail("Instance %s is running twice"
2590 " with different parameters", name)
2591 output[name] = value
2592
2593 return output
2594
2598 """Gather data about the console access of a set of instances of this node.
2599
2600 This function assumes that the caller already knows which instances are on
2601 this node, by calling a function such as L{GetAllInstancesInfo} or
2602 L{GetInstanceList}.
2603
2604 For every instance, a large amount of configuration data needs to be
2605 provided to the hypervisor interface in order to receive the console
2606 information. Whether this could or should be cut down can be discussed.
2607 The information is provided in a dictionary indexed by instance name,
2608 allowing any number of instance queries to be done.
2609
2610 @type instance_param_dict: dict of string to tuple of dictionaries, where the
2611 dictionaries represent: L{objects.Instance}, L{objects.Node},
2612 L{objects.NodeGroup}, HvParams, BeParams
2613 @param instance_param_dict: mapping of instance name to parameters necessary
2614 for console information retrieval
2615
2616 @rtype: dict
2617 @return: dictionary of instance: data, with data having the following keys:
2618 - instance: instance name
2619 - kind: console kind
2620 - message: used with kind == CONS_MESSAGE, indicates console to be
2621 unavailable, supplies error message
2622 - host: host to connect to
2623 - port: port to use
2624 - user: user for login
2625 - command: the command, broken into parts as an array
2626 - display: unknown, potentially unused?
2627
2628 """
2629
2630 output = {}
2631 for inst_name in instance_param_dict:
2632 instance = instance_param_dict[inst_name]["instance"]
2633 pnode = instance_param_dict[inst_name]["node"]
2634 group = instance_param_dict[inst_name]["group"]
2635 hvparams = instance_param_dict[inst_name]["hvParams"]
2636 beparams = instance_param_dict[inst_name]["beParams"]
2637
2638 instance = objects.Instance.FromDict(instance)
2639 pnode = objects.Node.FromDict(pnode)
2640 group = objects.NodeGroup.FromDict(group)
2641
2642 h = get_hv_fn(instance.hypervisor)
2643 output[inst_name] = h.GetInstanceConsole(instance, pnode, group,
2644 hvparams, beparams).ToDict()
2645
2646 return output
2647
2650 """Compute the OS log filename for a given instance and operation.
2651
2652 The instance name and os name are passed in as strings since not all
2653 operations have these as part of an instance object.
2654
2655 @type kind: string
2656 @param kind: the operation type (e.g. add, import, etc.)
2657 @type os_name: string
2658 @param os_name: the os name
2659 @type instance: string
2660 @param instance: the name of the instance being imported/added/etc.
2661 @type component: string or None
2662 @param component: the name of the component of the instance being
2663 transferred
2664
2665 """
2666
2667 if component:
2668 assert "/" not in component
2669 c_msg = "-%s" % component
2670 else:
2671 c_msg = ""
2672 base = ("%s-%s-%s%s-%s.log" %
2673 (kind, os_name, instance, c_msg, utils.TimestampForFilename()))
2674 return utils.PathJoin(pathutils.LOG_OS_DIR, base)
2675
2678 """Add an OS to an instance.
2679
2680 @type instance: L{objects.Instance}
2681 @param instance: Instance whose OS is to be installed
2682 @type reinstall: boolean
2683 @param reinstall: whether this is an instance reinstall
2684 @type debug: integer
2685 @param debug: debug level, passed to the OS scripts
2686 @rtype: None
2687
2688 """
2689 inst_os = OSFromDisk(instance.os)
2690
2691 create_env = OSEnvironment(instance, inst_os, debug)
2692 if reinstall:
2693 create_env["INSTANCE_REINSTALL"] = "1"
2694
2695 logfile = _InstanceLogName("add", instance.os, instance.name, None)
2696
2697 result = utils.RunCmd([inst_os.create_script], env=create_env,
2698 cwd=inst_os.path, output=logfile, reset_env=True)
2699 if result.failed:
2700 logging.error("os create command '%s' returned error: %s, logfile: %s,"
2701 " output: %s", result.cmd, result.fail_reason, logfile,
2702 result.output)
2703 lines = [utils.SafeEncode(val)
2704 for val in utils.TailFile(logfile, lines=20)]
2705 _Fail("OS create script failed (%s), last lines in the"
2706 " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
2707
2710 """Run the OS rename script for an instance.
2711
2712 @type instance: L{objects.Instance}
2713 @param instance: Instance whose OS is to be installed
2714 @type old_name: string
2715 @param old_name: previous instance name
2716 @type debug: integer
2717 @param debug: debug level, passed to the OS scripts
2718 @rtype: boolean
2719 @return: the success of the operation
2720
2721 """
2722 inst_os = OSFromDisk(instance.os)
2723
2724 rename_env = OSEnvironment(instance, inst_os, debug)
2725 rename_env["OLD_INSTANCE_NAME"] = old_name
2726
2727 logfile = _InstanceLogName("rename", instance.os,
2728 "%s-%s" % (old_name, instance.name), None)
2729
2730 result = utils.RunCmd([inst_os.rename_script], env=rename_env,
2731 cwd=inst_os.path, output=logfile, reset_env=True)
2732
2733 if result.failed:
2734 logging.error("os create command '%s' returned error: %s output: %s",
2735 result.cmd, result.fail_reason, result.output)
2736 lines = [utils.SafeEncode(val)
2737 for val in utils.TailFile(logfile, lines=20)]
2738 _Fail("OS rename script failed (%s), last lines in the"
2739 " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
2740
2743 """Returns symlink path for block device.
2744
2745 """
2746 if _dir is None:
2747 _dir = pathutils.DISK_LINKS_DIR
2748
2749 assert idx is not None or uuid is not None
2750
2751
2752 if uuid:
2753 ident = uuid
2754 else:
2755 ident = idx
2756
2757 return utils.PathJoin(_dir,
2758 ("%s%s%s" %
2759 (instance_name, constants.DISK_SEPARATOR, ident)))
2760
2763 """Set up symlinks to a instance's block device.
2764
2765 This is an auxiliary function run when an instance is start (on the primary
2766 node) or when an instance is migrated (on the target node).
2767
2768
2769 @param instance_name: the name of the target instance
2770 @param device_path: path of the physical block device, on the node
2771 @param idx: the disk index
2772 @param uuid: the disk uuid
2773 @return: absolute path to the disk's symlink
2774
2775 """
2776
2777 if not device_path:
2778 return None
2779
2780 link_name = _GetBlockDevSymlinkPath(instance_name, idx, uuid)
2781
2782 try:
2783 os.symlink(device_path, link_name)
2784 except OSError, err:
2785 if err.errno == errno.EEXIST:
2786 if (not os.path.islink(link_name) or
2787 os.readlink(link_name) != device_path):
2788 os.remove(link_name)
2789 os.symlink(device_path, link_name)
2790 else:
2791 raise
2792
2793 return link_name
2794
2797 """Remove the block device symlinks belonging to the given instance.
2798
2799 """
2800 def _remove_symlink(link_name):
2801 if os.path.islink(link_name):
2802 try:
2803 os.remove(link_name)
2804 except OSError:
2805 logging.exception("Can't remove symlink '%s'", link_name)
2806
2807 for idx, disk in enumerate(disks):
2808 link_name = _GetBlockDevSymlinkPath(instance_name, uuid=disk.uuid)
2809 _remove_symlink(link_name)
2810
2811 link_name = _GetBlockDevSymlinkPath(instance_name, idx=idx)
2812 _remove_symlink(link_name)
2813
2816 """Get the URI for the device.
2817
2818 @type instance: L{objects.Instance}
2819 @param instance: the instance which disk belongs to
2820 @type disk: L{objects.Disk}
2821 @param disk: the target disk object
2822 @type device: L{bdev.BlockDev}
2823 @param device: the corresponding BlockDevice
2824 @rtype: string
2825 @return: the device uri if any else None
2826
2827 """
2828 access_mode = disk.params.get(constants.LDP_ACCESS,
2829 constants.DISK_KERNELSPACE)
2830 if access_mode == constants.DISK_USERSPACE:
2831
2832 return device.GetUserspaceAccessUri(instance.hypervisor)
2833 else:
2834 return None
2835
2838 """Set up an instance's block device(s).
2839
2840 This is run on the primary node at instance startup. The block
2841 devices must be already assembled.
2842
2843 @type instance: L{objects.Instance}
2844 @param instance: the instance whose disks we should assemble
2845 @rtype: list
2846 @return: list of (disk_object, link_name, drive_uri)
2847
2848 """
2849 block_devices = []
2850 for idx, disk in enumerate(instance.disks_info):
2851 device = _RecursiveFindBD(disk)
2852 if device is None:
2853 raise errors.BlockDeviceError("Block device '%s' is not set up." %
2854 str(disk))
2855 device.Open()
2856 try:
2857
2858
2859 _SymlinkBlockDev(instance.name, device.dev_path, idx=idx)
2860 link_name = _SymlinkBlockDev(instance.name, device.dev_path,
2861 uuid=disk.uuid)
2862 except OSError, e:
2863 raise errors.BlockDeviceError("Cannot create block device symlink: %s" %
2864 e.strerror)
2865 uri = _CalculateDeviceURI(instance, disk, device)
2866
2867 block_devices.append((disk, link_name, uri))
2868
2869 return block_devices
2870
2876
2882
2883
2884 -def StartInstance(instance, startup_paused, reason, store_reason=True):
2885 """Start an instance.
2886
2887 @type instance: L{objects.Instance}
2888 @param instance: the instance object
2889 @type startup_paused: bool
2890 @param instance: pause instance at startup?
2891 @type reason: list of reasons
2892 @param reason: the reason trail for this startup
2893 @type store_reason: boolean
2894 @param store_reason: whether to store the shutdown reason trail on file
2895 @rtype: None
2896
2897 """
2898 instance_info = _GetInstanceInfo(instance)
2899
2900 if instance_info and not _IsInstanceUserDown(instance_info):
2901 logging.info("Instance '%s' already running, not starting", instance.name)
2902 return
2903
2904 try:
2905 block_devices = _GatherAndLinkBlockDevs(instance)
2906 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2907 hyper.StartInstance(instance, block_devices, startup_paused)
2908 if store_reason:
2909 _StoreInstReasonTrail(instance.name, reason)
2910 except errors.BlockDeviceError, err:
2911 _Fail("Block device error: %s", err, exc=True)
2912 except errors.HypervisorError, err:
2913 _RemoveBlockDevLinks(instance.name, instance.disks_info)
2914 _Fail("Hypervisor error: %s", err, exc=True)
2915
2918 """Shut an instance down.
2919
2920 @note: this functions uses polling with a hardcoded timeout.
2921
2922 @type instance: L{objects.Instance}
2923 @param instance: the instance object
2924 @type timeout: integer
2925 @param timeout: maximum timeout for soft shutdown
2926 @type reason: list of reasons
2927 @param reason: the reason trail for this shutdown
2928 @type store_reason: boolean
2929 @param store_reason: whether to store the shutdown reason trail on file
2930 @rtype: None
2931
2932 """
2933 hyper = hypervisor.GetHypervisor(instance.hypervisor)
2934
2935 if not _GetInstanceInfo(instance):
2936 logging.info("Instance '%s' not running, doing nothing", instance.name)
2937 return
2938
2939 class _TryShutdown(object):
2940 def __init__(self):
2941 self.tried_once = False
2942
2943 def __call__(self):
2944 if not _GetInstanceInfo(instance):
2945 return
2946
2947 try:
2948 hyper.StopInstance(instance, retry=self.tried_once, timeout=timeout)
2949 if store_reason:
2950 _StoreInstReasonTrail(instance.name, reason)
2951 except errors.HypervisorError, err:
2952
2953
2954 if not _GetInstanceInfo(instance):
2955 return
2956
2957 _Fail("Failed to stop instance '%s': %s", instance.name, err)
2958
2959 self.tried_once = True
2960
2961 raise utils.RetryAgain()
2962
2963 try:
2964 utils.Retry(_TryShutdown(), 5, timeout)
2965 except utils.RetryTimeout:
2966
2967 logging.error("Shutdown of '%s' unsuccessful, forcing", instance.name)
2968
2969 try:
2970 hyper.StopInstance(instance, force=True)
2971 except errors.HypervisorError, err:
2972
2973
2974 if _GetInstanceInfo(instance):
2975 _Fail("Failed to force stop instance '%s': %s", instance.name, err)
2976
2977 time.sleep(1)
2978
2979 if _GetInstanceInfo(instance):
2980 _Fail("Could not shutdown instance '%s' even by destroy", instance.name)
2981
2982 try:
2983 hyper.CleanupInstance(instance.name)
2984 except errors.HypervisorError, err:
2985 logging.warning("Failed to execute post-shutdown cleanup step: %s", err)
2986
2987 _RemoveBlockDevLinks(instance.name, instance.disks_info)
2988
2989
2990 -def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
2991 """Reboot an instance.
2992
2993 @type instance: L{objects.Instance}
2994 @param instance: the instance object to reboot
2995 @type reboot_type: str
2996 @param reboot_type: the type of reboot, one the following
2997 constants:
2998 - L{constants.INSTANCE_REBOOT_SOFT}: only reboot the
2999 instance OS, do not recreate the VM
3000 - L{constants.INSTANCE_REBOOT_HARD}: tear down and
3001 restart the VM (at the hypervisor level)
3002 - the other reboot type (L{constants.INSTANCE_REBOOT_FULL}) is
3003 not accepted here, since that mode is handled differently, in
3004 cmdlib, and translates into full stop and start of the
3005 instance (instead of a call_instance_reboot RPC)
3006 @type shutdown_timeout: integer
3007 @param shutdown_timeout: maximum timeout for soft shutdown
3008 @type reason: list of reasons
3009 @param reason: the reason trail for this reboot
3010 @rtype: None
3011
3012 """
3013
3014
3015
3016 if not _GetInstanceInfo(instance):
3017 _Fail("Cannot reboot instance '%s' that is not running", instance.name)
3018
3019 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3020 if reboot_type == constants.INSTANCE_REBOOT_SOFT:
3021 try:
3022 hyper.RebootInstance(instance)
3023 except errors.HypervisorError, err:
3024 _Fail("Failed to soft reboot instance '%s': %s", instance.name, err)
3025 elif reboot_type == constants.INSTANCE_REBOOT_HARD:
3026 try:
3027 InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
3028 result = StartInstance(instance, False, reason, store_reason=False)
3029 _StoreInstReasonTrail(instance.name, reason)
3030 return result
3031 except errors.HypervisorError, err:
3032 _Fail("Failed to hard reboot instance '%s': %s", instance.name, err)
3033 else:
3034 _Fail("Invalid reboot_type received: '%s'", reboot_type)
3035
3056
3071
3074 """Prepare the node to accept an instance.
3075
3076 @type instance: L{objects.Instance}
3077 @param instance: the instance definition
3078 @type info: string/data (opaque)
3079 @param info: migration information, from the source node
3080 @type target: string
3081 @param target: target host (usually ip), on this node
3082
3083 """
3084 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3085 try:
3086 hyper.AcceptInstance(instance, info, target)
3087 except errors.HypervisorError, err:
3088 _Fail("Failed to accept instance: %s", err, exc=True)
3089
3092 """Finalize any preparation to accept an instance.
3093
3094 @type instance: L{objects.Instance}
3095 @param instance: the instance definition
3096 @type info: string/data (opaque)
3097 @param info: migration information, from the source node
3098 @type success: boolean
3099 @param success: whether the migration was a success or a failure
3100
3101 """
3102 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3103 try:
3104 hyper.FinalizeMigrationDst(instance, info, success)
3105 except errors.HypervisorError, err:
3106 _Fail("Failed to finalize migration on the target node: %s", err, exc=True)
3107
3110 """Migrates an instance to another node.
3111
3112 @type cluster_name: string
3113 @param cluster_name: name of the cluster
3114 @type instance: L{objects.Instance}
3115 @param instance: the instance definition
3116 @type target: string
3117 @param target: the target node name
3118 @type live: boolean
3119 @param live: whether the migration should be done live or not (the
3120 interpretation of this parameter is left to the hypervisor)
3121 @raise RPCFail: if migration fails for some reason
3122
3123 """
3124 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3125
3126 try:
3127 hyper.MigrateInstance(cluster_name, instance, target, live)
3128 except errors.HypervisorError, err:
3129 _Fail("Failed to migrate instance: %s", err, exc=True)
3130
3133 """Finalize the instance migration on the source node.
3134
3135 @type instance: L{objects.Instance}
3136 @param instance: the instance definition of the migrated instance
3137 @type success: bool
3138 @param success: whether the migration succeeded or not
3139 @type live: bool
3140 @param live: whether the user requested a live migration or not
3141 @raise RPCFail: If the execution fails for some reason
3142
3143 """
3144 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3145
3146 try:
3147 hyper.FinalizeMigrationSource(instance, success, live)
3148 except Exception, err:
3149 _Fail("Failed to finalize the migration on the source node: %s", err,
3150 exc=True)
3151
3154 """Get the migration status
3155
3156 @type instance: L{objects.Instance}
3157 @param instance: the instance that is being migrated
3158 @rtype: L{objects.MigrationStatus}
3159 @return: the status of the current migration (one of
3160 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
3161 progress info that can be retrieved from the hypervisor
3162 @raise RPCFail: If the migration status cannot be retrieved
3163
3164 """
3165 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3166 try:
3167 return hyper.GetMigrationStatus(instance)
3168 except Exception, err:
3169 _Fail("Failed to get migration status: %s", err, exc=True)
3170
3171
3172 -def HotplugDevice(instance, action, dev_type, device, extra, seq):
3173 """Hotplug a device
3174
3175 Hotplug is currently supported only for KVM Hypervisor.
3176 @type instance: L{objects.Instance}
3177 @param instance: the instance to which we hotplug a device
3178 @type action: string
3179 @param action: the hotplug action to perform
3180 @type dev_type: string
3181 @param dev_type: the device type to hotplug
3182 @type device: either L{objects.NIC} or L{objects.Disk}
3183 @param device: the device object to hotplug
3184 @type extra: tuple
3185 @param extra: extra info used for disk hotplug (disk link, drive uri)
3186 @type seq: int
3187 @param seq: the index of the device from master perspective
3188 @raise RPCFail: in case instance does not have KVM hypervisor
3189
3190 """
3191 hyper = hypervisor.GetHypervisor(instance.hypervisor)
3192 try:
3193 hyper.VerifyHotplugSupport(instance, action, dev_type)
3194 except errors.HotplugError, err:
3195 _Fail("Hotplug is not supported: %s", err)
3196
3197 if action == constants.HOTPLUG_ACTION_ADD:
3198 fn = hyper.HotAddDevice
3199 elif action == constants.HOTPLUG_ACTION_REMOVE:
3200 fn = hyper.HotDelDevice
3201 elif action == constants.HOTPLUG_ACTION_MODIFY:
3202 fn = hyper.HotModDevice
3203 else:
3204 assert action in constants.HOTPLUG_ALL_ACTIONS
3205
3206 return fn(instance, dev_type, device, extra, seq)
3207
3218
3244
3245
3246 -def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
3247 """Creates a block device for an instance.
3248
3249 @type disk: L{objects.Disk}
3250 @param disk: the object describing the disk we should create
3251 @type size: int
3252 @param size: the size of the physical underlying device, in MiB
3253 @type owner: str
3254 @param owner: the name of the instance for which disk is created,
3255 used for device cache data
3256 @type on_primary: boolean
3257 @param on_primary: indicates if it is the primary node or not
3258 @type info: string
3259 @param info: string that will be sent to the physical device
3260 creation, used for example to set (LVM) tags on LVs
3261 @type excl_stor: boolean
3262 @param excl_stor: Whether exclusive_storage is active
3263
3264 @return: the new unique_id of the device (this can sometime be
3265 computed only after creation), or None. On secondary nodes,
3266 it's not required to return anything.
3267
3268 """
3269
3270
3271 clist = []
3272 if disk.children:
3273 for child in disk.children:
3274 try:
3275 crdev = _RecursiveAssembleBD(child, owner, on_primary)
3276 except errors.BlockDeviceError, err:
3277 _Fail("Can't assemble device %s: %s", child, err)
3278 if on_primary or disk.AssembleOnSecondary():
3279
3280
3281 try:
3282
3283 crdev.Open()
3284 except errors.BlockDeviceError, err:
3285 _Fail("Can't make child '%s' read-write: %s", child, err)
3286 clist.append(crdev)
3287
3288 try:
3289 device = bdev.Create(disk, clist, excl_stor)
3290 except errors.BlockDeviceError, err:
3291 _Fail("Can't create block device: %s", err)
3292
3293 if on_primary or disk.AssembleOnSecondary():
3294 try:
3295 device.Assemble()
3296 except errors.BlockDeviceError, err:
3297 _Fail("Can't assemble device after creation, unusual event: %s", err)
3298 if on_primary or disk.OpenOnSecondary():
3299 try:
3300 device.Open(force=True)
3301 except errors.BlockDeviceError, err:
3302 _Fail("Can't make device r/w after creation, unusual event: %s", err)
3303 DevCacheManager.UpdateCache(device.dev_path, owner,
3304 on_primary, disk.iv_name)
3305
3306 device.SetInfo(info)
3307
3308 return device.unique_id
3309
3310
3311 -def _DumpDevice(source_path, target_path, offset, size, truncate):
3312 """This function images/wipes the device using a local file.
3313
3314 @type source_path: string
3315 @param source_path: path of the image or data source (e.g., "/dev/zero")
3316
3317 @type target_path: string
3318 @param target_path: path of the device to image/wipe
3319
3320 @type offset: int
3321 @param offset: offset in MiB in the output file
3322
3323 @type size: int
3324 @param size: maximum size in MiB to write (data source might be smaller)
3325
3326 @type truncate: bool
3327 @param truncate: whether the file should be truncated
3328
3329 @return: None
3330 @raise RPCFail: in case of failure
3331
3332 """
3333
3334
3335
3336 block_size = constants.DD_BLOCK_SIZE
3337
3338 cmd = [constants.DD_CMD, "if=%s" % source_path, "seek=%d" % offset,
3339 "bs=%s" % block_size, "oflag=direct", "of=%s" % target_path,
3340 "count=%d" % size]
3341
3342 if not truncate:
3343 cmd.append("conv=notrunc")
3344
3345 result = utils.RunCmd(cmd)
3346
3347 if result.failed:
3348 _Fail("Dump command '%s' exited with error: %s; output: %s", result.cmd,
3349 result.fail_reason, result.output)
3350
3353 """This function images a device using a downloaded image file.
3354
3355 @type source_url: string
3356 @param source_url: URL of image to dump to disk
3357
3358 @type target_path: string
3359 @param target_path: path of the device to image
3360
3361 @type size: int
3362 @param size: maximum size in MiB to write (data source might be smaller)
3363
3364 @rtype: NoneType
3365 @return: None
3366 @raise RPCFail: in case of download or write failures
3367
3368 """
3369 class DDParams(object):
3370 def __init__(self, current_size, total_size):
3371 self.current_size = current_size
3372 self.total_size = total_size
3373 self.image_size_error = False
3374
3375 def dd_write(ddparams, out):
3376 if ddparams.current_size < ddparams.total_size:
3377 ddparams.current_size += len(out)
3378 target_file.write(out)
3379 else:
3380 ddparams.image_size_error = True
3381 return -1
3382
3383 target_file = open(target_path, "r+")
3384 ddparams = DDParams(0, 1024 * 1024 * size)
3385
3386 curl = pycurl.Curl()
3387 curl.setopt(pycurl.VERBOSE, True)
3388 curl.setopt(pycurl.NOSIGNAL, True)
3389 curl.setopt(pycurl.USERAGENT, http.HTTP_GANETI_VERSION)
3390 curl.setopt(pycurl.URL, source_url)
3391 curl.setopt(pycurl.WRITEFUNCTION, lambda out: dd_write(ddparams, out))
3392
3393 try:
3394 curl.perform()
3395 except pycurl.error:
3396 if ddparams.image_size_error:
3397 _Fail("Disk image larger than the disk")
3398 else:
3399 raise
3400
3401 target_file.close()
3402
3405 """Copies data from source block device to target.
3406
3407 This function gets the export and import commands from the source and
3408 target devices respectively, and then concatenates them to a single
3409 command using a pipe ("|"). Finally, executes the unified command that
3410 will transfer the data between the devices during the disk template
3411 conversion operation.
3412
3413 @type src_disk: L{objects.Disk}
3414 @param src_disk: the disk object we want to copy from
3415 @type target_disk: L{objects.Disk}
3416 @param target_disk: the disk object we want to copy to
3417
3418 @rtype: NoneType
3419 @return: None
3420 @raise RPCFail: in case of failure
3421
3422 """
3423 src_dev = _RecursiveFindBD(src_disk)
3424 if src_dev is None:
3425 _Fail("Cannot copy from device '%s': device not found", src_disk.uuid)
3426
3427 dest_dev = _RecursiveFindBD(target_disk)
3428 if dest_dev is None:
3429 _Fail("Cannot copy to device '%s': device not found", target_disk.uuid)
3430
3431 src_cmd = src_dev.Export()
3432 dest_cmd = dest_dev.Import()
3433 command = "%s | %s" % (utils.ShellQuoteArgs(src_cmd),
3434 utils.ShellQuoteArgs(dest_cmd))
3435
3436 result = utils.RunCmd(command)
3437 if result.failed:
3438 _Fail("Disk conversion command '%s' exited with error: %s; output: %s",
3439 result.cmd, result.fail_reason, result.output)
3440
3443 """Wipes a block device.
3444
3445 @type disk: L{objects.Disk}
3446 @param disk: the disk object we want to wipe
3447 @type offset: int
3448 @param offset: The offset in MiB in the file
3449 @type size: int
3450 @param size: The size in MiB to write
3451
3452 """
3453 try:
3454 rdev = _RecursiveFindBD(disk)
3455 except errors.BlockDeviceError:
3456 rdev = None
3457
3458 if not rdev:
3459 _Fail("Cannot wipe device %s: device not found", disk.iv_name)
3460 if offset < 0:
3461 _Fail("Negative offset")
3462 if size < 0:
3463 _Fail("Negative size")
3464 if offset > rdev.size:
3465 _Fail("Wipe offset is bigger than device size")
3466 if (offset + size) > rdev.size:
3467 _Fail("Wipe offset and size are bigger than device size")
3468
3469 _DumpDevice("/dev/zero", rdev.dev_path, offset, size, True)
3470
3473 """Images a block device either by dumping a local file or
3474 downloading a URL.
3475
3476 @type disk: L{objects.Disk}
3477 @param disk: the disk object we want to image
3478
3479 @type image: string
3480 @param image: file path to the disk image be dumped
3481
3482 @type size: int
3483 @param size: The size in MiB to write
3484
3485 @rtype: NoneType
3486 @return: None
3487 @raise RPCFail: in case of failure
3488
3489 """
3490 if not (utils.IsUrl(image) or os.path.exists(image)):
3491 _Fail("Image '%s' not found", image)
3492
3493 try:
3494 rdev = _RecursiveFindBD(disk)
3495 except errors.BlockDeviceError:
3496 rdev = None
3497
3498 if not rdev:
3499 _Fail("Cannot image device %s: device not found", disk.iv_name)
3500 if size < 0:
3501 _Fail("Negative size")
3502 if size > rdev.size:
3503 _Fail("Image size is bigger than device size")
3504
3505 if utils.IsUrl(image):
3506 _DownloadAndDumpDevice(image, rdev.dev_path, size)
3507 else:
3508 _DumpDevice(image, rdev.dev_path, 0, size, False)
3509
3512 """Pause or resume the sync of the block device.
3513
3514 @type disks: list of L{objects.Disk}
3515 @param disks: the disks object we want to pause/resume
3516 @type pause: bool
3517 @param pause: Wheater to pause or resume
3518
3519 """
3520 success = []
3521 for disk in disks:
3522 try:
3523 rdev = _RecursiveFindBD(disk)
3524 except errors.BlockDeviceError:
3525 rdev = None
3526
3527 if not rdev:
3528 success.append((False, ("Cannot change sync for device %s:"
3529 " device not found" % disk.iv_name)))
3530 continue
3531
3532 result = rdev.PauseResumeSync(pause)
3533
3534 if result:
3535 success.append((result, None))
3536 else:
3537 if pause:
3538 msg = "Pause"
3539 else:
3540 msg = "Resume"
3541 success.append((result, "%s for device %s failed" % (msg, disk.iv_name)))
3542
3543 return success
3544
3547 """Remove a block device.
3548
3549 @note: This is intended to be called recursively.
3550
3551 @type disk: L{objects.Disk}
3552 @param disk: the disk object we should remove
3553 @rtype: boolean
3554 @return: the success of the operation
3555
3556 """
3557 msgs = []
3558 try:
3559 rdev = _RecursiveFindBD(disk)
3560 except errors.BlockDeviceError, err:
3561
3562 logging.info("Can't attach to device %s in remove", disk)
3563 rdev = None
3564 if rdev is not None:
3565 r_path = rdev.dev_path
3566
3567 def _TryRemove():
3568 try:
3569 rdev.Remove()
3570 return []
3571 except errors.BlockDeviceError, err:
3572 return [str(err)]
3573
3574 msgs.extend(utils.SimpleRetry([], _TryRemove,
3575 constants.DISK_REMOVE_RETRY_INTERVAL,
3576 constants.DISK_REMOVE_RETRY_TIMEOUT))
3577
3578 if not msgs:
3579 DevCacheManager.RemoveCache(r_path)
3580
3581 if disk.children:
3582 for child in disk.children:
3583 try:
3584 BlockdevRemove(child)
3585 except RPCFail, err:
3586 msgs.append(str(err))
3587
3588 if msgs:
3589 _Fail("; ".join(msgs))
3590
3593 """Activate a block device for an instance.
3594
3595 This is run on the primary and secondary nodes for an instance.
3596
3597 @note: this function is called recursively.
3598
3599 @type disk: L{objects.Disk}
3600 @param disk: the disk we try to assemble
3601 @type owner: str
3602 @param owner: the name of the instance which owns the disk
3603 @type as_primary: boolean
3604 @param as_primary: if we should make the block device
3605 read/write
3606
3607 @return: the assembled device or None (in case no device
3608 was assembled)
3609 @raise errors.BlockDeviceError: in case there is an error
3610 during the activation of the children or the device
3611 itself
3612
3613 """
3614 children = []
3615 if disk.children:
3616 mcn = disk.ChildrenNeeded()
3617 if mcn == -1:
3618 mcn = 0
3619 else:
3620 mcn = len(disk.children) - mcn
3621 for chld_disk in disk.children:
3622 try:
3623 cdev = _RecursiveAssembleBD(chld_disk, owner, as_primary)
3624 except errors.BlockDeviceError, err:
3625 if children.count(None) >= mcn:
3626 raise
3627 cdev = None
3628 logging.error("Error in child activation (but continuing): %s",
3629 str(err))
3630 children.append(cdev)
3631
3632 if as_primary or disk.AssembleOnSecondary():
3633 r_dev = bdev.Assemble(disk, children)
3634 result = r_dev
3635 if as_primary or disk.OpenOnSecondary():
3636 r_dev.Open()
3637 DevCacheManager.UpdateCache(r_dev.dev_path, owner,
3638 as_primary, disk.iv_name)
3639
3640 else:
3641 result = True
3642 return result
3643
3646 """Activate a block device for an instance.
3647
3648 This is a wrapper over _RecursiveAssembleBD.
3649
3650 @rtype: str or boolean
3651 @return: a tuple with the C{/dev/...} path and the created symlink
3652 for primary nodes, and (C{True}, C{True}) for secondary nodes
3653
3654 """
3655 try:
3656 result = _RecursiveAssembleBD(disk, instance.name, as_primary)
3657 if isinstance(result, BlockDev):
3658
3659 dev_path = result.dev_path
3660 link_name = None
3661 uri = None
3662 if as_primary:
3663
3664
3665 _SymlinkBlockDev(instance.name, dev_path, idx=idx)
3666 link_name = _SymlinkBlockDev(instance.name, dev_path, uuid=disk.uuid)
3667 uri = _CalculateDeviceURI(instance, disk, result)
3668 elif result:
3669 return result, result
3670 else:
3671 _Fail("Unexpected result from _RecursiveAssembleBD")
3672 except errors.BlockDeviceError, err:
3673 _Fail("Error while assembling disk: %s", err, exc=True)
3674 except OSError, err:
3675 _Fail("Error while symlinking disk: %s", err, exc=True)
3676
3677 return dev_path, link_name, uri
3678
3681 """Shut down a block device.
3682
3683 First, if the device is assembled (Attach() is successful), then
3684 the device is shutdown. Then the children of the device are
3685 shutdown.
3686
3687 This function is called recursively. Note that we don't cache the
3688 children or such, as oppossed to assemble, shutdown of different
3689 devices doesn't require that the upper device was active.
3690
3691 @type disk: L{objects.Disk}
3692 @param disk: the description of the disk we should
3693 shutdown
3694 @rtype: None
3695
3696 """
3697 msgs = []
3698 r_dev = _RecursiveFindBD(disk)
3699 if r_dev is not None:
3700 r_path = r_dev.dev_path
3701 try:
3702 r_dev.Shutdown()
3703 DevCacheManager.RemoveCache(r_path)
3704 except errors.BlockDeviceError, err:
3705 msgs.append(str(err))
3706
3707 if disk.children:
3708 for child in disk.children:
3709 try:
3710 BlockdevShutdown(child)
3711 except RPCFail, err:
3712 msgs.append(str(err))
3713
3714 if msgs:
3715 _Fail("; ".join(msgs))
3716
3719 """Extend a mirrored block device.
3720
3721 @type parent_cdev: L{objects.Disk}
3722 @param parent_cdev: the disk to which we should add children
3723 @type new_cdevs: list of L{objects.Disk}
3724 @param new_cdevs: the list of children which we should add
3725 @rtype: None
3726
3727 """
3728 parent_bdev = _RecursiveFindBD(parent_cdev)
3729 if parent_bdev is None:
3730 _Fail("Can't find parent device '%s' in add children", parent_cdev)
3731 new_bdevs = [_RecursiveFindBD(disk) for disk in new_cdevs]
3732 if new_bdevs.count(None) > 0:
3733 _Fail("Can't find new device(s) to add: %s:%s", new_bdevs, new_cdevs)
3734 parent_bdev.AddChildren(new_bdevs)
3735
3738 """Shrink a mirrored block device.
3739
3740 @type parent_cdev: L{objects.Disk}
3741 @param parent_cdev: the disk from which we should remove children
3742 @type new_cdevs: list of L{objects.Disk}
3743 @param new_cdevs: the list of children which we should remove
3744 @rtype: None
3745
3746 """
3747 parent_bdev = _RecursiveFindBD(parent_cdev)
3748 if parent_bdev is None:
3749 _Fail("Can't find parent device '%s' in remove children", parent_cdev)
3750 devs = []
3751 for disk in new_cdevs:
3752 rpath = disk.StaticDevPath()
3753 if rpath is None:
3754 bd = _RecursiveFindBD(disk)
3755 if bd is None:
3756 _Fail("Can't find device %s while removing children", disk)
3757 else:
3758 devs.append(bd.dev_path)
3759 else:
3760 if not utils.IsNormAbsPath(rpath):
3761 _Fail("Strange path returned from StaticDevPath: '%s'", rpath)
3762 devs.append(rpath)
3763 parent_bdev.RemoveChildren(devs)
3764
3767 """Get the mirroring status of a list of devices.
3768
3769 @type disks: list of L{objects.Disk}
3770 @param disks: the list of disks which we should query
3771 @rtype: disk
3772 @return: List of L{objects.BlockDevStatus}, one for each disk
3773 @raise errors.BlockDeviceError: if any of the disks cannot be
3774 found
3775
3776 """
3777 stats = []
3778 for dsk in disks:
3779 rbd = _RecursiveFindBD(dsk)
3780 if rbd is None:
3781 _Fail("Can't find device %s", dsk)
3782
3783 stats.append(rbd.CombinedSyncStatus())
3784
3785 return stats
3786
3789 """Get the mirroring status of a list of devices.
3790
3791 @type disks: list of L{objects.Disk}
3792 @param disks: the list of disks which we should query
3793 @rtype: disk
3794 @return: List of tuples, (bool, status), one for each disk; bool denotes
3795 success/failure, status is L{objects.BlockDevStatus} on success, string
3796 otherwise
3797
3798 """
3799 result = []
3800 for disk in disks:
3801 try:
3802 rbd = _RecursiveFindBD(disk)
3803 if rbd is None:
3804 result.append((False, "Can't find device %s" % disk))
3805 continue
3806
3807 status = rbd.CombinedSyncStatus()
3808 except errors.BlockDeviceError, err:
3809 logging.exception("Error while getting disk status")
3810 result.append((False, str(err)))
3811 else:
3812 result.append((True, status))
3813
3814 assert len(disks) == len(result)
3815
3816 return result
3817
3820 """Check if a device is activated.
3821
3822 If so, return information about the real device.
3823
3824 @type disk: L{objects.Disk}
3825 @param disk: the disk object we need to find
3826
3827 @return: None if the device can't be found,
3828 otherwise the device instance
3829
3830 """
3831 children = []
3832 if disk.children:
3833 for chdisk in disk.children:
3834 children.append(_RecursiveFindBD(chdisk))
3835
3836 return bdev.FindDevice(disk, children)
3837
3840 """Opens the underlying block device of a disk.
3841
3842 @type disk: L{objects.Disk}
3843 @param disk: the disk object we want to open
3844
3845 """
3846 real_disk = _RecursiveFindBD(disk)
3847 if real_disk is None:
3848 _Fail("Block device '%s' is not set up", disk)
3849
3850 real_disk.Open()
3851
3852 return real_disk
3853
3856 """Check if a device is activated.
3857
3858 If it is, return information about the real device.
3859
3860 @type disk: L{objects.Disk}
3861 @param disk: the disk to find
3862 @rtype: None or objects.BlockDevStatus
3863 @return: None if the disk cannot be found, otherwise a the current
3864 information
3865
3866 """
3867 try:
3868 rbd = _RecursiveFindBD(disk)
3869 except errors.BlockDeviceError, err:
3870 _Fail("Failed to find device: %s", err, exc=True)
3871
3872 if rbd is None:
3873 return None
3874
3875 return rbd.GetSyncStatus()
3876
3879 """Computes the size of the given disks.
3880
3881 If a disk is not found, returns None instead.
3882
3883 @type disks: list of L{objects.Disk}
3884 @param disks: the list of disk to compute the size for
3885 @rtype: list
3886 @return: list with elements None if the disk cannot be found,
3887 otherwise the pair (size, spindles), where spindles is None if the
3888 device doesn't support that
3889
3890 """
3891 result = []
3892 for cf in disks:
3893 try:
3894 rbd = _RecursiveFindBD(cf)
3895 except errors.BlockDeviceError:
3896 result.append(None)
3897 continue
3898 if rbd is None:
3899 result.append(None)
3900 else:
3901 result.append(rbd.GetActualDimensions())
3902 return result
3903
3904
3905 -def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
3906 """Write a file to the filesystem.
3907
3908 This allows the master to overwrite(!) a file. It will only perform
3909 the operation if the file belongs to a list of configuration files.
3910
3911 @type file_name: str
3912 @param file_name: the target file name
3913 @type data: str
3914 @param data: the new contents of the file
3915 @type mode: int
3916 @param mode: the mode to give the file (can be None)
3917 @type uid: string
3918 @param uid: the owner of the file
3919 @type gid: string
3920 @param gid: the group of the file
3921 @type atime: float
3922 @param atime: the atime to set on the file (can be None)
3923 @type mtime: float
3924 @param mtime: the mtime to set on the file (can be None)
3925 @rtype: None
3926
3927 """
3928 file_name = vcluster.LocalizeVirtualPath(file_name)
3929
3930 if not os.path.isabs(file_name):
3931 _Fail("Filename passed to UploadFile is not absolute: '%s'", file_name)
3932
3933 if file_name not in _ALLOWED_UPLOAD_FILES:
3934 _Fail("Filename passed to UploadFile not in allowed upload targets: '%s'",
3935 file_name)
3936
3937 raw_data = _Decompress(data)
3938
3939 if not (isinstance(uid, basestring) and isinstance(gid, basestring)):
3940 _Fail("Invalid username/groupname type")
3941
3942 getents = runtime.GetEnts()
3943 uid = getents.LookupUser(uid)
3944 gid = getents.LookupGroup(gid)
3945
3946 utils.SafeWriteFile(file_name, None,
3947 data=raw_data, mode=mode, uid=uid, gid=gid,
3948 atime=atime, mtime=mtime)
3949
3950
3951 -def RunOob(oob_program, command, node, timeout):
3952 """Executes oob_program with given command on given node.
3953
3954 @param oob_program: The path to the executable oob_program
3955 @param command: The command to invoke on oob_program
3956 @param node: The node given as an argument to the program
3957 @param timeout: Timeout after which we kill the oob program
3958
3959 @return: stdout
3960 @raise RPCFail: If execution fails for some reason
3961
3962 """
3963 result = utils.RunCmd([oob_program, command, node], timeout=timeout)
3964
3965 if result.failed:
3966 _Fail("'%s' failed with reason '%s'; output: %s", result.cmd,
3967 result.fail_reason, result.output)
3968
3969 return result.stdout
3970
3973 """Compute and return the API version of a given OS.
3974
3975 This function will try to read the API version of the OS residing in
3976 the 'os_dir' directory.
3977
3978 @type os_dir: str
3979 @param os_dir: the directory in which we should look for the OS
3980 @rtype: tuple
3981 @return: tuple (status, data) with status denoting the validity and
3982 data holding either the valid versions or an error message
3983
3984 """
3985 api_file = utils.PathJoin(os_dir, constants.OS_API_FILE)
3986
3987 try:
3988 st = os.stat(api_file)
3989 except EnvironmentError, err:
3990 return False, ("Required file '%s' not found under path %s: %s" %
3991 (constants.OS_API_FILE, os_dir, utils.ErrnoOrStr(err)))
3992
3993 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
3994 return False, ("File '%s' in %s is not a regular file" %
3995 (constants.OS_API_FILE, os_dir))
3996
3997 try:
3998 api_versions = utils.ReadFile(api_file).splitlines()
3999 except EnvironmentError, err:
4000 return False, ("Error while reading the API version file at %s: %s" %
4001 (api_file, utils.ErrnoOrStr(err)))
4002
4003 try:
4004 api_versions = [int(version.strip()) for version in api_versions]
4005 except (TypeError, ValueError), err:
4006 return False, ("API version(s) can't be converted to integer: %s" %
4007 str(err))
4008
4009 return True, api_versions
4010
4013 """Compute the validity for all OSes.
4014
4015 @type top_dirs: list
4016 @param top_dirs: the list of directories in which to
4017 search (if not given defaults to
4018 L{pathutils.OS_SEARCH_PATH})
4019 @rtype: list of L{objects.OS}
4020 @return: a list of tuples (name, path, status, diagnose, variants,
4021 parameters, api_version) for all (potential) OSes under all
4022 search paths, where:
4023 - name is the (potential) OS name
4024 - path is the full path to the OS
4025 - status True/False is the validity of the OS
4026 - diagnose is the error message for an invalid OS, otherwise empty
4027 - variants is a list of supported OS variants, if any
4028 - parameters is a list of (name, help) parameters, if any
4029 - api_version is a list of support OS API versions
4030
4031 """
4032 if top_dirs is None:
4033 top_dirs = pathutils.OS_SEARCH_PATH
4034
4035 result = []
4036 for dir_name in top_dirs:
4037 if os.path.isdir(dir_name):
4038 try:
4039 f_names = utils.ListVisibleFiles(dir_name)
4040 except EnvironmentError, err:
4041 logging.exception("Can't list the OS directory %s: %s", dir_name, err)
4042 break
4043 for name in f_names:
4044 os_path = utils.PathJoin(dir_name, name)
4045 status, os_inst = _TryOSFromDisk(name, base_dir=dir_name)
4046 if status:
4047 diagnose = ""
4048 variants = os_inst.supported_variants
4049 parameters = os_inst.supported_parameters
4050 api_versions = os_inst.api_versions
4051 trusted = False if os_inst.create_script_untrusted else True
4052 else:
4053 diagnose = os_inst
4054 variants = parameters = api_versions = []
4055 trusted = True
4056 result.append((name, os_path, status, diagnose, variants,
4057 parameters, api_versions, trusted))
4058
4059 return result
4060
4063 """Create an OS instance from disk.
4064
4065 This function will return an OS instance if the given name is a
4066 valid OS name.
4067
4068 @type base_dir: string
4069 @keyword base_dir: Base directory containing OS installations.
4070 Defaults to a search in all the OS_SEARCH_PATH dirs.
4071 @rtype: tuple
4072 @return: success and either the OS instance if we find a valid one,
4073 or error message
4074
4075 """
4076 if base_dir is None:
4077 os_dir = utils.FindFile(name, pathutils.OS_SEARCH_PATH, os.path.isdir)
4078 else:
4079 os_dir = utils.FindFile(name, [base_dir], os.path.isdir)
4080
4081 if os_dir is None:
4082 return False, "Directory for OS %s not found in search path" % name
4083
4084 status, api_versions = _OSOndiskAPIVersion(os_dir)
4085 if not status:
4086
4087 return status, api_versions
4088
4089 if not constants.OS_API_VERSIONS.intersection(api_versions):
4090 return False, ("API version mismatch for path '%s': found %s, want %s." %
4091 (os_dir, api_versions, constants.OS_API_VERSIONS))
4092
4093
4094
4095
4096 os_files = dict.fromkeys(constants.OS_SCRIPTS, True)
4097
4098 os_files[constants.OS_SCRIPT_CREATE] = False
4099 os_files[constants.OS_SCRIPT_CREATE_UNTRUSTED] = False
4100
4101 if max(api_versions) >= constants.OS_API_V15:
4102 os_files[constants.OS_VARIANTS_FILE] = False
4103
4104 if max(api_versions) >= constants.OS_API_V20:
4105 os_files[constants.OS_PARAMETERS_FILE] = True
4106 else:
4107 del os_files[constants.OS_SCRIPT_VERIFY]
4108
4109 for (filename, required) in os_files.items():
4110 os_files[filename] = utils.PathJoin(os_dir, filename)
4111
4112 try:
4113 st = os.stat(os_files[filename])
4114 except EnvironmentError, err:
4115 if err.errno == errno.ENOENT and not required:
4116 del os_files[filename]
4117 continue
4118 return False, ("File '%s' under path '%s' is missing (%s)" %
4119 (filename, os_dir, utils.ErrnoOrStr(err)))
4120
4121 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
4122 return False, ("File '%s' under path '%s' is not a regular file" %
4123 (filename, os_dir))
4124
4125 if filename in constants.OS_SCRIPTS:
4126 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
4127 return False, ("File '%s' under path '%s' is not executable" %
4128 (filename, os_dir))
4129
4130 if not constants.OS_SCRIPT_CREATE in os_files and \
4131 not constants.OS_SCRIPT_CREATE_UNTRUSTED in os_files:
4132 return False, ("A create script (trusted or untrusted) under path '%s'"
4133 " must exist" % os_dir)
4134
4135 create_script = os_files.get(constants.OS_SCRIPT_CREATE, None)
4136 create_script_untrusted = os_files.get(constants.OS_SCRIPT_CREATE_UNTRUSTED,
4137 None)
4138
4139 variants = []
4140 if constants.OS_VARIANTS_FILE in os_files:
4141 variants_file = os_files[constants.OS_VARIANTS_FILE]
4142 try:
4143 variants = \
4144 utils.FilterEmptyLinesAndComments(utils.ReadFile(variants_file))
4145 except EnvironmentError, err:
4146
4147 if err.errno != errno.ENOENT:
4148 return False, ("Error while reading the OS variants file at %s: %s" %
4149 (variants_file, utils.ErrnoOrStr(err)))
4150
4151 parameters = []
4152 if constants.OS_PARAMETERS_FILE in os_files:
4153 parameters_file = os_files[constants.OS_PARAMETERS_FILE]
4154 try:
4155 parameters = utils.ReadFile(parameters_file).splitlines()
4156 except EnvironmentError, err:
4157 return False, ("Error while reading the OS parameters file at %s: %s" %
4158 (parameters_file, utils.ErrnoOrStr(err)))
4159 parameters = [v.split(None, 1) for v in parameters]
4160
4161 os_obj = objects.OS(name=name, path=os_dir,
4162 create_script=create_script,
4163 create_script_untrusted=create_script_untrusted,
4164 export_script=os_files[constants.OS_SCRIPT_EXPORT],
4165 import_script=os_files[constants.OS_SCRIPT_IMPORT],
4166 rename_script=os_files[constants.OS_SCRIPT_RENAME],
4167 verify_script=os_files.get(constants.OS_SCRIPT_VERIFY,
4168 None),
4169 supported_variants=variants,
4170 supported_parameters=parameters,
4171 api_versions=api_versions)
4172 return True, os_obj
4173
4176 """Create an OS instance from disk.
4177
4178 This function will return an OS instance if the given name is a
4179 valid OS name. Otherwise, it will raise an appropriate
4180 L{RPCFail} exception, detailing why this is not a valid OS.
4181
4182 This is just a wrapper over L{_TryOSFromDisk}, which doesn't raise
4183 an exception but returns true/false status data.
4184
4185 @type base_dir: string
4186 @keyword base_dir: Base directory containing OS installations.
4187 Defaults to a search in all the OS_SEARCH_PATH dirs.
4188 @rtype: L{objects.OS}
4189 @return: the OS instance if we find a valid one
4190 @raise RPCFail: if we don't find a valid OS
4191
4192 """
4193 name_only = objects.OS.GetName(name)
4194 status, payload = _TryOSFromDisk(name_only, base_dir)
4195
4196 if not status:
4197 _Fail(payload)
4198
4199 return payload
4200
4201
4202 -def OSCoreEnv(os_name, inst_os, os_params, debug=0):
4203 """Calculate the basic environment for an os script.
4204
4205 @type os_name: str
4206 @param os_name: full operating system name (including variant)
4207 @type inst_os: L{objects.OS}
4208 @param inst_os: operating system for which the environment is being built
4209 @type os_params: dict
4210 @param os_params: the OS parameters
4211 @type debug: integer
4212 @param debug: debug level (0 or 1, for OS Api 10)
4213 @rtype: dict
4214 @return: dict of environment variables
4215 @raise errors.BlockDeviceError: if the block device
4216 cannot be found
4217
4218 """
4219 result = {}
4220 api_version = \
4221 max(constants.OS_API_VERSIONS.intersection(inst_os.api_versions))
4222 result["OS_API_VERSION"] = "%d" % api_version
4223 result["OS_NAME"] = inst_os.name
4224 result["DEBUG_LEVEL"] = "%d" % debug
4225
4226
4227 if api_version >= constants.OS_API_V15 and inst_os.supported_variants:
4228 variant = objects.OS.GetVariant(os_name)
4229 if not variant:
4230 variant = inst_os.supported_variants[0]
4231 else:
4232 variant = ""
4233 result["OS_VARIANT"] = variant
4234
4235
4236 for pname, pvalue in os_params.items():
4237 result["OSP_%s" % pname.upper().replace("-", "_")] = pvalue
4238
4239
4240
4241
4242 result["PATH"] = constants.HOOKS_PATH
4243
4244 return result
4245
4248 """Calculate the environment for an os script.
4249
4250 @type instance: L{objects.Instance}
4251 @param instance: target instance for the os script run
4252 @type inst_os: L{objects.OS}
4253 @param inst_os: operating system for which the environment is being built
4254 @type debug: integer
4255 @param debug: debug level (0 or 1, for OS Api 10)
4256 @rtype: dict
4257 @return: dict of environment variables
4258 @raise errors.BlockDeviceError: if the block device
4259 cannot be found
4260
4261 """
4262 result = OSCoreEnv(instance.os, inst_os, objects.FillDict(instance.osparams,
4263 instance.osparams_private.Unprivate()), debug=debug)
4264
4265 for attr in ["name", "os", "uuid", "ctime", "mtime", "primary_node"]:
4266 result["INSTANCE_%s" % attr.upper()] = str(getattr(instance, attr))
4267
4268 result["HYPERVISOR"] = instance.hypervisor
4269 result["DISK_COUNT"] = "%d" % len(instance.disks_info)
4270 result["NIC_COUNT"] = "%d" % len(instance.nics)
4271 result["INSTANCE_SECONDARY_NODES"] = \
4272 ("%s" % " ".join(instance.secondary_nodes))
4273
4274
4275 for idx, disk in enumerate(instance.disks_info):
4276 real_disk = _OpenRealBD(disk)
4277 uri = _CalculateDeviceURI(instance, disk, real_disk)
4278 result["DISK_%d_ACCESS" % idx] = disk.mode
4279 result["DISK_%d_UUID" % idx] = disk.uuid
4280 if real_disk.dev_path:
4281 result["DISK_%d_PATH" % idx] = real_disk.dev_path
4282 if uri:
4283 result["DISK_%d_URI" % idx] = uri
4284 if disk.name:
4285 result["DISK_%d_NAME" % idx] = disk.name
4286 if constants.HV_DISK_TYPE in instance.hvparams:
4287 result["DISK_%d_FRONTEND_TYPE" % idx] = \
4288 instance.hvparams[constants.HV_DISK_TYPE]
4289 if disk.dev_type in constants.DTS_BLOCK:
4290 result["DISK_%d_BACKEND_TYPE" % idx] = "block"
4291 elif disk.dev_type in constants.DTS_FILEBASED:
4292 result["DISK_%d_BACKEND_TYPE" % idx] = \
4293 "file:%s" % disk.logical_id[0]
4294
4295
4296 for idx, nic in enumerate(instance.nics):
4297 result["NIC_%d_MAC" % idx] = nic.mac
4298 result["NIC_%d_UUID" % idx] = nic.uuid
4299 if nic.name:
4300 result["NIC_%d_NAME" % idx] = nic.name
4301 if nic.ip:
4302 result["NIC_%d_IP" % idx] = nic.ip
4303 result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE]
4304 if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
4305 result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK]
4306 if nic.nicparams[constants.NIC_LINK]:
4307 result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK]
4308 if nic.netinfo:
4309 nobj = objects.Network.FromDict(nic.netinfo)
4310 result.update(nobj.HooksDict("NIC_%d_" % idx))
4311 if constants.HV_NIC_TYPE in instance.hvparams:
4312 result["NIC_%d_FRONTEND_TYPE" % idx] = \
4313 instance.hvparams[constants.HV_NIC_TYPE]
4314
4315
4316 for source, kind in [(instance.beparams, "BE"), (instance.hvparams, "HV")]:
4317 for key, value in source.items():
4318 result["INSTANCE_%s_%s" % (kind, key)] = str(value)
4319
4320 return result
4321
4324 """Compute the validity for all ExtStorage Providers.
4325
4326 @type top_dirs: list
4327 @param top_dirs: the list of directories in which to
4328 search (if not given defaults to
4329 L{pathutils.ES_SEARCH_PATH})
4330 @rtype: list of L{objects.ExtStorage}
4331 @return: a list of tuples (name, path, status, diagnose, parameters)
4332 for all (potential) ExtStorage Providers under all
4333 search paths, where:
4334 - name is the (potential) ExtStorage Provider
4335 - path is the full path to the ExtStorage Provider
4336 - status True/False is the validity of the ExtStorage Provider
4337 - diagnose is the error message for an invalid ExtStorage Provider,
4338 otherwise empty
4339 - parameters is a list of (name, help) parameters, if any
4340
4341 """
4342 if top_dirs is None:
4343 top_dirs = pathutils.ES_SEARCH_PATH
4344
4345 result = []
4346 for dir_name in top_dirs:
4347 if os.path.isdir(dir_name):
4348 try:
4349 f_names = utils.ListVisibleFiles(dir_name)
4350 except EnvironmentError, err:
4351 logging.exception("Can't list the ExtStorage directory %s: %s",
4352 dir_name, err)
4353 break
4354 for name in f_names:
4355 es_path = utils.PathJoin(dir_name, name)
4356 status, es_inst = extstorage.ExtStorageFromDisk(name, base_dir=dir_name)
4357 if status:
4358 diagnose = ""
4359 parameters = es_inst.supported_parameters
4360 else:
4361 diagnose = es_inst
4362 parameters = []
4363 result.append((name, es_path, status, diagnose, parameters))
4364
4365 return result
4366
4367
4368 -def BlockdevGrow(disk, amount, dryrun, backingstore, excl_stor):
4369 """Grow a stack of block devices.
4370
4371 This function is called recursively, with the childrens being the
4372 first ones to resize.
4373
4374 @type disk: L{objects.Disk}
4375 @param disk: the disk to be grown
4376 @type amount: integer
4377 @param amount: the amount (in mebibytes) to grow with
4378 @type dryrun: boolean
4379 @param dryrun: whether to execute the operation in simulation mode
4380 only, without actually increasing the size
4381 @param backingstore: whether to execute the operation on backing storage
4382 only, or on "logical" storage only; e.g. DRBD is logical storage,
4383 whereas LVM, file, RBD are backing storage
4384 @rtype: (status, result)
4385 @type excl_stor: boolean
4386 @param excl_stor: Whether exclusive_storage is active
4387 @return: a tuple with the status of the operation (True/False), and
4388 the errors message if status is False
4389
4390 """
4391 r_dev = _RecursiveFindBD(disk)
4392 if r_dev is None:
4393 _Fail("Cannot find block device %s", disk)
4394
4395 try:
4396 r_dev.Grow(amount, dryrun, backingstore, excl_stor)
4397 except errors.BlockDeviceError, err:
4398 _Fail("Failed to grow block device: %s", err, exc=True)
4399
4402 """Create a snapshot copy of a block device.
4403
4404 This function is called recursively, and the snapshot is actually created
4405 just for the leaf lvm backend device.
4406
4407 @type disk: L{objects.Disk}
4408 @param disk: the disk to be snapshotted
4409 @type snap_name: string
4410 @param snap_name: the name of the snapshot
4411 @type snap_size: int
4412 @param snap_size: the size of the snapshot
4413 @rtype: string
4414 @return: snapshot disk ID as (vg, lv)
4415
4416 """
4417 def _DiskSnapshot(disk, snap_name=None, snap_size=None):
4418 r_dev = _RecursiveFindBD(disk)
4419 if r_dev is not None:
4420 return r_dev.Snapshot(snap_name=snap_name, snap_size=snap_size)
4421 else:
4422 _Fail("Cannot find block device %s", disk)
4423
4424 if disk.SupportsSnapshots():
4425 if disk.dev_type == constants.DT_DRBD8:
4426 if not disk.children:
4427 _Fail("DRBD device '%s' without backing storage cannot be snapshotted",
4428 disk.unique_id)
4429 return BlockdevSnapshot(disk.children[0], snap_name, snap_size)
4430 else:
4431 return _DiskSnapshot(disk, snap_name, snap_size)
4432 else:
4433 _Fail("Cannot snapshot block device '%s' of type '%s'",
4434 disk.logical_id, disk.dev_type)
4435
4438 """Sets 'metadata' information on block devices.
4439
4440 This function sets 'info' metadata on block devices. Initial
4441 information is set at device creation; this function should be used
4442 for example after renames.
4443
4444 @type disk: L{objects.Disk}
4445 @param disk: the disk to be grown
4446 @type info: string
4447 @param info: new 'info' metadata
4448 @rtype: (status, result)
4449 @return: a tuple with the status of the operation (True/False), and
4450 the errors message if status is False
4451
4452 """
4453 r_dev = _RecursiveFindBD(disk)
4454 if r_dev is None:
4455 _Fail("Cannot find block device %s", disk)
4456
4457 try:
4458 r_dev.SetInfo(info)
4459 except errors.BlockDeviceError, err:
4460 _Fail("Failed to set information on block device: %s", err, exc=True)
4461
4464 """Write out the export configuration information.
4465
4466 @type instance: L{objects.Instance}
4467 @param instance: the instance which we export, used for
4468 saving configuration
4469 @type snap_disks: list of L{objects.Disk}
4470 @param snap_disks: list of snapshot block devices, which
4471 will be used to get the actual name of the dump file
4472
4473 @rtype: None
4474
4475 """
4476 destdir = utils.PathJoin(pathutils.EXPORT_DIR, instance.name + ".new")
4477 finaldestdir = utils.PathJoin(pathutils.EXPORT_DIR, instance.name)
4478 disk_template = utils.GetDiskTemplate(snap_disks)
4479
4480 config = objects.SerializableConfigParser()
4481
4482 config.add_section(constants.INISECT_EXP)
4483 config.set(constants.INISECT_EXP, "version", str(constants.EXPORT_VERSION))
4484 config.set(constants.INISECT_EXP, "timestamp", "%d" % int(time.time()))
4485 config.set(constants.INISECT_EXP, "source", instance.primary_node)
4486 config.set(constants.INISECT_EXP, "os", instance.os)
4487 config.set(constants.INISECT_EXP, "compression", "none")
4488
4489 config.add_section(constants.INISECT_INS)
4490 config.set(constants.INISECT_INS, "name", instance.name)
4491 config.set(constants.INISECT_INS, "maxmem", "%d" %
4492 instance.beparams[constants.BE_MAXMEM])
4493 config.set(constants.INISECT_INS, "minmem", "%d" %
4494 instance.beparams[constants.BE_MINMEM])
4495
4496 config.set(constants.INISECT_INS, "memory", "%d" %
4497 instance.beparams[constants.BE_MAXMEM])
4498 config.set(constants.INISECT_INS, "vcpus", "%d" %
4499 instance.beparams[constants.BE_VCPUS])
4500 config.set(constants.INISECT_INS, "disk_template", disk_template)
4501 config.set(constants.INISECT_INS, "hypervisor", instance.hypervisor)
4502 config.set(constants.INISECT_INS, "tags", " ".join(instance.GetTags()))
4503
4504 nic_total = 0
4505 for nic_count, nic in enumerate(instance.nics):
4506 nic_total += 1
4507 config.set(constants.INISECT_INS, "nic%d_mac" %
4508 nic_count, "%s" % nic.mac)
4509 config.set(constants.INISECT_INS, "nic%d_ip" % nic_count, "%s" % nic.ip)
4510 config.set(constants.INISECT_INS, "nic%d_network" % nic_count,
4511 "%s" % nic.network)
4512 config.set(constants.INISECT_INS, "nic%d_name" % nic_count,
4513 "%s" % nic.name)
4514 for param in constants.NICS_PARAMETER_TYPES:
4515 config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param),
4516 "%s" % nic.nicparams.get(param, None))
4517
4518 config.set(constants.INISECT_INS, "nic_count", "%d" % nic_total)
4519
4520 disk_total = 0
4521 for disk_count, disk in enumerate(snap_disks):
4522 if disk:
4523 disk_total += 1
4524 config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count,
4525 ("%s" % disk.iv_name))
4526 config.set(constants.INISECT_INS, "disk%d_dump" % disk_count,
4527 ("%s" % disk.uuid))
4528 config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
4529 ("%d" % disk.size))
4530 config.set(constants.INISECT_INS, "disk%d_name" % disk_count,
4531 "%s" % disk.name)
4532
4533 config.set(constants.INISECT_INS, "disk_count", "%d" % disk_total)
4534
4535
4536
4537 config.add_section(constants.INISECT_HYP)
4538 for name, value in instance.hvparams.items():
4539 if name not in constants.HVC_GLOBALS:
4540 config.set(constants.INISECT_HYP, name, str(value))
4541
4542 config.add_section(constants.INISECT_BEP)
4543 for name, value in instance.beparams.items():
4544 config.set(constants.INISECT_BEP, name, str(value))
4545
4546 config.add_section(constants.INISECT_OSP)
4547 for name, value in instance.osparams.items():
4548 config.set(constants.INISECT_OSP, name, str(value))
4549
4550 config.add_section(constants.INISECT_OSP_PRIVATE)
4551 for name, value in instance.osparams_private.items():
4552 config.set(constants.INISECT_OSP_PRIVATE, name, str(value.Get()))
4553
4554 utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE),
4555 data=config.Dumps())
4556 shutil.rmtree(finaldestdir, ignore_errors=True)
4557 shutil.move(destdir, finaldestdir)
4558
4581
4584 """Return a list of exports currently available on this machine.
4585
4586 @rtype: list
4587 @return: list of the exports
4588
4589 """
4590 if os.path.isdir(pathutils.EXPORT_DIR):
4591 return sorted(utils.ListVisibleFiles(pathutils.EXPORT_DIR))
4592 else:
4593 _Fail("No exports directory")
4594
4597 """Remove an existing export from the node.
4598
4599 @type export: str
4600 @param export: the name of the export to remove
4601 @rtype: None
4602
4603 """
4604 target = utils.PathJoin(pathutils.EXPORT_DIR, export)
4605
4606 try:
4607 shutil.rmtree(target)
4608 except EnvironmentError, err:
4609 _Fail("Error while removing the export: %s", err, exc=True)
4610
4613 """Rename a list of block devices.
4614
4615 @type devlist: list of tuples
4616 @param devlist: list of tuples of the form (disk, new_unique_id); disk is
4617 an L{objects.Disk} object describing the current disk, and new
4618 unique_id is the name we rename it to
4619 @rtype: boolean
4620 @return: True if all renames succeeded, False otherwise
4621
4622 """
4623 msgs = []
4624 result = True
4625 for disk, unique_id in devlist:
4626 dev = _RecursiveFindBD(disk)
4627 if dev is None:
4628 msgs.append("Can't find device %s in rename" % str(disk))
4629 result = False
4630 continue
4631 try:
4632 old_rpath = dev.dev_path
4633 dev.Rename(unique_id)
4634 new_rpath = dev.dev_path
4635 if old_rpath != new_rpath:
4636 DevCacheManager.RemoveCache(old_rpath)
4637
4638
4639
4640
4641
4642 except errors.BlockDeviceError, err:
4643 msgs.append("Can't rename device '%s' to '%s': %s" %
4644 (dev, unique_id, err))
4645 logging.exception("Can't rename device '%s' to '%s'", dev, unique_id)
4646 result = False
4647 if not result:
4648 _Fail("; ".join(msgs))
4649
4667
4670 """Create file storage directory.
4671
4672 @type file_storage_dir: str
4673 @param file_storage_dir: directory to create
4674
4675 @rtype: tuple
4676 @return: tuple with first element a boolean indicating wheter dir
4677 creation was successful or not
4678
4679 """
4680 file_storage_dir = _TransformFileStorageDir(file_storage_dir)
4681 if os.path.exists(file_storage_dir):
4682 if not os.path.isdir(file_storage_dir):
4683 _Fail("Specified storage dir '%s' is not a directory",
4684 file_storage_dir)
4685 else:
4686 try:
4687 os.makedirs(file_storage_dir, 0750)
4688 except OSError, err:
4689 _Fail("Cannot create file storage directory '%s': %s",
4690 file_storage_dir, err, exc=True)
4691
4694 """Remove file storage directory.
4695
4696 Remove it only if it's empty. If not log an error and return.
4697
4698 @type file_storage_dir: str
4699 @param file_storage_dir: the directory we should cleanup
4700 @rtype: tuple (success,)
4701 @return: tuple of one element, C{success}, denoting
4702 whether the operation was successful
4703
4704 """
4705 file_storage_dir = _TransformFileStorageDir(file_storage_dir)
4706 if os.path.exists(file_storage_dir):
4707 if not os.path.isdir(file_storage_dir):
4708 _Fail("Specified Storage directory '%s' is not a directory",
4709 file_storage_dir)
4710
4711 try:
4712 os.rmdir(file_storage_dir)
4713 except OSError, err:
4714 _Fail("Cannot remove file storage directory '%s': %s",
4715 file_storage_dir, err)
4716
4719 """Rename the file storage directory.
4720
4721 @type old_file_storage_dir: str
4722 @param old_file_storage_dir: the current path
4723 @type new_file_storage_dir: str
4724 @param new_file_storage_dir: the name we should rename to
4725 @rtype: tuple (success,)
4726 @return: tuple of one element, C{success}, denoting
4727 whether the operation was successful
4728
4729 """
4730 old_file_storage_dir = _TransformFileStorageDir(old_file_storage_dir)
4731 new_file_storage_dir = _TransformFileStorageDir(new_file_storage_dir)
4732 if not os.path.exists(new_file_storage_dir):
4733 if os.path.isdir(old_file_storage_dir):
4734 try:
4735 os.rename(old_file_storage_dir, new_file_storage_dir)
4736 except OSError, err:
4737 _Fail("Cannot rename '%s' to '%s': %s",
4738 old_file_storage_dir, new_file_storage_dir, err)
4739 else:
4740 _Fail("Specified storage dir '%s' is not a directory",
4741 old_file_storage_dir)
4742 else:
4743 if os.path.exists(old_file_storage_dir):
4744 _Fail("Cannot rename '%s' to '%s': both locations exist",
4745 old_file_storage_dir, new_file_storage_dir)
4746
4749 """Checks whether the given filename is in the queue directory.
4750
4751 @type file_name: str
4752 @param file_name: the file name we should check
4753 @rtype: None
4754 @raises RPCFail: if the file is not valid
4755
4756 """
4757 if not utils.IsBelowDir(pathutils.QUEUE_DIR, file_name):
4758 _Fail("Passed job queue file '%s' does not belong to"
4759 " the queue directory '%s'", file_name, pathutils.QUEUE_DIR)
4760
4763 """Updates a file in the queue directory.
4764
4765 This is just a wrapper over L{utils.io.WriteFile}, with proper
4766 checking.
4767
4768 @type file_name: str
4769 @param file_name: the job file name
4770 @type content: str
4771 @param content: the new job contents
4772 @rtype: boolean
4773 @return: the success of the operation
4774
4775 """
4776 file_name = vcluster.LocalizeVirtualPath(file_name)
4777
4778 _EnsureJobQueueFile(file_name)
4779 getents = runtime.GetEnts()
4780
4781
4782 utils.WriteFile(file_name, data=_Decompress(content), uid=getents.masterd_uid,
4783 gid=getents.daemons_gid, mode=constants.JOB_QUEUE_FILES_PERMS)
4784
4787 """Renames a job queue file.
4788
4789 This is just a wrapper over os.rename with proper checking.
4790
4791 @type old: str
4792 @param old: the old (actual) file name
4793 @type new: str
4794 @param new: the desired file name
4795 @rtype: tuple
4796 @return: the success of the operation and payload
4797
4798 """
4799 old = vcluster.LocalizeVirtualPath(old)
4800 new = vcluster.LocalizeVirtualPath(new)
4801
4802 _EnsureJobQueueFile(old)
4803 _EnsureJobQueueFile(new)
4804
4805 getents = runtime.GetEnts()
4806
4807 utils.RenameFile(old, new, mkdir=True, mkdir_mode=0750,
4808 dir_uid=getents.masterd_uid, dir_gid=getents.daemons_gid)
4809
4812 """Closes the given block devices.
4813
4814 This means they will be switched to secondary mode (in case of
4815 DRBD).
4816
4817 @param instance_name: if the argument is not empty, the symlinks
4818 of this instance will be removed
4819 @type disks: list of L{objects.Disk}
4820 @param disks: the list of disks to be closed
4821 @rtype: tuple (success, message)
4822 @return: a tuple of success and message, where success
4823 indicates the succes of the operation, and message
4824 which will contain the error details in case we
4825 failed
4826
4827 """
4828 bdevs = []
4829 for cf in disks:
4830 rd = _RecursiveFindBD(cf)
4831 if rd is None:
4832 _Fail("Can't find device %s", cf)
4833 bdevs.append(rd)
4834
4835 msg = []
4836 for rd in bdevs:
4837 try:
4838 rd.Close()
4839 except errors.BlockDeviceError, err:
4840 msg.append(str(err))
4841 if msg:
4842 _Fail("Can't close devices: %s", ",".join(msg))
4843 else:
4844 if instance_name:
4845 _RemoveBlockDevLinks(instance_name, disks)
4846
4849 """Opens the given block devices.
4850
4851 """
4852 bdevs = []
4853 for cf in disks:
4854 rd = _RecursiveFindBD(cf)
4855 if rd is None:
4856 _Fail("Can't find device %s", cf)
4857 bdevs.append(rd)
4858
4859 msg = []
4860 for idx, rd in enumerate(bdevs):
4861 try:
4862 rd.Open(exclusive=exclusive)
4863 _SymlinkBlockDev(instance_name, rd.dev_path, uuid=disks[idx].uuid)
4864
4865
4866
4867 _SymlinkBlockDev(instance_name, rd.dev_path, idx=idx)
4868 except errors.BlockDeviceError, err:
4869 msg.append(str(err))
4870
4871 if msg:
4872 _Fail("Can't open devices: %s", ",".join(msg))
4873
4876 """Validates the given hypervisor parameters.
4877
4878 @type hvname: string
4879 @param hvname: the hypervisor name
4880 @type hvparams: dict
4881 @param hvparams: the hypervisor parameters to be validated
4882 @rtype: None
4883
4884 """
4885 try:
4886 hv_type = hypervisor.GetHypervisor(hvname)
4887 hv_type.ValidateParameters(hvparams)
4888 except errors.HypervisorError, err:
4889 _Fail(str(err), log=False)
4890
4893 """Check whether a list of parameters is supported by the OS.
4894
4895 @type os_obj: L{objects.OS}
4896 @param os_obj: OS object to check
4897 @type parameters: list
4898 @param parameters: the list of parameters to check
4899
4900 """
4901 supported = [v[0] for v in os_obj.supported_parameters]
4902 delta = frozenset(parameters).difference(supported)
4903 if delta:
4904 _Fail("The following parameters are not supported"
4905 " by the OS %s: %s" % (os_obj.name, utils.CommaJoin(delta)))
4906
4909 """Check whether an OS name conforms to the os variants specification.
4910
4911 @type os_obj: L{objects.OS}
4912 @param os_obj: OS object to check
4913
4914 @type name: string
4915 @param name: OS name passed by the user, to check for validity
4916
4917 @rtype: NoneType
4918 @return: None
4919 @raise RPCFail: if OS variant is not valid
4920
4921 """
4922 variant = objects.OS.GetVariant(name)
4923
4924 if not os_obj.supported_variants:
4925 if variant:
4926 _Fail("OS '%s' does not support variants ('%s' passed)" %
4927 (os_obj.name, variant))
4928 else:
4929 return
4930
4931 if not variant:
4932 _Fail("OS name '%s' must include a variant" % name)
4933
4934 if variant not in os_obj.supported_variants:
4935 _Fail("OS '%s' does not support variant '%s'" % (os_obj.name, variant))
4936
4937
4938 -def ValidateOS(required, osname, checks, osparams, force_variant):
4939 """Validate the given OS parameters.
4940
4941 @type required: boolean
4942 @param required: whether absence of the OS should translate into
4943 failure or not
4944 @type osname: string
4945 @param osname: the OS to be validated
4946 @type checks: list
4947 @param checks: list of the checks to run (currently only 'parameters')
4948 @type osparams: dict
4949 @param osparams: dictionary with OS parameters, some of which may be
4950 private.
4951 @rtype: boolean
4952 @return: True if the validation passed, or False if the OS was not
4953 found and L{required} was false
4954
4955 """
4956 if not constants.OS_VALIDATE_CALLS.issuperset(checks):
4957 _Fail("Unknown checks required for OS %s: %s", osname,
4958 set(checks).difference(constants.OS_VALIDATE_CALLS))
4959
4960 name_only = objects.OS.GetName(osname)
4961 status, tbv = _TryOSFromDisk(name_only, None)
4962
4963 if not status:
4964 if required:
4965 _Fail(tbv)
4966 else:
4967 return False
4968
4969 if not force_variant:
4970 _CheckOSVariant(tbv, osname)
4971
4972 if max(tbv.api_versions) < constants.OS_API_V20:
4973 return True
4974
4975 if constants.OS_VALIDATE_PARAMETERS in checks:
4976 _CheckOSPList(tbv, osparams.keys())
4977
4978 validate_env = OSCoreEnv(osname, tbv, osparams)
4979 result = utils.RunCmd([tbv.verify_script] + checks, env=validate_env,
4980 cwd=tbv.path, reset_env=True)
4981 if result.failed:
4982 logging.error("os validate command '%s' returned error: %s output: %s",
4983 result.cmd, result.fail_reason, result.output)
4984 _Fail("OS validation script failed (%s), output: %s",
4985 result.fail_reason, result.output, log=False)
4986
4987 return True
4988
4989
4990 -def ExportOS(instance, override_env):
4991 """Creates a GZIPed tarball with an OS definition and environment.
4992
4993 The archive contains a file with the environment variables needed by
4994 the OS scripts.
4995
4996 @type instance: L{objects.Instance}
4997 @param instance: instance for which the OS definition is exported
4998
4999 @type override_env: dict of string to string
5000 @param override_env: if supplied, it overrides the environment on a
5001 key-by-key basis that is part of the archive
5002
5003 @rtype: string
5004 @return: filepath of the archive
5005
5006 """
5007 assert instance
5008 assert instance.os
5009
5010 temp_dir = tempfile.mkdtemp()
5011 inst_os = OSFromDisk(instance.os)
5012
5013 result = utils.RunCmd(["ln", "-s", inst_os.path,
5014 utils.PathJoin(temp_dir, "os")])
5015 if result.failed:
5016 _Fail("Failed to copy OS package '%s' to '%s': %s, output '%s'",
5017 inst_os, temp_dir, result.fail_reason, result.output)
5018
5019 env = OSEnvironment(instance, inst_os)
5020 env.update(override_env)
5021
5022 with open(utils.PathJoin(temp_dir, "environment"), "w") as f:
5023 for var in env:
5024 f.write(var + "=" + env[var] + "\n")
5025
5026 (fd, os_package) = tempfile.mkstemp(suffix=".tgz")
5027 os.close(fd)
5028
5029 result = utils.RunCmd(["tar", "--dereference", "-czv",
5030 "-f", os_package,
5031 "-C", temp_dir,
5032 "."])
5033 if result.failed:
5034 _Fail("Failed to create OS archive '%s': %s, output '%s'",
5035 os_package, result.fail_reason, result.output)
5036
5037 result = utils.RunCmd(["rm", "-rf", temp_dir])
5038 if result.failed:
5039 _Fail("Failed to remove copy of OS package '%s' in '%s': %s, output '%s'",
5040 inst_os, temp_dir, result.fail_reason, result.output)
5041
5042 return os_package
5043
5066
5075
5078 """Creates a new X509 certificate for SSL/TLS.
5079
5080 @type validity: int
5081 @param validity: Validity in seconds
5082 @rtype: tuple; (string, string)
5083 @return: Certificate name and public part
5084
5085 """
5086 serial_no = int(time.time())
5087 (key_pem, cert_pem) = \
5088 utils.GenerateSelfSignedX509Cert(netutils.Hostname.GetSysName(),
5089 min(validity, _MAX_SSL_CERT_VALIDITY),
5090 serial_no)
5091
5092 cert_dir = tempfile.mkdtemp(dir=cryptodir,
5093 prefix="x509-%s-" % utils.TimestampForFilename())
5094 try:
5095 name = os.path.basename(cert_dir)
5096 assert len(name) > 5
5097
5098 (_, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
5099
5100 utils.WriteFile(key_file, mode=0400, data=key_pem)
5101 utils.WriteFile(cert_file, mode=0400, data=cert_pem)
5102
5103
5104 return (name, cert_pem)
5105 except Exception:
5106 shutil.rmtree(cert_dir, ignore_errors=True)
5107 raise
5108
5111 """Removes a X509 certificate.
5112
5113 @type name: string
5114 @param name: Certificate name
5115
5116 """
5117 (cert_dir, key_file, cert_file) = _GetX509Filenames(cryptodir, name)
5118
5119 utils.RemoveFile(key_file)
5120 utils.RemoveFile(cert_file)
5121
5122 try:
5123 os.rmdir(cert_dir)
5124 except EnvironmentError, err:
5125 _Fail("Cannot remove certificate directory '%s': %s",
5126 cert_dir, err)
5127
5130 """Returns the command for the requested input/output.
5131
5132 @type instance: L{objects.Instance}
5133 @param instance: The instance object
5134 @param mode: Import/export mode
5135 @param ieio: Input/output type
5136 @param ieargs: Input/output arguments
5137
5138 """
5139 assert mode in (constants.IEM_IMPORT, constants.IEM_EXPORT)
5140
5141 env = None
5142 prefix = None
5143 suffix = None
5144 exp_size = None
5145
5146 if ieio == constants.IEIO_FILE:
5147 (filename, ) = ieargs
5148
5149 if not utils.IsNormAbsPath(filename):
5150 _Fail("Path '%s' is not normalized or absolute", filename)
5151
5152 real_filename = os.path.realpath(filename)
5153 directory = os.path.dirname(real_filename)
5154
5155 if not utils.IsBelowDir(pathutils.EXPORT_DIR, real_filename):
5156 _Fail("File '%s' is not under exports directory '%s': %s",
5157 filename, pathutils.EXPORT_DIR, real_filename)
5158
5159
5160 utils.Makedirs(directory, mode=0750)
5161
5162 quoted_filename = utils.ShellQuote(filename)
5163
5164 if mode == constants.IEM_IMPORT:
5165 suffix = "> %s" % quoted_filename
5166 elif mode == constants.IEM_EXPORT:
5167 suffix = "< %s" % quoted_filename
5168
5169
5170 try:
5171 st = os.stat(filename)
5172 except EnvironmentError, err:
5173 logging.error("Can't stat(2) %s: %s", filename, err)
5174 else:
5175 exp_size = utils.BytesToMebibyte(st.st_size)
5176
5177 elif ieio == constants.IEIO_RAW_DISK:
5178 (disk, ) = ieargs
5179 real_disk = _OpenRealBD(disk)
5180
5181 if mode == constants.IEM_IMPORT:
5182 suffix = "| %s" % utils.ShellQuoteArgs(real_disk.Import())
5183
5184 elif mode == constants.IEM_EXPORT:
5185 prefix = "%s |" % utils.ShellQuoteArgs(real_disk.Export())
5186 exp_size = disk.size
5187
5188 elif ieio == constants.IEIO_SCRIPT:
5189 (disk, disk_index, ) = ieargs
5190
5191 assert isinstance(disk_index, (int, long))
5192
5193 inst_os = OSFromDisk(instance.os)
5194 env = OSEnvironment(instance, inst_os)
5195
5196 if mode == constants.IEM_IMPORT:
5197 disk_path_var = "DISK_%d_PATH" % disk_index
5198 if disk_path_var in env:
5199 env["IMPORT_DEVICE"] = env[disk_path_var]
5200 env["IMPORT_DISK_PATH"] = env[disk_path_var]
5201
5202 disk_uri_var = "DISK_%d_URI" % disk_index
5203 if disk_uri_var in env:
5204 env["IMPORT_DISK_URI"] = env[disk_uri_var]
5205
5206 env["IMPORT_INDEX"] = str(disk_index)
5207 script = inst_os.import_script
5208
5209 elif mode == constants.IEM_EXPORT:
5210 disk_path_var = "DISK_%d_PATH" % disk_index
5211 if disk_path_var in env:
5212 env["EXPORT_DEVICE"] = env[disk_path_var]
5213 env["EXPORT_DISK_PATH"] = env[disk_path_var]
5214
5215 disk_uri_var = "DISK_%d_URI" % disk_index
5216 if disk_uri_var in env:
5217 env["EXPORT_DISK_URI"] = env[disk_uri_var]
5218
5219 env["EXPORT_INDEX"] = str(disk_index)
5220 script = inst_os.export_script
5221
5222
5223 script_cmd = utils.BuildShellCmd("( cd %s && %s; )", inst_os.path, script)
5224
5225 if mode == constants.IEM_IMPORT:
5226 suffix = "| %s" % script_cmd
5227
5228 elif mode == constants.IEM_EXPORT:
5229 prefix = "%s |" % script_cmd
5230
5231
5232 exp_size = constants.IE_CUSTOM_SIZE
5233
5234 else:
5235 _Fail("Invalid %s I/O mode %r", mode, ieio)
5236
5237 return (env, prefix, suffix, exp_size)
5238
5241 """Creates status directory for import/export.
5242
5243 """
5244 return tempfile.mkdtemp(dir=pathutils.IMPORT_EXPORT_DIR,
5245 prefix=("%s-%s-" %
5246 (prefix, utils.TimestampForFilename())))
5247
5251 """Starts an import or export daemon.
5252
5253 @param mode: Import/output mode
5254 @type opts: L{objects.ImportExportOptions}
5255 @param opts: Daemon options
5256 @type host: string
5257 @param host: Remote host for export (None for import)
5258 @type port: int
5259 @param port: Remote port for export (None for import)
5260 @type instance: L{objects.Instance}
5261 @param instance: Instance object
5262 @type component: string
5263 @param component: which part of the instance is transferred now,
5264 e.g. 'disk/0'
5265 @param ieio: Input/output type
5266 @param ieioargs: Input/output arguments
5267
5268 """
5269
5270
5271
5272
5273
5274 if mode == constants.IEM_IMPORT:
5275 prefix = "import"
5276
5277 if not (host is None and port is None):
5278 _Fail("Can not specify host or port on import")
5279
5280 elif mode == constants.IEM_EXPORT:
5281 prefix = "export"
5282
5283 if host is None or port is None:
5284 _Fail("Host and port must be specified for an export")
5285
5286 else:
5287 _Fail("Invalid mode %r", mode)
5288
5289 if (opts.key_name is None) ^ (opts.ca_pem is None):
5290 _Fail("Cluster certificate can only be used for both key and CA")
5291
5292 (cmd_env, cmd_prefix, cmd_suffix, exp_size) = \
5293 _GetImportExportIoCommand(instance, mode, ieio, ieioargs)
5294
5295 if opts.key_name is None:
5296
5297 key_path = pathutils.NODED_CERT_FILE
5298 cert_path = pathutils.NODED_CERT_FILE
5299 assert opts.ca_pem is None
5300 else:
5301 (_, key_path, cert_path) = _GetX509Filenames(pathutils.CRYPTO_KEYS_DIR,
5302 opts.key_name)
5303 assert opts.ca_pem is not None
5304
5305 for i in [key_path, cert_path]:
5306 if not os.path.exists(i):
5307 _Fail("File '%s' does not exist" % i)
5308
5309 status_dir = _CreateImportExportStatusDir("%s-%s" % (prefix, component))
5310 try:
5311 status_file = utils.PathJoin(status_dir, _IES_STATUS_FILE)
5312 pid_file = utils.PathJoin(status_dir, _IES_PID_FILE)
5313 ca_file = utils.PathJoin(status_dir, _IES_CA_FILE)
5314
5315 if opts.ca_pem is None:
5316
5317 ca = utils.ReadFile(pathutils.NODED_CERT_FILE)
5318 else:
5319 ca = opts.ca_pem
5320
5321
5322 utils.WriteFile(ca_file, data=ca, mode=0400)
5323
5324 cmd = [
5325 pathutils.IMPORT_EXPORT_DAEMON,
5326 status_file, mode,
5327 "--key=%s" % key_path,
5328 "--cert=%s" % cert_path,
5329 "--ca=%s" % ca_file,
5330 ]
5331
5332 if host:
5333 cmd.append("--host=%s" % host)
5334
5335 if port:
5336 cmd.append("--port=%s" % port)
5337
5338 if opts.ipv6:
5339 cmd.append("--ipv6")
5340 else:
5341 cmd.append("--ipv4")
5342
5343 if opts.compress:
5344 cmd.append("--compress=%s" % opts.compress)
5345
5346 if opts.magic:
5347 cmd.append("--magic=%s" % opts.magic)
5348
5349 if exp_size is not None:
5350 cmd.append("--expected-size=%s" % exp_size)
5351
5352 if cmd_prefix:
5353 cmd.append("--cmd-prefix=%s" % cmd_prefix)
5354
5355 if cmd_suffix:
5356 cmd.append("--cmd-suffix=%s" % cmd_suffix)
5357
5358 if mode == constants.IEM_EXPORT:
5359
5360 cmd.append("--connect-retries=%s" % constants.RIE_CONNECT_RETRIES)
5361 cmd.append("--connect-timeout=%s" % constants.RIE_CONNECT_ATTEMPT_TIMEOUT)
5362 elif opts.connect_timeout is not None:
5363 assert mode == constants.IEM_IMPORT
5364
5365 cmd.append("--connect-timeout=%s" % opts.connect_timeout)
5366
5367 logfile = _InstanceLogName(prefix, instance.os, instance.name, component)
5368
5369
5370
5371 utils.StartDaemon(cmd, env=cmd_env, pidfile=pid_file,
5372 output=logfile)
5373
5374
5375 return os.path.basename(status_dir)
5376
5377 except Exception:
5378 shutil.rmtree(status_dir, ignore_errors=True)
5379 raise
5380
5383 """Returns import/export daemon status.
5384
5385 @type names: sequence
5386 @param names: List of names
5387 @rtype: List of dicts
5388 @return: Returns a list of the state of each named import/export or None if a
5389 status couldn't be read
5390
5391 """
5392 result = []
5393
5394 for name in names:
5395 status_file = utils.PathJoin(pathutils.IMPORT_EXPORT_DIR, name,
5396 _IES_STATUS_FILE)
5397
5398 try:
5399 data = utils.ReadFile(status_file)
5400 except EnvironmentError, err:
5401 if err.errno != errno.ENOENT:
5402 raise
5403 data = None
5404
5405 if not data:
5406 result.append(None)
5407 continue
5408
5409 result.append(serializer.LoadJson(data))
5410
5411 return result
5412
5427
5430 """Cleanup after an import or export.
5431
5432 If the import/export daemon is still running it's killed. Afterwards the
5433 whole status directory is removed.
5434
5435 """
5436 logging.info("Finalizing import/export %s", name)
5437
5438 status_dir = utils.PathJoin(pathutils.IMPORT_EXPORT_DIR, name)
5439
5440 pid = utils.ReadLockedPidFile(utils.PathJoin(status_dir, _IES_PID_FILE))
5441
5442 if pid:
5443 logging.info("Import/export %s is still running with PID %s",
5444 name, pid)
5445 utils.KillProcess(pid, waitpid=False)
5446
5447 shutil.rmtree(status_dir, ignore_errors=True)
5448
5451 """Finds attached L{BlockDev}s for the given disks.
5452
5453 @type disks: list of L{objects.Disk}
5454 @param disks: the disk objects we need to find
5455
5456 @return: list of L{BlockDev} objects or C{None} if a given disk
5457 was not found or was no attached.
5458
5459 """
5460 bdevs = []
5461
5462 for disk in disks:
5463 rd = _RecursiveFindBD(disk)
5464 if rd is None:
5465 _Fail("Can't find device %s", disk)
5466 bdevs.append(rd)
5467 return bdevs
5468
5471 """Disconnects the network on a list of drbd devices.
5472
5473 """
5474 bdevs = _FindDisks(disks)
5475
5476
5477 for rd in bdevs:
5478 try:
5479 rd.DisconnectNet()
5480 except errors.BlockDeviceError, err:
5481 _Fail("Can't change network configuration to standalone mode: %s",
5482 err, exc=True)
5483
5486 """Attaches the network on a list of drbd devices.
5487
5488 """
5489 bdevs = _FindDisks(disks)
5490
5491
5492
5493 for rd in bdevs:
5494 try:
5495 rd.AttachNet(multimaster)
5496 except errors.BlockDeviceError, err:
5497 _Fail("Can't change network configuration: %s", err)
5498
5499
5500
5501
5502
5503
5504
5505 def _Attach():
5506 all_connected = True
5507
5508 for rd in bdevs:
5509 stats = rd.GetProcStatus()
5510
5511 if multimaster:
5512
5513
5514
5515
5516
5517
5518 all_connected = (all_connected and
5519 stats.is_connected and
5520 stats.is_disk_uptodate and
5521 stats.peer_disk_uptodate)
5522 else:
5523 all_connected = (all_connected and
5524 (stats.is_connected or stats.is_in_resync))
5525
5526 if stats.is_standalone:
5527
5528
5529
5530 try:
5531 rd.AttachNet(multimaster)
5532 except errors.BlockDeviceError, err:
5533 _Fail("Can't change network configuration: %s", err)
5534
5535 if not all_connected:
5536 raise utils.RetryAgain()
5537
5538 try:
5539
5540 utils.Retry(_Attach, (0.1, 1.5, 5.0), 2 * 60)
5541 except utils.RetryTimeout:
5542 _Fail("Timeout in disk reconnecting")
5543
5546 """Wait until DRBDs have synchronized.
5547
5548 """
5549 def _helper(rd):
5550 stats = rd.GetProcStatus()
5551 if not (stats.is_connected or stats.is_in_resync):
5552 raise utils.RetryAgain()
5553 return stats
5554
5555 bdevs = _FindDisks(disks)
5556
5557 min_resync = 100
5558 alldone = True
5559 for rd in bdevs:
5560 try:
5561
5562 stats = utils.Retry(_helper, 1, 15, args=[rd])
5563 except utils.RetryTimeout:
5564 stats = rd.GetProcStatus()
5565
5566 if not (stats.is_connected or stats.is_in_resync):
5567 _Fail("DRBD device %s is not in sync: stats=%s", rd, stats)
5568 alldone = alldone and (not stats.is_in_resync)
5569 if stats.sync_percent is not None:
5570 min_resync = min(min_resync, stats.sync_percent)
5571
5572 return (alldone, min_resync)
5573
5576 """Checks which of the passed disks needs activation and returns their UUIDs.
5577
5578 """
5579 faulty_disks = []
5580
5581 for disk in disks:
5582 rd = _RecursiveFindBD(disk)
5583 if rd is None:
5584 faulty_disks.append(disk)
5585 continue
5586
5587 stats = rd.GetProcStatus()
5588 if stats.is_standalone or stats.is_diskless:
5589 faulty_disks.append(disk)
5590
5591 return [disk.uuid for disk in faulty_disks]
5592
5602
5605 """Hard-powercycle the node.
5606
5607 Because we need to return first, and schedule the powercycle in the
5608 background, we won't be able to report failures nicely.
5609
5610 """
5611 hyper = hypervisor.GetHypervisor(hypervisor_type)
5612 try:
5613 pid = os.fork()
5614 except OSError:
5615
5616 pid = 0
5617 if pid > 0:
5618 return "Reboot scheduled in 5 seconds"
5619
5620 try:
5621 utils.Mlockall()
5622 except Exception:
5623 pass
5624 time.sleep(5)
5625 hyper.PowercycleNode(hvparams=hvparams)
5626
5629 """Verifies a restricted command name.
5630
5631 @type cmd: string
5632 @param cmd: Command name
5633 @rtype: tuple; (boolean, string or None)
5634 @return: The tuple's first element is the status; if C{False}, the second
5635 element is an error message string, otherwise it's C{None}
5636
5637 """
5638 if not cmd.strip():
5639 return (False, "Missing command name")
5640
5641 if os.path.basename(cmd) != cmd:
5642 return (False, "Invalid command name")
5643
5644 if not constants.EXT_PLUGIN_MASK.match(cmd):
5645 return (False, "Command name contains forbidden characters")
5646
5647 return (True, None)
5648
5651 """Common checks for restricted command file system directories and files.
5652
5653 @type path: string
5654 @param path: Path to check
5655 @param owner: C{None} or tuple containing UID and GID
5656 @rtype: tuple; (boolean, string or C{os.stat} result)
5657 @return: The tuple's first element is the status; if C{False}, the second
5658 element is an error message string, otherwise it's the result of C{os.stat}
5659
5660 """
5661 if owner is None:
5662
5663 owner = (0, 0)
5664
5665 try:
5666 st = os.stat(path)
5667 except EnvironmentError, err:
5668 return (False, "Can't stat(2) '%s': %s" % (path, err))
5669
5670 if stat.S_IMODE(st.st_mode) & (~_RCMD_MAX_MODE):
5671 return (False, "Permissions on '%s' are too permissive" % path)
5672
5673 if (st.st_uid, st.st_gid) != owner:
5674 (owner_uid, owner_gid) = owner
5675 return (False, "'%s' is not owned by %s:%s" % (path, owner_uid, owner_gid))
5676
5677 return (True, st)
5678
5681 """Verifies restricted command directory.
5682
5683 @type path: string
5684 @param path: Path to check
5685 @rtype: tuple; (boolean, string or None)
5686 @return: The tuple's first element is the status; if C{False}, the second
5687 element is an error message string, otherwise it's C{None}
5688
5689 """
5690 (status, value) = _CommonRestrictedCmdCheck(path, _owner)
5691
5692 if not status:
5693 return (False, value)
5694
5695 if not stat.S_ISDIR(value.st_mode):
5696 return (False, "Path '%s' is not a directory" % path)
5697
5698 return (True, None)
5699
5702 """Verifies a whole restricted command and returns its executable filename.
5703
5704 @type path: string
5705 @param path: Directory containing restricted commands
5706 @type cmd: string
5707 @param cmd: Command name
5708 @rtype: tuple; (boolean, string)
5709 @return: The tuple's first element is the status; if C{False}, the second
5710 element is an error message string, otherwise the second element is the
5711 absolute path to the executable
5712
5713 """
5714 executable = utils.PathJoin(path, cmd)
5715
5716 (status, msg) = _CommonRestrictedCmdCheck(executable, _owner)
5717
5718 if not status:
5719 return (False, msg)
5720
5721 if not utils.IsExecutable(executable):
5722 return (False, "access(2) thinks '%s' can't be executed" % executable)
5723
5724 return (True, executable)
5725
5731 """Performs a number of tests on a restricted command.
5732
5733 @type path: string
5734 @param path: Directory containing restricted commands
5735 @type cmd: string
5736 @param cmd: Command name
5737 @return: Same as L{_VerifyRestrictedCmd}
5738
5739 """
5740
5741 (status, msg) = _verify_dir(path)
5742 if status:
5743
5744 (status, msg) = _verify_name(cmd)
5745
5746 if not status:
5747 return (False, msg)
5748
5749
5750 return _verify_cmd(path, cmd)
5751
5762 """Executes a command after performing strict tests.
5763
5764 @type cmd: string
5765 @param cmd: Command name
5766 @type lock_file: string
5767 @param lock_file: path to the lock file
5768 @type path: string
5769 @param path: path to the directory in which the command is present
5770 @type inp: string
5771 @param inp: Input to be passed to the command
5772 @rtype: string
5773 @return: Command output
5774 @raise RPCFail: In case of an error
5775
5776 """
5777 logging.info("Preparing to run restricted command '%s'", cmd)
5778
5779 if not _enabled:
5780 _Fail("Restricted commands disabled at configure time")
5781
5782 lock = None
5783 try:
5784 cmdresult = None
5785 try:
5786 lock = utils.FileLock.Open(lock_file)
5787 lock.Exclusive(blocking=True, timeout=_lock_timeout)
5788
5789 (status, value) = _prepare_fn(path, cmd)
5790
5791 if status:
5792 if inp:
5793 input_fd = tempfile.TemporaryFile()
5794 input_fd.write(inp)
5795 input_fd.flush()
5796 input_fd.seek(0)
5797 else:
5798 input_fd = None
5799 cmdresult = _runcmd_fn([value], env={}, reset_env=True,
5800 postfork_fn=lambda _: lock.Unlock(),
5801 input_fd=input_fd)
5802 if input_fd:
5803 input_fd.close()
5804 else:
5805 logging.error(value)
5806 except Exception:
5807
5808 logging.exception("Caught exception")
5809
5810 if cmdresult is None:
5811 logging.info("Sleeping for %0.1f seconds before returning",
5812 _RCMD_INVALID_DELAY)
5813 _sleep_fn(_RCMD_INVALID_DELAY)
5814
5815
5816 _Fail("Executing command '%s' failed" % cmd)
5817 elif cmdresult.failed or cmdresult.fail_reason:
5818 _Fail("Restricted command '%s' failed: %s; output: %s",
5819 cmd, cmdresult.fail_reason, cmdresult.output)
5820 else:
5821 return cmdresult.output
5822 finally:
5823 if lock is not None:
5824
5825 lock.Close()
5826 lock = None
5827
5830 """Creates or removes the watcher pause file.
5831
5832 @type until: None or number
5833 @param until: Unix timestamp saying until when the watcher shouldn't run
5834
5835 """
5836 if until is None:
5837 logging.info("Received request to no longer pause watcher")
5838 utils.RemoveFile(_filename)
5839 else:
5840 logging.info("Received request to pause watcher until %s", until)
5841
5842 if not ht.TNumber(until):
5843 _Fail("Duration must be numeric")
5844
5845 utils.WriteFile(_filename, data="%d\n" % (until, ), mode=0644)
5846
5873
5876 """ Checks if a file exists and returns information related to it.
5877
5878 Currently returned information:
5879 - file size: int, size in bytes
5880
5881 @type file_path: string
5882 @param file_path: Name of file to examine.
5883
5884 @rtype: tuple of bool, dict
5885 @return: Whether the file exists, and a dictionary of information about the
5886 file gathered by os.stat.
5887
5888 """
5889 try:
5890 stat_info = os.stat(file_path)
5891 values_dict = {
5892 constants.STAT_SIZE: stat_info.st_size,
5893 }
5894 return True, values_dict
5895 except IOError:
5896 return False, {}
5897
5900 """Hook runner.
5901
5902 This class is instantiated on the node side (ganeti-noded) and not
5903 on the master side.
5904
5905 """
5906 - def __init__(self, hooks_base_dir=None):
5907 """Constructor for hooks runner.
5908
5909 @type hooks_base_dir: str or None
5910 @param hooks_base_dir: if not None, this overrides the
5911 L{pathutils.HOOKS_BASE_DIR} (useful for unittests)
5912
5913 """
5914 if hooks_base_dir is None:
5915 hooks_base_dir = pathutils.HOOKS_BASE_DIR
5916
5917
5918 self._BASE_DIR = hooks_base_dir
5919
5921 """Check that the hooks will be run only locally and then run them.
5922
5923 """
5924 assert len(node_list) == 1
5925 node = node_list[0]
5926 assert node == constants.DUMMY_UUID
5927
5928 results = self.RunHooks(hpath, phase, env)
5929
5930
5931 return {node: (None, False, results)}
5932
5933 - def RunHooks(self, hpath, phase, env):
5934 """Run the scripts in the hooks directory.
5935
5936 @type hpath: str
5937 @param hpath: the path to the hooks directory which
5938 holds the scripts
5939 @type phase: str
5940 @param phase: either L{constants.HOOKS_PHASE_PRE} or
5941 L{constants.HOOKS_PHASE_POST}
5942 @type env: dict
5943 @param env: dictionary with the environment for the hook
5944 @rtype: list
5945 @return: list of 3-element tuples:
5946 - script path
5947 - script result, either L{constants.HKR_SUCCESS} or
5948 L{constants.HKR_FAIL}
5949 - output of the script
5950
5951 @raise errors.ProgrammerError: for invalid input
5952 parameters
5953
5954 """
5955 if phase == constants.HOOKS_PHASE_PRE:
5956 suffix = "pre"
5957 elif phase == constants.HOOKS_PHASE_POST:
5958 suffix = "post"
5959 else:
5960 _Fail("Unknown hooks phase '%s'", phase)
5961
5962 subdir = "%s-%s.d" % (hpath, suffix)
5963 dir_name = utils.PathJoin(self._BASE_DIR, subdir)
5964
5965 results = []
5966
5967 if not os.path.isdir(dir_name):
5968
5969
5970 return results
5971
5972 runparts_results = utils.RunParts(dir_name, env=env, reset_env=True)
5973
5974 for (relname, relstatus, runresult) in runparts_results:
5975 if relstatus == constants.RUNPARTS_SKIP:
5976 rrval = constants.HKR_SKIP
5977 output = ""
5978 elif relstatus == constants.RUNPARTS_ERR:
5979 rrval = constants.HKR_FAIL
5980 output = "Hook script execution error: %s" % runresult
5981 elif relstatus == constants.RUNPARTS_RUN:
5982 if runresult.failed:
5983 rrval = constants.HKR_FAIL
5984 else:
5985 rrval = constants.HKR_SUCCESS
5986 output = utils.SafeEncode(runresult.output.strip())
5987 results.append(("%s/%s" % (subdir, relname), rrval, output))
5988
5989 return results
5990
5993 """IAllocator runner.
5994
5995 This class is instantiated on the node side (ganeti-noded) and not on
5996 the master side.
5997
5998 """
5999 @staticmethod
6000 - def Run(name, idata, ial_params):
6001 """Run an iallocator script.
6002
6003 @type name: str
6004 @param name: the iallocator script name
6005 @type idata: str
6006 @param idata: the allocator input data
6007 @type ial_params: list
6008 @param ial_params: the iallocator parameters
6009
6010 @rtype: tuple
6011 @return: two element tuple of:
6012 - status
6013 - either error message or stdout of allocator (for success)
6014
6015 """
6016 alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
6017 os.path.isfile)
6018 if alloc_script is None:
6019 _Fail("iallocator module '%s' not found in the search path", name)
6020
6021 fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
6022 try:
6023 os.write(fd, idata)
6024 os.close(fd)
6025 result = utils.RunCmd([alloc_script, fin_name] + ial_params)
6026 if result.failed:
6027 _Fail("iallocator module '%s' failed: %s, output '%s'",
6028 name, result.fail_reason, result.output)
6029 finally:
6030 os.unlink(fin_name)
6031
6032 return result.stdout
6033
6036 """Simple class for managing a cache of block device information.
6037
6038 """
6039 _DEV_PREFIX = "/dev/"
6040 _ROOT_DIR = pathutils.BDEV_CACHE_DIR
6041
6042 @classmethod
6044 """Converts a /dev/name path to the cache file name.
6045
6046 This replaces slashes with underscores and strips the /dev
6047 prefix. It then returns the full path to the cache file.
6048
6049 @type dev_path: str
6050 @param dev_path: the C{/dev/} path name
6051 @rtype: str
6052 @return: the converted path name
6053
6054 """
6055 if dev_path.startswith(cls._DEV_PREFIX):
6056 dev_path = dev_path[len(cls._DEV_PREFIX):]
6057 dev_path = dev_path.replace("/", "_")
6058 fpath = utils.PathJoin(cls._ROOT_DIR, "bdev_%s" % dev_path)
6059 return fpath
6060
6061 @classmethod
6062 - def UpdateCache(cls, dev_path, owner, on_primary, iv_name):
6063 """Updates the cache information for a given device.
6064
6065 @type dev_path: str
6066 @param dev_path: the pathname of the device
6067 @type owner: str
6068 @param owner: the owner (instance name) of the device
6069 @type on_primary: bool
6070 @param on_primary: whether this is the primary
6071 node nor not
6072 @type iv_name: str
6073 @param iv_name: the instance-visible name of the
6074 device, as in objects.Disk.iv_name
6075
6076 @rtype: None
6077
6078 """
6079 if dev_path is None:
6080 logging.error("DevCacheManager.UpdateCache got a None dev_path")
6081 return
6082 fpath = cls._ConvertPath(dev_path)
6083 if on_primary:
6084 state = "primary"
6085 else:
6086 state = "secondary"
6087 if iv_name is None:
6088 iv_name = "not_visible"
6089 fdata = "%s %s %s\n" % (str(owner), state, iv_name)
6090 try:
6091 utils.WriteFile(fpath, data=fdata)
6092 except EnvironmentError, err:
6093 logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
6094
6095 @classmethod
6097 """Remove data for a dev_path.
6098
6099 This is just a wrapper over L{utils.io.RemoveFile} with a converted
6100 path name and logging.
6101
6102 @type dev_path: str
6103 @param dev_path: the pathname of the device
6104
6105 @rtype: None
6106
6107 """
6108 if dev_path is None:
6109 logging.error("DevCacheManager.RemoveCache got a None dev_path")
6110 return
6111 fpath = cls._ConvertPath(dev_path)
6112 try:
6113 utils.RemoveFile(fpath)
6114 except EnvironmentError, err:
6115 logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
6116