Package ganeti :: Module backend
[hide private]
[frames] | no frames]

Source Code for Module ganeti.backend

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